一、进入内核入口点
在 ARM64 平台上,内核镜像通常以 Image 或 Image.gz 形式被 Bootloader(如 U-Boot、BL33)加载到内存,并通过寄存器(如 x0
、x1
、x2
)传递设备树(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();
-
初始化 RCU(Read-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启动后续再介绍