当前位置: 首页 > news >正文

Linux kernel arm64 启动流程

一、进入内核入口点

在 ARM64 平台上,内核镜像通常以 ImageImage.gz 形式被 Bootloader(如 U-Boot、BL33)加载到内存,并通过寄存器(如 x0x1x2)传递设备树(DTB)的地址。

入口点位于:

arch/arm64/kernel/head.S

主要执行步骤:

  • 设置异常级别(通常内核运行在 EL1)。

  • 初始化栈指针(sp)。

  • 设置内核页表,开启 MMU。

  • 建立异常向量表。

  • 跳转到 C 语言入口:start_kernel()


二、早期汇编阶段(head.S)

head.S 中,内核完成最基础的硬件环境搭建:

此阶段结束后,系统具备了最小可运行环境,跳入 C 语言。

primary_entry├─ record_mmu_state         // 检查进入时 MMU/Cache 状态├─ preserve_boot_args       // 保存 boot args (x0=dtb)├─ init_kernel_el           // 切换到 EL1├─ __cpu_setup              // CPU 寄存器初始化└─ __primary_switch├─ __enable_mmu        // 开启 MMU├─ __pi_early_map_kernel // 建立正式页表└─ __primary_switched├─ init_cpu_task   // 初始化 task/栈├─ 设置向量表、保存 dtb/偏移├─ finalise_el2└─ start_kernel   // 进入 C 入口

启动参数

注意primary_switch和带ed的


三、C 语言入口:start_kernel()

C 语言初始化入口是:

init/main.c: start_kernel()

  • 这是内核启动的核心函数,几乎所有初始化工作都从这里展开。

函数属性和调用约束

  • asmlinkage:保证函数参数从寄存器传递,符合汇编调用约定。

  • __visible:防止编译器优化掉符号,使调试和链接器可见。

  • __init:该函数只在启动期间使用,启动完成后其所在代码段可回收。

  • __no_sanitize_address:关闭 AddressSanitizer 检测。

  • __noreturn:函数不会返回。

  • __no_stack_protector:关闭栈保护,避免初始化阶段栈保护干扰。


初始化内核最基本的结构

  • set_task_stack_end_magic():初始化 init_task 栈边界标记,用于栈溢出检测。

  • smp_setup_processor_id():获取当前 CPU 的物理 ID。

  • debug_objects_early_init():初始化内核调试对象系统。

  • init_vmlinux_build_id():记录内核 Build ID,用于调试。

  • cgroup_init_early():初始化控制组 (cgroup) 的基础数据结构。


关闭中断,保证安全初始化

  • 禁用本地 CPU 中断,防止在硬件和内核数据结构尚未初始化时被中断打断。

  • 设置标记 early_boot_irqs_disabled,方便后续检查。


Boot CPU 初始化和早期架构设置

这些函数主要做 CPU 和架构相关的早期设置

  • boot_cpu_init():初始化 boot CPU 的特权寄存器和 CPU 状态。

  • page_address_init():设置内存页管理相关的基础数据结构。

  • pr_notice("%s", linux_banner):打印内核启动横幅信息。

  • setup_arch():架构相关初始化,如物理内存扫描、异常向量表设置、页表准备。

  • jump_label_init() / static_call_init():静态分支预测优化初始化,用于 LSM、安全模块等。

  • early_security_init():早期安全模块初始化。

  • setup_boot_config():读取启动参数和硬件配置。

  • setup_command_line():保存内核命令行参数。

  • setup_nr_cpu_ids():计算系统 CPU 数量。

  • setup_per_cpu_areas():分配每个 CPU 的内核栈和数据结构。

  • smp_prepare_boot_cpu():arch-specific boot CPU hooks。

  • early_numa_node_init() / boot_cpu_hotplug_init():NUMA 节点和 CPU 热插拔早期初始化。


解析启动命令行参数

  • parse_early_param():解析内核启动参数中早期注册的参数(__setup() 宏注册的参数)。

  • parse_args():处理剩余的参数,解析传递给 init 的命令行选项。

  • set_init_arg:设置 init 进程的启动参数。

  • unknown_bootoption:记录无法识别的参数。


