1. Linux 启动流程概览:从 BIOS/UEFI 到内核启动的完整链路

Linux 系统的启动是一个多阶段、高度模块化的过程。理解从硬件上电到内核接管系统的完整链路,是分析根文件系统挂载失败等启动故障的基础。本章将拆解这一链路中的每个关键环节,并说明各阶段的作用与交互关系。

1.1 启动阶段总览

整个启动流程可划分为以下 6 个主要阶段,每个阶段都有明确的输入、输出和故障边界:

阶段 执行主体 核心任务 典型故障点
1. 固件阶段 BIOS / UEFI 硬件自检、选择启动设备、加载引导程序 启动设备未识别、引导顺序错误
2. 引导加载阶段 GRUB / U-Boot 加载内核映像与 initramfs 到内存 内核映像损坏、配置文件错误
3. 内核解压与初始化 Linux 内核 解压内核、初始化架构子系统、建立页表 硬件不兼容、内核参数错误
4. 内核核心初始化 Linux 内核 初始化中断、调度器、内存管理、驱动模型 驱动缺失、资源冲突
5. initramfs 阶段 initramfs / initrd 加载必要驱动、挂载临时根文件系统 缺少块设备驱动、设备节点错误
6. 根文件系统切换 内核 + init 挂载真正的根文件系统,执行 /sbin/init 根设备无法挂载、fstab 错误
关键概念: 根文件系统挂载失败通常发生在第 5 阶段末尾或第 6 阶段。此时内核已成功启动,但无法找到或挂载最终的根设备,导致系统进入 panic 状态。

1.2 固件阶段:BIOS 与 UEFI 的差异

固件阶段负责最基本的硬件初始化和启动设备选择。BIOS(传统)和 UEFI(现代)在实现方式上有显著区别:

  • BIOS 传统模式: 读取 MBR(主引导记录,位于磁盘第 1 扇区),执行其中的 446 字节引导代码。MBR 分区表最多支持 4 个主分区,磁盘容量限制在 2TB 以下。
  • UEFI 模式: 读取 ESP(EFI 系统分区)中的 .efi 引导文件。UEFI 使用 GPT 分区表,支持更大的磁盘和更多的分区,且具备网络启动、安全启动等高级功能。

以下 SVG 图展示了两种固件模式下启动设备选择的流程对比:

BIOS 与 UEFI 启动流程对比 BIOS 与 UEFI 启动流程对比 BIOS 传统模式 读取 MBR (第1扇区) 执行引导代码 (446字节) 加载 GRUB stage1/stage2 UEFI 模式 读取 GPT 分区表 查找 ESP 分区 (FAT32) 执行 .efi 引导文件 UEFI 支持安全启动、网络启动和更大的磁盘,已成为主流嵌入式与服务器平台的标准

1.3 引导加载阶段:GRUB 与内核加载

引导加载程序(Bootloader)是固件与内核之间的桥梁。在 x86 平台上,GRUB 是最常用的引导加载程序;在嵌入式系统中,U-Boot 占据主导地位。GRUB 的核心工作流程如下:

  1. 读取配置文件: GRUB 从 /boot/grub/grub.cfg 中读取菜单项和内核参数。
  2. 加载内核映像: 将 vmlinuz-* 内核文件从磁盘读取到内存指定位置。
  3. 加载 initramfs: 将 initramfs-*.img 文件加载到内存,供内核后续使用。
  4. 设置内核参数: 将 root=、ro、console= 等参数传递给内核。
  5. 跳转到内核入口: 通过 boot 命令将控制权交给内核的 start_kernel 函数。
常见故障: 如果 GRUB 配置中 root= 参数指定的设备不存在(例如 root=/dev/sda1 但实际使用 NVMe 设备 /dev/nvme0n1p1),内核将无法找到根文件系统,导致挂载失败。

1.4 内核初始化:从汇编到 C 语言

当内核接收到控制权后,首先执行架构相关的汇编代码(arch/x86/boot/header.S),然后跳转到 start_kernel() 函数。以下是内核初始化的关键子阶段:

