1. Linux内核启动流程概述:从BIOS/UEFI到根文件系统挂载的完整链路

Linux内核的启动过程是一个从硬件初始化到软件环境建立的复杂链路。理解这一完整流程,是正确配置根文件系统挂载参数的前提。本章将系统梳理从系统上电到根文件系统挂载的每一个关键阶段,帮助您建立全局视角。

1.1 启动流程总览

整个启动链路可划分为以下六个核心阶段,每个阶段都有明确的职责与输出产物:

阶段 主要执行者 核心任务 输出/状态
① 固件阶段 BIOS / UEFI 硬件自检、设备枚举、选择启动设备 启动设备中的引导加载程序被加载到内存
② 引导加载程序 GRUB2 / U-Boot / systemd-boot 加载内核映像与initramfs到内存,传递启动参数 内核映像与设备树(Device Tree)就绪
③ 内核解压与早期初始化 内核自解压代码 + head.S 解压内核、建立页表、初始化CPU与内存子系统 内核进入C语言环境,启动内核主线程
④ 内核核心初始化 start_kernel() 初始化调度器、内存管理、中断、定时器、驱动模型 内核子系统全部就绪,准备挂载根文件系统
⑤ 挂载根文件系统 prepare_namespace() / initramfs 解析root=参数,挂载真正的根文件系统 根文件系统挂载到 / 目录
⑥ 用户空间初始化 init 进程 (PID 1) 执行 /sbin/init 或指定的init程序 系统进入用户空间运行状态
Linux内核启动完整链路 ① 固件阶段 BIOS / UEFI ② 引导加载程序 GRUB2 / U-Boot ③ 内核解压 head.S / 自解压 ④ 核心初始化 start_kernel() ⑤ 挂载根文件系统 prepare_namespace() ⑥ 用户空间初始化 init (PID 1) 关键输出说明: • 阶段①→②:引导加载程序被加载到内存并执行 • 阶段④→⑤:内核完成所有子系统初始化后,根据 root= 参数挂载根文件系统 典型挂载参数示例: root=/dev/sda1 rootfstype=ext4 rootwait ro root=UUID=xxxx-xxxx rootflags=errors=remount-ro

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

固件阶段是启动链路的起点,负责最基本的硬件初始化和引导加载程序的加载。BIOS(传统)与UEFI(现代)在实现方式上存在显著差异:

特性 BIOS (Legacy) UEFI
引导方式 读取MBR(主引导记录,512字节) 读取EFI系统分区(ESP)中的.efi文件
磁盘分区 MBR分区表(最大2TB,最多4个主分区) GPT分区表(最大9.4ZB,128个分区)
引导加载程序位置 MBR + 后续扇区(通常446字节代码区) ESP分区中的文件(如 /EFI/BOOT/BOOTX64.EFI)
硬件初始化 实模式,中断向量表,传统INT 13h磁盘访问 保护模式/长模式,提供Boot Services与Runtime Services
安全启动 不支持 支持Secure Boot,验证引导加载程序签名
嵌入式系统提示: 在嵌入式平台(如ARM/RISC-V)中,通常没有BIOS/UEFI,而是使用BootROM + SPL(Secondary Program Loader)的启动链。U-Boot作为最常用的引导加载程序,其行为更接近UEFI的引导方式。

1.3 引导加载程序:内核与参数的传递者

引导加载程序的核心职责是加载内核映像(vmlinuz)和可选的initramfs到内存,并传递内核命令行参数。这些参数直接影响根文件系统的挂载行为。

以GRUB2为例,其配置文件通常位于 /boot/grub/grub.cfg,典型的内核启动命令行如下:

linux   /vmlinuz-5.15.0-generic root=/dev/sda1 ro quiet splash
initrd  /initrd.img-5.15.0-generic

其中 root=/dev/sda1 就是指定根文件系统设备的关键参数。引导加载程序将其放入内核的 boot_params 结构体中,内核在后续阶段解析使用。

1.4 内核解压与早期初始化

内核映像通常以压缩形式存储(gzip/xz/zstd)。引导加载程序加载后,内核入口点(arch/x86/boot/header.S 或 ARM 的 stext)首先执行:

  • 自解压: 解压内核到内存中的正确位置(通常为物理地址的高端区域)。
  • 架构初始化: 建立临时页表、设置栈指针、检测CPU特性。
  • 进入C环境: 跳转到 start_kernel() 函数,这是内核初始化流程的C语言入口。