早期随机数生成器和日志

  • random_init_early():初始化硬件或软件随机数生成器。

  • setup_log_buf():初始化内核日志缓冲区。

  • vfs_caches_init_early():早期文件系统缓存初始化。

  • sort_main_extable():初始化异常表(异常处理)。

  • trap_init():初始化陷阱向量和异常处理。

  • mm_core_init():核心内存管理初始化。

  • poking_init() / ftrace_init() / early_trace_init():调试和跟踪设施初始化。


调度器与内核数据结构初始化

  • sched_init():初始化进程调度器。

  • radix_tree_init() / maple_tree_init():内核数据结构初始化。

  • housekeeping_init():初始化内核维护结构。

  • workqueue_init_early():允许早期工作队列创建和调度。

  • RCU 初始化:包括 RCU、kvfree RCU 等。

  • trace_init():初始化跟踪事件系统。


IRQ 与时钟相关初始化

  • early_irq_init() / init_IRQ():中断控制器初始化。

  • tick_init() / timers_init() / hrtimers_init():定时器初始化。

  • timekeeping_init() / time_init():系统时间初始化。

  • RCU nohz:无节拍模式初始化。


内核随机数和防护机制

  • random_init():完整 RNG 初始化。

  • kfence_init():内存安全检测。

  • boot_init_stack_canary():栈溢出检测。


性能与调试设施

  • perf_event_init():性能计数器初始化。

  • profile_init():系统调用和性能分析初始化。

  • call_function_init():异步函数调用初始化。


启用中断

  • 启用本地 CPU 中断,允许内核正常调度和中断处理。


内存管理与控制组初始化

  • kmem_cache_init_late():slab/缓存分配器初始化。

  • setup_per_cpu_pageset() / numa_policy_init():NUMA 和 per-CPU 内存分配策略。


控制组、命名空间、进程等初始化

  • 内核所有高级结构初始化,包括:

    • 任务/进程管理pid_idr_init() / fork_init()

    • 虚拟内存和页缓存anon_vma_init() / pagecache_init()

    • 命名空间uts_ns_init() / net_ns_init() / nsfs_init()

    • 控制组cgroup_init()

    • 安全/密钥cred_init() / key_init() / security_init()

    • 性能/统计taskstats_init_early() / delayacct_init()


ACPI 和平台相关初始化

  • ACPI:硬件抽象和电源管理初始化。

  • arch_post_acpi_subsys_init():架构相关的后 ACPI 初始化。


启动第一个用户空间进程

rest_init();

  • 创建 第一个内核线程 init,通常执行 /sbin/init 或 initramfs。

  • 至此内核完成从 汇编 -> C 语言 -> 用户态 的完整启动流程。


四、架构相关初始化:setup_arch()

核心职责

setup_arch() 是 ARM64 Linux 内核 C 语言阶段的早期架构初始化函数。它在 head.S 汇编阶段完成最基本 CPU/内存设置后被调用,作用是设置内核运行所需的硬件环境、内存布局以及平台相关功能。主要包括内存管理初始化、CPU 异常屏蔽、平台硬件初始化以及 SMP 支持等。


内存管理初始化

setup_initial_init_mm(_stext, _etext, _edata, _end); arm64_memblock_init(); paging_init(); bootmem_init();

  • 初始化内核的初始页表和内存段,确保内核代码段、数据段和 BSS 段可用。

  • 初始化 memblock,管理早期物理内存。

  • 初始化分页机制(MMU 页表和内核虚拟映射)。

  • 初始化早期内存分配器(bootmem allocator)。


内核命令行

*cmdline_p = boot_command_line; parse_early_param();

  • 保存 bootloader 传入的内核命令行。

  • 解析早期内核启动参数,如 console=, root=, loglevel=


KASLR 初始化

kaslr_init();

  • 随机化内核加载地址,提高安全性。


早期固定映射(Fixmap / IO Remap)

early_fixmap_init(); early_ioremap_init();

  • 提供早期固定虚拟地址来访问硬件寄存器或内核关键数据。

  • 在 MMU 开启之前或开启初期使用。


平台硬件初始化