子阶段 函数/模块 完成标志
解压内核 decompress_kernel() 解压完成,跳转到解压后的内核
架构初始化 setup_arch() 页表建立、内存布局识别
中断初始化 trap_init() 中断向量表就绪
调度器初始化 sched_init() 进程调度器启动
内存管理初始化 mm_init() 伙伴系统和 slab 分配器就绪
驱动模型初始化 do_initcalls() 所有内置驱动初始化完成
根文件系统挂载 prepare_namespace() 尝试挂载根设备

以下 SVG 图展示了内核初始化阶段的函数调用链路:

Linux 内核初始化函数调用链路 Linux 内核初始化函数调用链路 start_kernel() setup_arch() trap_init() sched_init() mm_init() do_initcalls() prepare_namespace() 挂载根文件系统 prepare_namespace() 是根文件系统挂载的关键函数,它解析 root= 参数并调用 mount_root() 如果此阶段失败,内核将调用 panic() 并停止启动

1.5 initramfs 阶段:临时根文件系统

initramfs(Initial RAM File System)是一个在内存中运行的临时根文件系统,其核心目的是加载必要的内核模块和驱动程序,以便能够访问真正的根文件系统。initramfs 的工作流程如下:

  • 内核解压 initramfs: 内核将 initramfs 映像解压到内存中的 tmpfs 文件系统。
  • 执行 /init 脚本: 内核以 PID 1 执行 initramfs 中的 /init 脚本(通常是 shell 脚本或 busybox 程序)。
  • 加载驱动模块: 使用 modprobe 加载存储控制器、文件系统等驱动。
  • 创建设备节点: 使用 mknod 或 devtmpfs 创建根设备节点。
  • 挂载根文件系统: 尝试挂载 root= 参数指定的设备。
  • 切换根目录: 通过 switch_root 或 pivot_root 将根目录切换到真正的根文件系统。
常见陷阱: 如果 initramfs 中缺少必要的内核模块(例如 NVMe 驱动、ext4 文件系统驱动),即使内核参数正确,也无法挂载根文件系统。此时系统会报告 "VFS: Cannot open root device" 错误。

1.6 根文件系统切换:最后的接力棒

当 initramfs 成功挂载真正的根文件系统后,需要执行根目录切换操作。这一过程涉及两个关键系统调用:

系统调用 使用场景 行为
pivot_root() 传统 initramfs 将当前根移动到指定目录,并将新根设置为根目录
switch_root() 现代 initramfs (busybox) 删除 initramfs 中的所有文件,然后执行 exec /sbin/init

切换成功后,内核将执行真正的 init 进程(通常是 /sbin/init 或 /lib/systemd/systemd),系统启动进入用户空间阶段。

调试技巧: 在 GRUB 启动参数中添加 rd.break 或 break 可以让内核在 initramfs 阶段暂停,进入 shell 环境手动检查设备节点、驱动加载情况和根设备可用性。

1.7 完整启动链路 SVG 总览

以下 SVG 图将上述所有阶段整合为一条完整的启动链路,帮助建立全局视角:

Linux 完整启动链路总览 Linux 完整启动链路总览 BIOS / UEFI GRUB / U-Boot 内核解压 内核初始化 initramfs 根文件系统 阶段 1-2:硬件初始化与引导加载 → 阶段 3-4:内核启动与驱动初始化 → 阶段 5-6:根文件系统挂载与用户空间启动 阶段 1-2 关键输出 • 启动设备选择完成 • 内核映像加载到内存 • 内核参数传递给内核 阶段 5-6 关键输出 • 块设备驱动加载完成 • 根设备节点就绪 • 根文件系统成功挂载 根文件系统挂载失败常见原因(按阶段定位) 阶段 2:root= 参数错误或设备名不匹配 阶段 5:initramfs 缺少存储驱动或文件系统驱动 阶段 6:根设备损坏、fstab 配置错误或文件系统超级块损坏

1.8 本章小结