关键点: 在早期初始化阶段,内核尚未具备完整的文件系统驱动能力。此时如果根文件系统位于需要驱动支持的设备(如NVMe、USB、网络文件系统),则必须依赖initramfs(初始内存文件系统)来加载必要的驱动模块。

1.5 内核核心初始化:start_kernel() 的旅程

start_kernel() 是内核初始化的中枢函数,它按顺序调用各个子系统的初始化函数。以下是与根文件系统挂载直接相关的关键步骤:

调用顺序 函数/子系统 作用
1 setup_arch() 解析启动参数,建立内存布局,初始化页表
2 trap_init() 初始化中断向量表
3 mm_init() 初始化内存管理子系统(伙伴系统、slab分配器)
4 sched_init() 初始化调度器,创建idle进程
5 vfs_caches_init() 初始化VFS(虚拟文件系统)缓存,包括dentry和inode缓存
6 populate_rootfs() 解压并挂载initramfs(如果存在)
7 do_basic_setup() 初始化驱动模型、PCI总线、块设备子系统等
8 prepare_namespace() 解析root=参数,挂载真正的根文件系统
start_kernel() 关键初始化流程 setup_arch() trap_init() mm_init() sched_init() vfs_caches_init() populate_rootfs() 挂载initramfs do_basic_setup() 驱动初始化 prepare_namespace() 挂载根文件系统 根文件系统挂载决策点: • 如果内核编译时内嵌了initramfs(CONFIG_INITRAMFS_SOURCE),则populate_rootfs()直接使用内嵌cpio归档 • 如果引导加载程序传递了initrd,则内核从内存中加载并挂载initramfs • prepare_namespace() 最终根据 root= 参数执行真正的根文件系统挂载

1.6 根文件系统挂载:prepare_namespace() 的决策逻辑

prepare_namespace() 是根文件系统挂载的核心函数,它根据内核命令行参数执行不同的挂载策略:

  • 直接设备挂载: root=/dev/sda1 或 root=PARTUUID=xxx,内核直接尝试挂载指定块设备。
  • UUID/LABEL挂载: root=UUID=xxx 或 root=LABEL=xxx,内核扫描所有块设备匹配UUID或卷标。
  • 网络文件系统: root=/dev/nfs nfsroot=server:path,内核通过NFS协议挂载远程根文件系统。
  • initramfs委托: 如果内核使用initramfs,则initramfs中的init进程负责挂载真正的根文件系统(通过pivot_root或switch_root)。
常见错误: 如果 root= 参数指定的设备不存在或文件系统类型不匹配,内核将触发 Kernel Panic,并输出 "VFS: Unable to mount root fs on unknown-block(x,y)" 错误信息。此时系统无法启动,必须检查启动参数是否正确。

1.7 用户空间初始化:init进程的启动

根文件系统挂载成功后,内核在根目录下查找并执行init程序(PID 1)。查找顺序如下:

  1. /sbin/init(最常用,通常指向systemd或sysvinit)
  2. /etc/init
  3. /bin/init
  4. /bin/sh(如果以上都不存在,作为应急shell)

如果内核命令行指定了 init= 参数(如 init=/bin/bash),则直接使用该参数指定的程序。

调试技巧: 在开发或调试阶段,可以在内核命令行添加 init=/bin/bash 直接进入shell,方便检查根文件系统挂载状态和修复问题。但请注意,这种方式不会启动任何系统服务,仅适用于临时调试。

1.8 本章小结

从BIOS/UEFI到根文件系统挂载的完整链路中,每个阶段都有明确的职责和输出。理解这一流程,有助于在遇到启动失败时快速定位问题阶段:

  • 如果系统在引导加载程序之前失败 → 检查固件设置和启动设备顺序。
  • 如果内核解压后立即崩溃 → 检查内核映像是否损坏或架构不匹配。
  • 如果内核在挂载根文件系统时Panic → 检查 root= 参数、文件系统类型和设备驱动。
  • 如果init进程无法启动 → 检查根文件系统内容是否完整,init程序是否存在。