setup_machine_fdt(__fdt_pointer); acpi_table_upgrade(); acpi_boot_table_init(); if (acpi_disabled) unflatten_device_tree(); psci_dt_init(); psci_acpi_init(); arm64_rsi_init();

  • 解析设备树(FDT)或 ACPI 表,获取硬件信息(CPU、内存、设备)。

  • 初始化 PSCI(CPU 热插拔和电源管理接口)。

  • 初始化 ARM64 低级平台接口(RSI)。


CPU 异常屏蔽

local_daif_restore(DAIF_PROCCTX_NOIRQ); cpu_uninstall_idmap();

  • DAIF 异常屏蔽寄存器控制 IRQ / FIQ / SError / Debug。

  • 这里取消调试和 SError 屏蔽,保证早期错误可报告,但仍保持中断屏蔽。

  • 卸载早期 identity mapping,防止 MMU speculatively fetch 新的页表条目。


EFI / Xen 初始化

xen_early_init(); efi_init();

  • 为 ACPI / boot table 解析做好准备。


静态 key 与动态 SCS 初始化

jump_label_init(); dynamic_scs_init();

  • 初始化静态分支(Static Keys),优化条件分支。

  • 初始化安全关键寄存器(Dynamic SCS)。


SMP 与 CPU 拓扑

init_bootcpu_ops(); smp_init_cpus(); smp_build_mpidr_hash();

  • 初始化 boot CPU 特定操作。

  • 初始化多核 CPU 支持。

  • 构建 MPIDR 哈希表,用于 CPU 拓扑管理。

感觉多核启动想单独讲一下了


启动检测与警告

if (!efi_enabled(EFI_BOOT)) {if ((u64)_text % MIN_KIMG_ALIGN) pr_warn(FW_BUG "Kernel image misaligned at boot..."); WARN_TAINT(mmu_enabled_at_boot, TAINT_FIRMWARE_WORKAROUND, FW_BUG "Booted with MMU enabled!"); }

  • 检查内核对齐情况。

  • 检查启动时 MMU 是否已经开启,不符合规范会打 TAINT 警告。


其他安全与调试初始化

kasan_init(); request_standard_resources(); early_ioremap_reset();

  • KASAN 早期初始化(内核地址错误检测)。

  • 标准资源请求和早期 IO remap 重置。


TTBR0 与初始化线程信息

init_task.thread_info.ttbr0 = phys_to_ttbr(__pa_symbol(reserved_pg_dir));

  • 在开启 SW_TTBR0_PAN 功能时,确保 init 线程访问用户空间时会触发翻译错误,从而保护内核。


x1-x3 启动参数检查

if (boot_args[1] || boot_args[2] || boot_args[3]) { pr_err("WARNING: x1-x3 nonzero in violation of boot protocol..."); }

  • 根据 ARM64 Boot Protocol,x1~x3 在启动时必须为 0。

  • 非零值表示 bootloader 或内核版本不兼容。


五、内核子系统初始化:initcall 机制

内核采用 initcall 机制 分阶段初始化各个子系统。start_kernel() 中最终会调用:

rest_init() → kernel_init() → kernel_init_freeable() → do_basic_setup() → do_initcalls()

其中 do_initcalls() 会按等级依次执行所有注册的初始化函数。

Initcall 等级如下(位于 include/linux/init.h):

  • pure_initcall:最早期的初始化。

  • core_initcall:核心子系统。

  • postcore_initcall:核心之后。

  • arch_initcall:体系结构相关初始化。

  • subsys_initcall:子系统(如 VFS、PCI、USB)。

  • fs_initcall:文件系统相关。

  • device_initcall:设备驱动初始化。

  • late_initcall:收尾阶段。

通过这种分层机制,内核保证依赖顺序正确,各个子系统逐步上线。

initcall合集

带sync需要等待,不带sync可以多核并行开始


六、用户空间启动

当内核子系统初始化完毕后,最终进入用户空间:

RCU 调度器初始化

rcu_scheduler_starting();

  • 初始化 RCURead-Copy Update)调度器,确保内核的读-写锁机制可以正常工作。

  • 这是启动多任务调度前必须做的操作。


创建 init 进程(PID 1)

