4. 内核启动阶段
当引导程序(GRUB、U-Boot 或 UEFI)把控制权交给内核镜像后,Linux 启动进入最“黑盒”的一段:从自解压到 start_kernel(),再到设备探测与模块加载,最终交出 init 进程。这一段通常只占整体启动时间的 20%~40%,但在嵌入式、云主机、虚拟机场景中,往往是优化收益最直接的部分。理解它的阶段划分与耗时点,是后续用 initcall_debug、bootgraph、ftrace 做定量优化的前提。
一、核心原理:内核启动的四个连续阶段
内核启动并非“一步到位”,而是严格串行的四个阶段,前一阶段未完成,后一阶段无法开始:
- 解压阶段(Decompression):引导程序加载的是压缩内核(
vmlinuz/zImage),内核头部自带解压器,将自身解压到内存并跳转到解压后的入口。 - 早期初始化(Early Init):建立临时页表、初始化内存管理、解析设备树或
boot_params、设置控制台,随后进入start_kernel()。 - 设备探测与子系统初始化(Probe & Initcall):按
initcall等级依次执行驱动、总线、文件系统、网络等初始化函数。 - 模块加载与根文件系统挂载(Module & Rootfs):加载 initramfs/initrd,按需加载内核模块,挂载真实根文件系统,最后启动
init。
这四个阶段在时间轴上首尾相接,任何一段的阻塞都会直接推迟用户空间可见的启动完成时间。
二、方法流程:如何定位内核阶段耗时
内核启动优化的第一步不是“改代码”,而是“量时间”。推荐按以下顺序采集数据:
- 在引导参数中加入
initcall_debug与printk.time=1,让每个 initcall 都带时间戳。 - 抓取
dmesg,用bootgraph.pl生成可视化时序图,快速识别长耗时 initcall。 - 对可疑驱动,用
ftrace的function_graph追踪其内部函数调用链。 - 检查
/sys/kernel/debug/devices_deferred,确认是否存在因依赖未就绪而反复延迟探测的设备。
# 采集 initcall 耗时
dmesg | grep -E "initcall|calling" | head -50
# 生成启动时序图
dmesg | ./scripts/bootgraph.pl > boot.svg
# 追踪某个驱动的 probe 函数
echo 1 > /sys/kernel/debug/tracing/events/ftrace/function/enable
echo 'pci_probe*' > /sys/kernel/debug/tracing/set_ftrace_filter
cat /sys/kernel/debug/tracing/trace_pipe
三、案例场景:一次真实的 1.8 秒内核耗时优化
某 ARM64 嵌入式设备冷启动总耗时 6.2 秒,其中内核阶段占 2.4 秒。通过 initcall_debug 发现:
| initcall 阶段 | 耗时 | 主要原因 | 优化动作 |
|---|---|---|---|
| 解压阶段 | 0.35 s | gzip 压缩率低、镜像 28 MB | 改用 zstd 压缩,镜像降至 19 MB |
| early_initcall | 0.12 s | DTB 节点过多,解析慢 | 裁剪未使用设备树节点 |
| device_initcall | 1.10 s | USB 控制器 probe 等待 PHY 就绪 | 改为模块按需加载,延迟 0.9 s |
| late_initcall | 0.23 s | 网络协议栈初始化 | 保留,属必要初始化 |
优化后内核阶段从 2.4 秒降至 1.1 秒,整体启动时间缩短约 21%。关键结论是:设备探测阶段的驱动 probe 往往是最大变量,而解压阶段则受镜像体积与压缩算法直接影响。
四、误区与复盘
误区一:只看总时间,不看阶段分布。很多团队直接对比“优化前后总启动时间”,却不知道时间花在解压还是 probe。没有阶段数据,优化就是盲猜。
误区二:把所有驱动都编译进内核。编译进内核意味着必须在启动时同步 probe,无法并行、无法延迟。对非关键外设,改为模块加载通常能显著缩短内核阶段。
误区三:忽略 initramfs 的重复解压。initramfs 本身是压缩的 cpio,内核解压一次,用户空间可能再解压一次,造成重复开销。
复盘建议:建立“内核启动基线”,每次内核配置或驱动变更后重新采集 initcall_debug 数据,对比各阶段耗时变化。把 bootgraph.pl 生成的时序图纳入 CI 产物,让性能回归可视化、可追踪。
内核启动阶段的优化核心在于“先测量、再定位、后裁剪”。解压阶段关注压缩算法与镜像体积,早期初始化关注设备树规模,设备探测关注驱动 probe 的同步阻塞,模块加载关注依赖顺序与 initramfs 设计。只有把四个阶段拆开量化,才能把内核启动时间从“不可控”变成“可优化”。