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

理解Linux系统的启动流程是内核调试与根文件系统挂载分析的基础。本章将沿着从硬件上电到用户空间首个进程启动的完整链路,逐一剖析每个阶段的职责、数据流与关键交接点。掌握这一全景图,后续分析initramfs机制与根文件系统挂载时才能精准定位问题所在。

2.1 启动流程总览:六个核心阶段

Linux启动可划分为六个逻辑阶段,每个阶段由不同的组件主导,并完成特定的初始化任务。下表总结了各阶段的起止点与核心产出:

阶段 起始事件 结束事件 核心组件 关键产出
1. 固件阶段 CPU复位 Bootloader加载 BIOS/UEFI 硬件自检、启动设备选择
2. Bootloader阶段 MBR/GPT引导 内核解压入口 GRUB2 / U-Boot 内核映像与initramfs加载到内存
3. 内核早期初始化 start_kernel() init进程启动 内核自身 架构初始化、内存管理、调度器
4. initramfs阶段 rootfs挂载 根文件系统切换 initramfs中的init 驱动加载、块设备就绪
5. 根文件系统挂载 pivot_root() 根文件系统就绪 内核VFS层 真正的根文件系统挂载
6. init进程启动 execve(/sbin/init) 用户空间初始化 systemd / SysVinit 系统服务启动
关键认知: 启动流程的本质是“控制权移交”——从固件到Bootloader,从Bootloader到内核,从内核到initramfs,最终到根文件系统上的init进程。每一次移交都伴随着执行环境的质变。

2.2 固件阶段:BIOS与UEFI的引导决策

CPU上电复位后,首先执行的是固件代码。传统BIOS与现代UEFI在引导方式上存在显著差异,直接影响内核加载路径。

2.2.1 BIOS引导流程

  • POST(上电自检): 检测CPU、内存、基本I/O设备。
  • MBR读取: 读取启动磁盘的第一个扇区(512字节),检查最后两个字节是否为0x55AA。
  • Bootloader加载: 将MBR中的第一阶段引导代码(446字节)加载到内存0x7C00处并执行。

2.2.2 UEFI引导流程

  • 安全启动(Secure Boot): 验证Bootloader签名的合法性。
  • EFI系统分区(ESP): 读取FAT32格式的ESP分区中的.efi文件。
  • Boot Manager: 根据NVRAM中的启动项顺序加载Bootloader(如grubx64.efi)。
嵌入式场景提示: 在ARM/嵌入式平台中,固件阶段通常由U-Boot SPL(Secondary Program Loader)完成,其作用等价于BIOS的MBR读取阶段,但支持从NAND Flash、SD卡、网络等多种介质启动。
固件阶段引导流程对比:BIOS vs UEFI CPU复位 → POST 读取MBR (0x7C00) Bootloader Stage1 CPU复位 → 安全启动 读取ESP分区 加载grubx64.efi BIOS使用实模式读取MBR(最大512字节),UEFI使用保护模式读取FAT32分区中的EFI应用 UEFI支持安全启动验证,BIOS无签名校验机制

2.3 Bootloader阶段:内核与initramfs的加载

Bootloader的核心职责是将内核映像(vmlinuz)与initramfs从存储介质加载到内存,并设置好启动参数后跳转到内核入口。以GRUB2为例,其工作流程如下:

  1. Stage1.5/Stage2加载: 从文件系统(ext4、Btrfs等)读取GRUB模块。
  2. 配置文件解析: 读取/boot/grub/grub.cfg,显示启动菜单。
  3. 内核加载: 将vmlinuz解压到内存地址0x100000(1MB处)。
  4. initramfs加载: 将initramfs映像加载到内存,并将物理地址通过boot_params传递给内核。
  5. 设置启动参数: 填充setup_header中的cmd_line_ptr,指向内核命令行。
  6. 跳转到保护模式: 执行ljmp指令进入32位保护模式,跳转到startup_32。
常见陷阱: 如果initramfs映像过大(超过内存可用连续区域),Bootloader可能加载失败。GRUB2在加载大型initramfs时可能触发“Out of memory”错误,此时需要调整GRUB_CMDLINE_LINUX中的crashkernel参数或减小initramfs体积。

