我知道这是一个非常老生常谈的问题,但还是希望能写一篇文章记录并看过一遍完整的kernel初始化函数到底干了啥。
kernel内核的入口函数在/init/main.c的start_kernel函数中,源代码在文章末尾,这里逐代码解释一遍start_kernel里干了什么。(注意,start_kernel的再前方还会存在一个汇编启动代码)
kernel源代码的获取:The Linux Kernel Archives
阶段一 早期环境
set_taskstackend_magic(&init_task):为 PID=0 的 idle 进程标记栈边界,防止栈溢出。
smp_setup_processor_id():初始化 SMP 对称多处理环境,标记当前启动 CPU 核 ID,让内核知道我现在在哪个核上面运行。
debug_objects_early_init():调试对象子系统的早期初始化。初始化 obj_hash 哈希表中每个桶的自旋锁,保证后续并发访问的线程安全。将静态预分配的对象池挂入 obj_pool 链表,以便在内存分配器(slab)还没就绪时,内核就能使用调试对象来跟踪锁、定时器等资源,排查死锁或内存泄漏问题。
init_vmlinux_build_id():初始化内核的构建 ID。它会为当前运行的内核镜像生成一个唯一的构建标识符。这个 ID 在调试和崩溃分析时非常有用,可以帮助开发者精准匹配内核版本和调试符号,快速定位问题。
cgroup_init_early():控制组(cgroup)子系统的早期初始化。cgroup 是 Linux 的资源隔离与限制机制,Docker 等容器技术就依赖它。这个函数会在系统启动早期完成,将会初始化从group所需的内部数据结构、注册根控制组并设置CPU、内存资源参数、为后续挂载cgroup创建文件系统。
local_irq_disable():关闭CPU本地中断,执行后,中断控制器发送给该CPU的所有中断信号都会被忽略,但中断控制器本身仍然可以接收和暂存来自硬件设备的中断请求。
阶段二 基本内核环境
boot_cpu_init():激活CPU,设置一系列的CPU位图,让内核承认这个CPU
page_address_init():初始化高端内存映射,在32位系统中,内核只能直接映射约896MB的物理内存(低端内存)。此函数负责初始化高端内存的映射表,为内核后续访问超过896MB的物理内存空间做好准备。在64位系统中,由于地址空间足够大,这个函数通常为空操作。
pr_notice("%s", linux_banner):这是内核启动后输出的第一条正式信息,会打印出Linux的版本号、编译时间、编译器信息等。
early_security_init():早期安全模块初始化,调用Linux安全模块(LSM)框架的早期初始化函数,为SELinux、AppArmor等安全模块的后续加载准备好内部数据结构,确保安全机制能尽早介入系统。
setup_arch(&command_line):架构特定设置,解析设备树(Device Tree)或ACPI表,获取硬件拓扑;初始化内存布局,设置memblock分配器;配置中断控制器、定时器等基础硬件;解析早期内核命令行参数
setup_boot_config() setup_command_line(command_line):这两个函数负责解析和处理内核命令行参数。setup_boot_config会处理bootconfig特性相关的参数,而setup_command_line则保存一份完整的命令行副本,供后续子系统使用。
setup_nr_cpu_ids():通过解析设备树或ACPI表,确定系统中实际可用的CPU核心数量,并设置nr_cpu_ids全局变量。这个值决定了后续per-CPU数据区域的大小。
setup_per_cpu_areas():为每个CPU分配独立的静态数据区域。per-CPU变量是内核中一种重要的同步机制,每个CPU都有自己独立的变量副本,无需加锁即可访问。这个函数会为所有可能的CPU创建这些区域。
smp_prepare_boot_cpu():这是一个架构特定的函数,允许各架构在引导CPU上执行一些额外的准备工作。例如,在ARM64架构上,它会设置CPU的启动模式标志。
boot_cpu_hotplug_init():初始化CPU热插拔子系统,为后续支持动态添加或移除CPU做好准备。它主要初始化热插拔状态机和相关的同步机制。
pr_notice("Kernel command line: %s\n", saved_command_line):最后,内核会打印出完整的启动命令行参数,方便调试和确认配置是否正确。这行日志在排查启动问题时非常有用。
这一阶段的代码完成了从“单核裸机”到“多核感知、内存可管理、硬件可描述”的关键转变。它让内核从汇编阶段的极简环境,正式进入一个具备基础架构、能够识别硬件拓扑、并准备好进行后续复杂初始化的C语言环境。
阶段三 内核启动命令参数解析与随机初始化
jump_label_init():初始化动态跳转标签机制。这是一种内核优化技术,通过在运行时动态替换指令(如将 nop 替换为 jmp 或反之),消除那些结果几乎固定的条件分支判断,避免 CPU 分支预测失败带来的性能损耗。它广泛用于内核的 tracepoint、调试开关等场景。
parse_early_param():解析内核命令行中的“早期参数”。这些参数通过 early_param 宏注册,需要在系统启动的最早阶段处理,例如 earlycon(早期控制台)、mem(内存大小限制)等。此函数会遍历命令行,将匹配的参数交给对应的处理函数执行。
after_dashes = parse_args("Booting kernel",
static_command_line, start_param,
stop_param - start_param,
-1, -1, NULL, &unknown_bootoption):这是最核心的参数解析调用,处理所有通过 __setup 或 module_param 注册的内核参数。它会遍历 static_command_line 中的每个参数,与 start_param 到 stop_param 之间的参数表进行匹配,找到对应的处理函数并执行。未识别的参数则交给 unknown_bootoption 函数处理,该函数会尝试将其作为模块参数或环境变量设置。
返回值 after_dashes 指向命令行中 -- 之后的部分,这部分内容通常是为 init 进程保留的参数。
print_unknown_bootoptions():输出所有内核无法识别的启动参数,方便开发者排查配置错误或拼写问题。
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
if (extra_init_args)
parse_args("Setting extra init args", extra_init_args,
NULL, 0, -1, -1, NULL, set_init_arg):传递 init 进程参数,这两段代码负责收集传递给 init 进程(即 PID 1 的初始进程)的参数。第一段解析命令行中 -- 之后的部分,第二段处理通过 extra_init_args 指定的额外参数。这些参数会被保存起来,在后续启动 init 进程时一并传递。
random_init_early(command_line):早期随机数初始化
阶段四 核心子系统初始化
这一段主要负责内核的日志、异常处理、内存管理、动态追踪等内容。
setup_log_buf(0):初始化内核日志缓冲区,printk日志系统生效
vfs_caches_init_early():初始化虚拟文件系统,为后续文件系统挂载、inode/dentry 等对象的分配做好准备,属于 VFS 完整初始化的前置步骤。
sort_main_extable():异常表排序,对内核的异常表进行排序。异常表记录了内核中那些可能触发缺页异常的指令地址及其对应的修复地址。排序后,内核在发生异常时可以通过二分查找快速定位并修复,显著提升异常处理效率。
trap_init():陷阱/异常处理初始化,初始化 CPU 的陷阱和异常处理机制,包括设置中断描述符表(IDT)中的各类异常处理入口(如除零错误、缺页异常、系统调用等)。这是内核能够捕获并响应硬件异常的基础。
mm_core_init():内存管理核心初始化。页表初始化:完成内核页表的最终构建,让虚拟地址能正确映射到物理地址;伙伴系统初始化:搭建物理内存分配器,将可用物理内存按 2 的幂次划分为空闲块;小内存分配器初始化:初始化 slab/slub 分配器,解决伙伴系统“仅能分配整页”的问题;vmalloc 区域初始化:为内核提供连续虚拟地址、非连续物理地址的内存分配能力。
poking_init():动态代码补丁初始化,初始化内核的代码补丁机制,为后续的静态密钥、ftrace 等需要在运行时动态修改内核代码的功能提供安全的内存映射区域。
ftrace_init():函数跟踪系统初始化,初始化 ftrace 函数跟踪框架。它会扫描内核中所有通过 mcount 机制预留的调用点,建立动态探测点数据库,为后续跟踪内核函数调用、分析性能瓶颈提供基础。
early_trace_init():启动早期跟踪功能。
这一阶段内核实现内存管理、日志输出、文件系统等基本功能。
阶段五 CPU调度器与内核工作队列初始化
sched_init():这是调度器子系统的核心初始化函数。此时内存分配器已经就绪,所以可以调用 kzalloc 等函数来分配数据结构。它主要完成:为每个 CPU 初始化运行队列(runqueue),这是调度器管理就绪任务的核心数据结构;初始化调度实体、调度类(如 CFS 公平调度类)和调度域;设置 init_task(PID 0 的 idle 进程)的调度权重;注册 CFS 的软中断 SCHED_SOFTIRQ,用于触发调度检查;初始化实时调度(RT)和截止时间调度(DL)的默认带宽限制。
if (WARN(!irqs_disabled(),
"Interrupts were enabled very early, fixing it\n"))
local_irq_disable():中断安全检查,内核期望在调度器初始化期间中断必须处于关闭状态。
radix_tree_init():基数树初始化,基数树是内核中一种高效的数据结构,用于通过整数键快速查找指针,广泛用于页缓存索引、IRQ 描述符管理等场景。预创建缓存可以避免后续使用时频繁申请内存,提升效率。
maple_tree_init():枫树初始化,枫树是一种缓存优化的 B 树变体,专门用于存储和查询非重叠范围。它已经取代了传统的红黑树,成为管理进程虚拟内存区域(VMA)的默认数据结构。相比红黑树,枫树在范围查询和内存占用方面都有更好的性能表现。
housekeeping_init():管家 CPU 初始化,这个机制用于 CPU 隔离场景:当用户通过 isolcpus= 内核参数指定某些 CPU 核心为隔离核心后,这些核心上的内核杂务(如调度器负载均衡、未绑定工作队列、定时器、RCU 回调等)需要迁移到其他核心上执行。
workqueue_init_early():工作队列早期初始化。工作队列是内核中一种将任务推迟到内核线程中异步执行的机制。早期初始化会创建必要的数据结构,允许后续代码创建和排队工作项,但工作项的实际执行要等到 workqueue_init() 完成、内核线程创建机制就绪之后才会开始。
rcu_init():初始化 RCU(Read-Copy-Update)同步机制。RCU 是一种适用于读多写少场景的高效同步机制,读操作无需加锁,写操作通过创建副本、原子替换、延迟释放旧数据来实现。
阶段六 中断、事件早期初始化
trace_init():事件追踪初始化,初始化内核事件追踪(Trace Events)子系统。从此处开始,内核中通过 trace_ 前缀定义的各类追踪点(如调度事件、中断事件)正式可用,开发者可以使用 ftrace、perf 等工具捕获内核行为。
if (initcall_debug)
initcall_debug_enable():初始化调用调试,如果内核命令行中包含 initcall_debug 参数,则开启初始化调用调试功能。它会记录每个 initcall(内核初始化函数)的执行时间,帮助开发者定位启动过程中耗时过长的初始化步骤。
context_tracking_init():上下文跟踪初始化,初始化上下文跟踪子系统,用于精确追踪 CPU 当前处于用户态还是内核态。它为高精度调度统计、功耗管理(如判断 CPU 是否空闲)以及虚拟化场景中的资源管理提供状态信息,是实现 NO_HZ_FULL 无滴答模式的基础。
early_irq_init():中断子系统早期初始化,初始化默认中断亲和性(让中断请求在所有 CPU 间均匀分布);设置 irq_desc 数据结构与虚拟中断号(virq)之间的映射关系;当配置了 CONFIG_SPARSE_IRQ 时,使用枫树(Maple Tree)实现非线性映射,支持更大的中断号范围
init_IRQ():中断控制器初始化,为每个 CPU 分配独立的中断栈(通常为 8KB 或 16KB),防止中断嵌套时栈溢出;调用 irqchip_init() 遍历设备树或 ACPI 表,匹配并初始化中断控制器驱动(如 GICv2/GICv3),向系统注册 irq_domain,建立硬件中断号到虚拟中断号的映射
tick_init():定时器滴答初始化,初始化内核的周期性定时器滴答机制。它注册一个名为 tick_notifier 的通知链,允许其他子系统在定时器事件发生时接收回调,为后续的 NO_HZ 和 NO_HZ_FULL 模式做好准备。
rcu_init_nohz():RCU 无滴答模式初始化,为 RCU 子系统启用无滴答模式支持。当系统配置了 NO_HZ 或 NO_HZ_FULL 时,CPU 可以在空闲或单任务运行时关闭定时器滴答,此函数确保 RCU 的宽限期检测和回调处理在这种模式下仍能正常工作。
init_timers():低精度定时器初始化,初始化内核的低精度定时器子系统。它会为每个 CPU 创建定时器管理数据结构(timer_base),并注册定时器软中断 TIMER_SOFTIRQ,使 mod_timer、del_timer 等 API 正式可用。
srcu_init():初始化 SRCU(Sleepable RCU),一种允许在读临界区内睡眠的 RCU 变体。它为每个 CPU 分配独立的 srcu_data 数据结构,用于跟踪 SRCU 的宽限期状态,适用于需要长时间持有读锁且允许阻塞的场景。
hrtimers_init():初始化高精度定时器(High-Resolution Timers)子系统。它为每个 CPU 创建红黑树用于管理高精度定时器,并注册 HRTIMER_SOFTIRQ 软中断。高精度定时器支持纳秒级精度,是实时内核和多媒体应用的基础。
softirq_init():初始化软中断子系统。它会为每个 CPU 创建 ksoftirqd 内核线程(每个 CPU 一个),用于在系统负载过高时延迟处理软中断,防止软中断长时间占用 CPU 导致用户进程饥饿。
timekeeping_init():初始化内核的时间记账子系统。它会读取持久时钟源(如 RTC)的初始时间,设置 CLOCK_REALTIME 的基准值,并初始化 CLOCK_MONOTONIC、CLOCK_BOOTTIME 等各类时间线,为后续的时间获取函数(如 ktime_get)提供正确的时间基准。
time_init():这是架构相关的时间初始化函数,通常完成:注册系统时钟源(Clocksource),如 ARM 架构的 arch_sys_counter;注册时钟事件设备(Clock Event Device),为定时器中断提供硬件支持;初始化高精度定时器的硬件基础。
这一阶段主要构建事件响应核时间管理基础设施。
阶段七 性能与安全防护初始化与中断开启
random_init():在时间记账(timekeeping)初始化完成后,正式初始化内核的随机数生成器(RNG)。此时系统已经具备了获取高精度时间戳的能力,可以为随机数生成器注入足够的熵,确保后续加密操作、地址随机化等安全机制能够获得高质量的随机数。
kfence_init():初始化 KFENCE(Kernel Electric-Fence),一种低开销的内存错误检测机制。它通过在内存分配时插入保护页,能够检测越界访问、释放后使用等内存错误,对性能影响极小,适合在生产环境中长期开启。
boot_init_stack_canary():初始化栈金丝雀(Stack Canary)保护机制。它生成一个随机数作为金丝雀值,放置在函数栈帧的返回地址之前。当函数返回时,内核会检查这个值是否被修改,如果被篡改则说明发生了栈溢出,立即触发内核恐慌,防止攻击者利用栈溢出漏洞劫持程序执行流。
perf_event_init():初始化内核的性能事件子系统。它会注册 perf 相关的软中断,初始化性能监控单元(PMU)的硬件计数器,为后续使用 perf 工具进行性能分析、采样和追踪提供基础。
profile_init():初始化内核的代码性能分析机制。它会为性能分析缓冲区分配内存,并注册 PROFILING 软中断,允许开发者通过 /proc/profile 接口获取内核函数的执行频率统计,用于定位性能热点。
call_function_init():初始化 SMP 系统中的跨 CPU 函数调用机制。它允许一个 CPU 向其他 CPU 发送中断信号,请求它们执行指定的函数。这是多核调度、RCU 回调处理、TLB 刷新等操作的基础。
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable():这是内核启动过程中一个重要的转折点:
首先检查中断是否确实处于关闭状态,如果意外开启则发出警告
然后将 early_boot_irqs_disabled 标志设为 false,告知内核“早期中断关闭阶段已结束”
最后调用 local_irq_enable() 正式开启中断,此后硬件中断可以被正常响应
kmem_cache_init_late():完成 slab 内存分配器的后期初始化。它会将早期阶段使用的临时 slab 缓存迁移到正式的缓存结构中,并启用缓存合并、回收等优化特性,提升内存分配效率。
阶段八 控制台、锁验证、内存初始化
console_init():控制台初始化,这是内核启动中一个重要的“分水岭”。在此之前,printk 输出的信息只是暂存在日志缓冲区中,用户看不到。console_init() 会初始化实际的串口或显卡控制台驱动,从此之后,内核日志会实时显示在屏幕上,方便开发者观察后续初始化过程。注释中特别提到这是一个“HACK”,因为它发生在 PCI 等总线枚举之前,所以控制台驱动需要能处理这种早期环境。
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later, panic_param):检查内核命令行参数是否过长。如果引导程序传递的参数超过了内核的缓冲区限制,panic_later 会被设置为非空值,此时内核会立即触发 panic,防止因参数截断导致不可预知的行为。
lockdep_init():初始化锁依赖检测器(Lock Dependency Validator)。这是内核中一个强大的调试工具,能够追踪所有锁的获取顺序,构建锁依赖图,在运行时检测潜在的死锁风险。初始化完成后,后续的锁操作都会被记录和验证。
locking_selftest():锁自检,执行锁机制的自检程序。它会模拟各种复杂的锁场景(如中断上下文中的锁操作、不同锁类型之间的嵌套),验证锁依赖检测器能否正确识别潜在的死锁和锁反转问题。注释特别说明这必须在中断开启后运行,因为需要测试硬中断/软中断上下文中的锁使用情况。
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
pr_crit("initrd overwritten ... - disabling it.\n");
initrd_start = 0;
}
#endif
initrd 内存覆盖检查,检查初始内存盘(initrd)是否被内核覆盖。如果 initrd 的起始物理地址低于 min_low_pfn(内核保留的低端内存边界),说明 initrd 所在的内存区域已被内核使用,数据可能已损坏。此时内核会输出严重错误信息并禁用 initrd,避免后续使用损坏的数据导致系统崩溃。
setup_per_cpu_pageset():为每个 CPU 初始化页缓存(page cache)的本地页集。每个 CPU 会获得一个预分配的热页链表,用于加速内存页的分配和回收,减少多核竞争,提升内存管理效率。
numa_policy_init():初始化 NUMA(非统一内存访问)内存策略子系统。它会创建默认的内存策略对象,并初始化策略相关的 slab 缓存,为后续进程设置内存分配策略(如绑定到特定节点、交错分配等)做好准备。
acpi_early_init():执行 ACPI(高级配置与电源接口)的早期初始化。它会解析 ACPI 表,获取硬件配置信息,初始化 ACPI 中断处理,并为后续的 PCI 枚举、电源管理等功能提供基础。
sched_clock_init():初始化调度器使用的时钟源。调度时钟是调度器进行时间片计算和负载统计的基础,它需要高精度且单调递增的时间戳。此函数会注册调度时钟的读取函数,确保调度器能获取准确的时间信息。
calibrate_delay():校准 CPU 的 BogoMIPS 值。通过执行一个精确的延迟循环,测量 CPU 在单位时间内能执行的循环次数,用于实现 udelay() 等短延迟函数。这个值虽然被称为“Bogo”(假的),但对于需要微秒级延迟的驱动程序至关重要。
arch_cpu_finalize_init():执行架构相关的 CPU 最终初始化。这是架构代码在启动过程中的最后一个初始化钩子,通常用于完成 CPU 特性的最终配置、启用性能监控单元、设置特定寄存器等收尾工作。
pid_idr_init():初始化进程 ID 分配器。它会创建用于管理 PID 的 IDR(ID Radix)数据结构,为后续创建新进程时分配唯一的 PID 做好准备。
anon_vma_init():初始化匿名虚拟内存区域(Anonymous VMA)的管理结构。匿名 VMA 用于管理没有对应文件映射的内存区域(如进程的堆和栈),此函数会创建专用的 slab 缓存,用于快速分配和回收匿名 VMA 对象。
阶段九 进程、文件系统、安全与命名空间初始化
thread_stack_cache_init():为内核线程的栈空间创建专用的 slab 缓存。每个内核线程需要独立的栈空间(通常为 8KB 或 16KB),通过预创建缓存可以快速分配和回收线程栈,提升进程创建的效率。
cred_init():初始化进程凭证(Credentials)管理子系统。凭证记录了进程的 UID、GID、权限位等安全属性,此函数会创建专用的 slab 缓存,并为 init_task 分配初始凭证,为后续的权限检查和安全审计打下基础。
fork_init():初始化进程创建(fork)机制。它会根据系统内存大小计算默认的最大进程数,初始化进程信号队列,并创建 task_struct 的 slab 缓存,为后续创建新进程做好准备。
proc_caches_init():初始化进程子系统所需的各种 slab 缓存,包括 mm_struct(内存描述符)、vm_area_struct(虚拟内存区域)、files_struct(文件描述符表)等核心数据结构的缓存,为进程的完整功能提供内存分配支持。
uts_ns_init():初始化 UTS(Unix Timesharing System)命名空间。UTS 命名空间允许每个容器拥有独立的主机名和域名,此函数会为 init_task 创建初始的 UTS 命名空间,并设置默认的主机名。
key_init():初始化内核密钥管理子系统。它会创建密钥相关的 slab 缓存,初始化密钥环(keyring)数据结构,为后续的加密密钥、认证令牌等安全凭证的管理提供基础。
security_init():初始化 Linux 安全模块(LSM)框架。它会调用 SELinux、AppArmor、TOMOYO 等安全模块的初始化函数,启用对应的安全策略,为系统提供强制访问控制能力。
dbg_late_init():执行调试子系统的后期初始化。在内存分配器、锁检测等基础机制就绪后,此函数会完成调试功能的最终配置,例如启用内核内存泄漏检测等高级调试特性。
net_ns_init():初始化网络命名空间子系统。它会创建初始网络命名空间,初始化网络相关的 sysctl 接口,为后续的网络协议栈初始化和容器网络隔离做好准备。
vfs_caches_init():完成虚拟文件系统(VFS)缓存的完整初始化。它会创建 inode、dentry 等核心文件系统对象的 slab 缓存,初始化文件系统注册表,为后续挂载根文件系统做好准备。
pagecache_init():初始化页缓存(Page Cache)子系统。页缓存是内核用于缓存文件数据的关键机制,此函数会初始化页缓存的基数树(Radix Tree)结构,为后续的文件读写操作提供高效的缓存支持。
signals_init():初始化进程信号机制。它会创建信号队列的 slab 缓存,为 init_task 初始化信号处理结构,使内核能够向进程发送和传递信号(如 SIGKILL、SIGTERM 等)。
seq_file_init():初始化序列文件(seq_file)接口。seq_file 是内核导出复杂数据(如 /proc 文件内容)的标准接口,此函数会创建相关的 slab 缓存,为后续注册 /proc 文件提供支持。
proc_root_init():初始化 /proc 虚拟文件系统的根节点。它会创建 /proc 目录及其下的核心子目录(如 /proc/self、/proc/thread-self),并注册基本的进程信息导出接口。
nsfs_init():初始化命名空间文件系统(nsfs)。nsfs 用于将命名空间对象以文件描述符的形式暴露给用户空间,是实现容器技术(如 Docker)中命名空间共享和迁移的关键机制。
cpuset_init():初始化 cpuset 子系统。cpuset 是 cgroup 的一个子系统,用于限制进程只能运行在特定的 CPU 核心和内存节点上,此函数会创建默认的 cpuset 根组。
cgroup_init():完成 cgroup 控制组的完整初始化。它会注册所有 cgroup 子系统(如 CPU、内存、I/O 控制器),创建默认的 cgroup 层级结构,为资源隔离和限制提供完整支持。
taskstats_init_early():初始化任务统计接口的早期部分。taskstats 是内核导出进程级统计信息(如 CPU 使用时间、内存占用)的 Netlink 接口,此函数会创建相关的缓存和数据结构。
delayacct_init():初始化延迟记账(Delay Accounting)子系统。它会初始化用于跟踪进程等待 CPU、I/O 等资源所花费时间的结构,为性能分析和资源调度提供数据支持。
acpi_subsystem_init():完成 ACPI 子系统的完整初始化。在早期 ACPI 初始化的基础上,此函数会进一步初始化 ACPI 电源管理、设备枚举等功能,为后续的 PCI 总线扫描和驱动加载做好准备。
arch_post_acpi_subsys_init():执行架构特定的 ACPI 后初始化。各架构可以在此钩子中完成依赖于 ACPI 表的额外配置,例如设置特定的中断路由或平台设备注册。
kcsan_init():初始化 KCSAN(Kernel Concurrency Sanitizer),一种内核数据竞争检测工具。它会在运行时监控内存访问模式,检测潜在的并发竞争条件,帮助开发者定位多线程同步问题。
arch_call_rest_init():这是 start_kernel 的最后一个重要调用。它会创建第一个内核线程(PID 1 的 init 进程),然后引导 CPU 进入空闲循环。init 进程会继续执行后续的设备驱动初始化、根文件系统挂载和用户空间启动等任务。注释中“we're now alive”标志着内核核心初始化已基本完成。
asmlinkage __visible __init __no_sanitize_address __noreturn __no_stack_protector
void start_kernel(void)
{
char *command_line;
char *after_dashes;
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();
init_vmlinux_build_id();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled = true;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them.
*/
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
early_security_init();
setup_arch(&command_line);
setup_boot_config();
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
boot_cpu_hotplug_init();
pr_notice("Kernel command line: %s\n", saved_command_line);
/* parameters may set static keys */
jump_label_init();
parse_early_param();
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);
print_unknown_bootoptions();
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
if (extra_init_args)
parse_args("Setting extra init args", extra_init_args,
NULL, 0, -1, -1, NULL, set_init_arg);
/* Architectural and non-timekeeping rng init, before allocator init */
random_init_early(command_line);
/*
* These use large bootmem allocations and must precede
* initalization of page allocator
*/
setup_log_buf(0);
vfs_caches_init_early();
sort_main_extable();
trap_init();
mm_core_init();
poking_init();
ftrace_init();
/* trace_printk can be enabled here */
early_trace_init();
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init();
if (WARN(!irqs_disabled(),
"Interrupts were enabled *very* early, fixing it\n"))
local_irq_disable();
radix_tree_init();
maple_tree_init();
/*
* Set up housekeeping before setting up workqueues to allow the unbound
* workqueue to take non-housekeeping into account.
*/
housekeeping_init();
/*
* Allow workqueue creation and work item queueing/cancelling
* early. Work item execution depends on kthreads and starts after
* workqueue_init().
*/
workqueue_init_early();
rcu_init();
/* Trace events are available after this */
trace_init();
if (initcall_debug)
initcall_debug_enable();
context_tracking_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
tick_init();
rcu_init_nohz();
init_timers();
srcu_init();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
/* This must be after timekeeping is initialized */
random_init();
/* These make use of the fully initialized rng */
kfence_init();
boot_init_stack_canary();
perf_event_init();
profile_init();
call_function_init();
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable();
kmem_cache_init_late();
/*
* HACK ALERT! This is early. We're enabling the console before
* we've done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init();
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param);
lockdep_init();
/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/
locking_selftest();
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
setup_per_cpu_pageset();
numa_policy_init();
acpi_early_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
arch_cpu_finalize_init();
pid_idr_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
thread_stack_cache_init();
cred_init();
fork_init();
proc_caches_init();
uts_ns_init();
key_init();
security_init();
dbg_late_init();
net_ns_init();
vfs_caches_init();
pagecache_init();
signals_init();
seq_file_init();
proc_root_init();
nsfs_init();
cpuset_init();
cgroup_init();
taskstats_init_early();
delayacct_init();
acpi_subsystem_init();
arch_post_acpi_subsys_init();
kcsan_init();
/* Do the rest non-__init'ed, we're now alive */
arch_call_rest_init();
/*
* Avoid stack canaries in callers of boot_init_stack_canary for gcc-10
* and older.
*/
#if !__has_attribute(__no_stack_protector__)
prevent_tail_call_optimization();
#endif
}