1. 内核启动流程全景:从BIOS/UEFI到init进程的完整链路

大家好,我是你们的调试老手。今天咱们聊聊内核启动的全景图。

说实话,我见过太多工程师一遇到启动卡住就慌了神。要么对着黑屏发呆,要么在dmesg里乱翻。其实啊,你只要把启动链路理清楚,问题就解决了一半。

我个人习惯把启动分成四个宏观阶段。每个阶段都有它自己的脾气。你想想看,从按下电源键到出现登录提示符,这中间发生了多少事?

1.1 第一阶段:固件层(BIOS/UEFI)

按下电源键,CPU先跑的是固件代码。这时候内核还没影呢。

  • BIOS(传统方式):先做POST自检,然后按启动顺序找MBR。我记得有次调试,客户说机器启动到一半就黑屏。查了半天,结果是BIOS里启动顺序配错了,它压根没去找硬盘上的内核。
  • UEFI(现代方式):更灵活,支持GPT分区,能直接加载EFI应用。我建议新项目都用UEFI,省心。

关键点:这个阶段卡住,99%是硬件问题或固件配置错误。别急着怀疑内核。

1.2 第二阶段:Bootloader(引导加载器)

固件把控制权交给bootloader。常见的有GRUB2、U-Boot(嵌入式)。

Bootloader的任务很简单:加载内核镜像到内存,然后跳过去执行。但这里有个坑——它得知道内核放哪了,根文件系统在哪。

我曾经遇到一个案例:GRUB配置里root参数写错了,内核启动到一半就panic,提示找不到根设备。说白了,就是bootloader没把正确的参数传给内核。

调试技巧:在GRUB菜单按'e'编辑启动参数,加个init=/bin/bash试试。如果系统能进单用户模式,那问题多半在init进程或根文件系统上。

1.3 第三阶段:内核初始化(核心阶段)

这是最复杂、也最容易出问题的阶段。内核从入口函数start_kernel()开始,一路初始化各种子系统。

我把它拆成几个子步骤:

  1. 早期架构初始化:设置页表、中断向量、控制寄存器。嗯,这里要是出问题,你连个打印都看不到。
  2. 控制台初始化:printk开始工作。我个人习惯在这里加一些自定义打印,方便定位。
  3. 内存管理初始化:建立伙伴系统、slab分配器。我记得有次调试,内核在mm_init()里卡住了,原因是内存描述符被破坏了。
  4. 调度器初始化:创建init进程(PID 1)。
  5. 设备驱动初始化:枚举PCI设备、加载驱动。这是panic高发区。

注意:如果内核在do_initcalls()阶段卡住,多半是某个驱动模块的init函数出了问题。我曾经遇到一个网卡驱动,它在申请DMA缓冲区时死循环了——因为硬件没就绪,它一直在轮询。

为什么会在这个阶段panic?说白了,内核发现某个关键资源拿不到,或者数据结构被破坏,它觉得没法继续了,就主动触发panic。比如BUG_ON()panic()调用。

1.4 第四阶段:init进程启动

内核初始化完成后,会启动用户空间的第一个进程——init。传统上是/sbin/init,现代系统可能是systemd

这个阶段卡住,通常是因为:

  • 根文件系统挂载失败(比如fstab写错了)
  • init程序本身损坏或不存在
  • 依赖的服务启动超时

我遇到过最离谱的一次:客户说系统启动到一半就重启。查了三天,发现是init脚本里有个rm -rf /的bug——嗯,你没看错,它把根目录删了。

1.5 宏观阶段总结

为了让你更直观地理解,我画了一张流程图:

内核启动宏观阶段流程图 BIOS/UEFI 固件初始化 Bootloader 加载内核镜像 内核初始化 start_kernel() → 子系统初始化 init进程启动 /sbin/init 或 systemd 内核初始化子步骤 ① 早期架构初始化(页表、中断) ② 控制台初始化(printk可用) ③ 内存管理初始化(伙伴系统) ④ 调度器初始化(创建PID 1) ⑤ 设备驱动初始化(do_initcalls) ⑥ 根文件系统挂载 常见卡住/panic位置 固件POST失败 → bootloader找不到内核 → 内存初始化panic → 驱动init死循环 → 根文件系统挂载失败

1.6 实战:如何快速定位卡住阶段

遇到启动卡住,我的第一反应是:先看它卡在哪个阶段。

现象 可能阶段 排查方法
黑屏,无任何输出 BIOS/UEFI 或 Bootloader 检查硬件、启动顺序、GRUB配置
有内核版本打印,然后卡住 内核初始化早期 earlyprintk参数,看最后一条打印
有驱动加载信息,然后panic 设备驱动初始化 查看panic栈回溯,定位哪个驱动
出现VFS: Cannot open root device 根文件系统挂载 检查内核参数root=和initramfs
出现Kernel panic - not syncing: Attempted to kill init! init进程启动 检查init程序是否存在、权限是否正确

我的小技巧:在编译内核时,打开CONFIG_DEBUG_KERNELCONFIG_EARLY_PRINTK。这样即使早期阶段出问题,也能看到一些输出。我曾经靠这个救回一块变砖的开发板。

好了,这一章就到这里。记住一句话:启动卡住不可怕,可怕的是你不知道卡在哪。把宏观链路刻在脑子里,排查起来就有方向了。


公众号:蓝海资料掘金营,微信deep3321