2.4 内核早期初始化:从start_kernel到rootfs

内核入口startup_32(或startup_64)完成架构相关的初始化后,调用start_kernel()——这是内核启动的C语言入口点。以下是最关键的初始化序列:

调用顺序 函数 初始化内容 对后续的影响
1 setup_arch() 解析启动参数、初始化页表、设置内存布局 确定物理内存映射与DMA区域
2 trap_init() 设置中断描述符表(IDT) 使能异常与中断处理
3 mm_init() 初始化内存管理器(伙伴系统、slab分配器) 动态内存分配可用
4 sched_init() 初始化调度器、创建init_task 多任务调度基础就绪
5 vfs_caches_init() 初始化VFS(虚拟文件系统)缓存 文件系统操作可用
6 populate_rootfs() 解压并挂载initramfs到rootfs 临时根文件系统就绪
关键转折点: populate_rootfs() 是启动流程中第一次与文件系统相关的操作。内核将嵌入的initramfs(或Bootloader传递的initramfs)解压到内存中的tmpfs,并挂载为rootfs。此时,内核拥有了一个可读写的临时根文件系统。
内核早期初始化关键路径(start_kernel → rootfs) startup_32/64 start_kernel() setup_arch() mm_init() sched_init() populate_rootfs() 解压initramfs到rootfs 临时根文件系统就绪

2.5 initramfs阶段:临时根文件系统的作用

在populate_rootfs()完成后,内核调用kernel_init(),该函数会尝试执行initramfs中的/init程序。initramfs是一个基于tmpfs的内存文件系统,其核心使命是:

  • 加载必要的内核模块: 特别是存储控制器驱动(如NVMe、AHCI、virtio-blk)。
  • 发现根设备: 扫描块设备,识别根文件系统所在的分区。
  • 执行根文件系统切换: 通过pivot_root()或switch_root()将根文件系统从initramfs切换到真正的存储设备。
调试技巧: 在initramfs的/init脚本中添加echo或ls命令,可以输出当前可用的块设备列表。例如:ls -l /dev/sd* /dev/nvme* /dev/vd*,用于排查根设备未被发现的问题。

2.6 根文件系统挂载:pivot_root与switch_root

initramfs中的init进程完成驱动加载和设备发现后,需要将根文件系统切换到真正的存储设备上。这一过程涉及两个关键系统调用:

系统调用 函数原型 行为 使用场景
pivot_root() int pivot_root(const char *new_root, const char *put_old) 将当前根文件系统移动到put_old目录,并将new_root设置为新根 内核2.6时代的标准做法
switch_root() int switch_root(const char *newroot, const char *init, ...) 删除initramfs中的所有文件,执行chroot到新根,并执行新的init 现代busybox initramfs的标准做法
关键区别: pivot_root() 保留initramfs在put_old目录中,需要手动卸载;switch_root() 直接销毁initramfs,更干净且节省内存。现代initramfs(如dracut生成的)默认使用switch_root。

2.7 init进程启动:用户空间的起点

根文件系统挂载成功后,内核执行run_init_process(),按以下顺序查找并执行init程序:

  1. /sbin/init
  2. /etc/init
  3. /bin/init
  4. /bin/sh(作为fallback)

一旦init进程启动(PID=1),内核的启动职责结束,控制权完全移交给用户空间。init进程负责:

  • 读取/etc/inittab(SysVinit)或/etc/systemd/system/default.target(systemd)。
  • 启动系统服务、挂载/etc/fstab中的文件系统、配置网络。
  • 启动getty提供登录提示符。
全景回顾: 从BIOS/UEFI到init进程,整个链路经历了“硬件初始化 → Bootloader加载 → 内核早期初始化 → initramfs过渡 → 根文件系统挂载 → 用户空间init”六个阶段。每个阶段都是下一个阶段的前提,任何一个环节的失败都会导致系统无法正常启动。后续章节将深入initramfs的内部构造与根文件系统挂载的详细机制。