pid = user_mode_thread(kernel_init, NULL, CLONE_FS);

  • user_mode_thread() 用来创建一个用户态线程,这里就是 init 进程

  • kernel_init 是 init 进程实际执行的函数。

  • CLONE_FS 表示线程共享文件系统信息。

  • init 进程最终会成为 PID=1 的用户态进程,是系统第一个启动的用户进程。


固定 init 进程在 Boot CPU 上

rcu_read_lock(); tsk = find_task_by_pid_ns(pid, &init_pid_ns); tsk->flags |= PF_NO_SETAFFINITY; set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id())); rcu_read_unlock();

  • 为了确保在多核系统中 init 进程不会被迁移到其他 CPU 上(因为调度器还没完全初始化)。

  • 使用 PF_NO_SETAFFINITY 阻止进程被迁移。

  • set_cpus_allowed_ptr() 只允许它在 Boot CPU 上运行。


初始化 NUMA 默认策略

numa_default_policy();

  • 设置 NUMA(Non-Uniform Memory Access)内存访问策略

  • 确保后续分配内存时,内核知道首选的节点。


创建 kthreadd(内核线程管理器)

pid = kernel_thread(kthreadd, NULL, NULL, CLONE_FS | CLONE_FILES); rcu_read_lock(); kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); rcu_read_unlock();

  • kthreadd 是 Linux 内核的 内核线程管理器

  • 它负责管理内核后台线程(kthreads)的创建和调度。

  • CLONE_FS | CLONE_FILES 让它共享文件系统和文件描述符。


设置系统状态

system_state = SYSTEM_SCHEDULING; complete(&kthreadd_done);

  • system_state 标记为 可调度,意味着内核进入正式调度阶段。

  • complete(&kthreadd_done) 通知 init 进程和其他线程,kthreadd 已经准备好。


启动 Boot CPU 调度器

schedule_preempt_disabled(); cpu_startup_entry(CPUHP_ONLINE);

  • schedule_preempt_disabled() 调用一次调度函数,启动调度器循环。

  • cpu_startup_entry() 进入 CPU idle loop,开始执行空闲线程(idle thread)。

  • Boot CPU 现在已经进入可调度状态,系统开始多任务执行。

回到kernel_init看如何进入用户态初始化

  • 调用 kernel_init()run_init_process()

  • 尝试执行:

    • /sbin/init

    • /etc/init

    • /bin/init

    • /bin/sh

如果成功,系统转交控制权给用户空间的 init 进程(现代系统多为 systemd)。

至此,Linux 内核启动流程完成。

systemd启动后续再介绍

http://www.dtcms.com/a/358985.html

相关文章:

  • ubuntu 安装conda, ubuntu24安装miniConda
  • python制作一个股票盯盘系统
  • 三重积分从入门到入土
  • 微风PLC编程软件下载(C4G02_Develop)
  • GESP5级2024年03月真题解析
  • Python实现全角数字转半角数字的完整教程
  • 一站式可视化运维:解锁时序数据库 TDengine 的正确打开方式
  • 数值分析——算法的稳定性
  • 鸿蒙服务端开发资料汇总
  • 中级统计师-统计实务-第三章 国民经济核算
  • 从支付工具到收益资产:稳定币在 Berachain 上的二次进化
  • 数位 dp
  • 函数(1)
  • React useState基本使用
  • 鸿蒙ArkTS 核心篇-13-if分支语句
  • 玄机靶场 | 第五届红明谷-异常行为溯源
  • Fortran二维数组去重(unique)算法实战
  • WSL使用指南
  • AUTOSAR进阶图解==>AUTOSAR_TR_FrancaIntegration
  • 【超全汇总】MySQL服务启动命令手册(Linux+Windows+macOS)(中)
  • 腾讯云OpenCloudOS 9系统部署OpenTenBase数据库详细教程
  • 轻量xlsx读取库xlsx_drone的编译与测试
  • 关联容器(Associative containers)
  • Windows server 2012安装步骤
  • 为什么要用 Markdown?以及如何使用它
  • 软考备考(5)
  • vite Rendering 10 pagesReferenceError: document is not defined
  • 35.Ansible的yaml语法与playbook的写法
  • Java基础知识(十)
  • 【软考架构】面向服务的体系结构(SOA)深度解析