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程序 | 系统进入用户空间运行状态 |
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,验证引导加载程序签名 |
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语言入口。
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=参数,挂载真正的根文件系统 |
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)。查找顺序如下:
/sbin/init(最常用,通常指向systemd或sysvinit)/etc/init/bin/init/bin/sh(如果以上都不存在,作为应急shell)
如果内核命令行指定了 init= 参数(如 init=/bin/bash),则直接使用该参数指定的程序。
init=/bin/bash 直接进入shell,方便检查根文件系统挂载状态和修复问题。但请注意,这种方式不会启动任何系统服务,仅适用于临时调试。
1.8 本章小结
从BIOS/UEFI到根文件系统挂载的完整链路中,每个阶段都有明确的职责和输出。理解这一流程,有助于在遇到启动失败时快速定位问题阶段:
- 如果系统在引导加载程序之前失败 → 检查固件设置和启动设备顺序。
- 如果内核解压后立即崩溃 → 检查内核映像是否损坏或架构不匹配。
- 如果内核在挂载根文件系统时Panic → 检查
root=参数、文件系统类型和设备驱动。 - 如果init进程无法启动 → 检查根文件系统内容是否完整,init程序是否存在。