2. 内核解压与初始化:vmlinuz、initrd/initramfs 的作用与加载过程

当 Bootloader(如 GRUB、U-Boot)将内核映像加载到内存并跳转到入口点后,Linux 内核启动进入解压与初始化阶段。这一阶段的核心任务是:将压缩的内核解压到正确位置、建立基本的 CPU 和内存环境、挂载一个临时的根文件系统(initramfs/initrd),最终为切换到真正的根文件系统做好准备。

2.1 vmlinuz:压缩的内核映像

vmlinuz 是 Linux 内核的压缩版本,通常位于 /boot 目录下。它由 vmlinux(未压缩的内核 ELF 文件)经过 gzip、bzip2 或 xz 压缩,并在头部附加了一段自解压代码(decompressor stub)。

文件 说明 典型位置
vmlinux 未压缩的原始内核 ELF 映像,包含完整的符号表,主要用于调试 /boot/vmlinux-$(uname -r) 或源码目录
vmlinuz 压缩后的内核映像,头部包含自解压代码,是实际启动加载的目标 /boot/vmlinuz-$(uname -r)
zImage / bzImage 旧式(zImage)和大内核(bzImage,Big zImage)格式,vmlinuz 通常就是 bzImage 编译输出 arch/x86/boot/bzImage
关键点:vmlinuz 的自解压代码由 arch/x86/boot/compressed/ 下的 head_32.S 或 head_64.S 实现。解压完成后,控制权移交给 start_kernel()。

2.2 内核解压流程

Bootloader 将 vmlinuz 加载到物理内存的某个位置(例如 0x100000,即 1MB 处),然后跳转到解压入口。解压过程分为以下步骤:

  1. 实模式入口:CPU 处于实模式,执行 arch/x86/boot/header.S 中的 start_of_setup,设置堆栈、读取启动参数。
  2. 切换到保护模式:通过 go_to_protected_mode() 开启 A20 地址线、加载 GDT,进入 32 位保护模式。
  3. 解压内核:执行 decompress_kernel(),将压缩的内核数据解压到 0x100000 或更高地址(CONFIG_RELOCATABLE 时地址可浮动)。
  4. 跳转到解压后的内核:解压完成后,调用 __startup_64()(64 位)或 startup_32(),进入内核主入口。
调试提示:如果内核在解压阶段崩溃,通常表现为“早期控制台无输出”或“解压失败”错误。可以通过 earlyprintk 内核参数在解压阶段输出调试信息。
内核解压与初始化流程 Bootloader vmlinuz 加载 实模式入口 保护模式切换 解压内核 start_kernel() initramfs 挂载 说明 解压阶段由 vmlinuz 头部的自解压代码完成,不依赖任何文件系统。 解压成功后,内核开始执行 C 语言入口 start_kernel(),进行体系结构初始化。

2.3 initrd 与 initramfs:临时根文件系统

内核初始化完成后,需要挂载真正的根文件系统(如 ext4、XFS 等)。但根文件系统可能位于需要驱动支持的设备上(如 NVMe SSD、USB 磁盘、LVM 卷、加密分区)。为了解决“先有鸡还是先有蛋”的问题,内核使用临时根文件系统:

特性 initrd (Initial RAM Disk) initramfs (Initial RAM File System)
本质 块设备上的文件系统映像(如 ext2),需要驱动支持 基于 tmpfs 的 cpio 归档,直接由内核内存管理
加载方式 Bootloader 加载到内存,内核模拟为 RAM 磁盘(/dev/ram0) Bootloader 加载到内存,内核直接解包为 tmpfs 根目录
大小限制 受 RAM 磁盘大小限制,通常较小 仅受内存限制,可更大
现代 Linux 默认 已逐步淘汰 主流发行版默认使用(如 dracut、initramfs-tools 生成)
常见误区:很多人认为 initramfs 是“initrd 的升级版”。实际上,initramfs 从 Linux 2.6 开始引入,设计更简洁——它不需要额外的块设备驱动,直接作为 tmpfs 挂载,减少了启动阶段的依赖。

2.4 initramfs 的加载与执行过程

initramfs 的加载发生在 start_kernel() 之后的 rest_init() 阶段,具体流程如下:

  1. 内核解包 initramfs:populate_rootfs() 函数将 Bootloader 传递的 initramfs cpio 归档解压到 rootfs(一个基于 tmpfs 的虚拟根文件系统)。
  2. 挂载为根文件系统:内核将 rootfs 设置为当前进程的根目录。
  3. 执行 /init:内核在 initramfs 中查找并执行 /init 脚本或程序。这个脚本负责加载必要的内核模块(如存储控制器驱动、文件系统驱动)、检测硬件、解密分区、组装 RAID/LVM 等。
  4. 切换到真实根文件系统:/init 脚本最终通过 switch_root 或 pivot_root 系统调用,将根文件系统切换到真正的存储设备(如 /dev/sda2),然后执行 /sbin/init(PID 1)。
关键文件:initramfs 中的 /init 脚本是启动修复的核心。如果该脚本出错(如找不到根设备、模块加载失败),系统将无法挂载真实根文件系统,进入紧急 shell 或 kernel panic。
initramfs 加载与根切换流程 start_kernel() populate_rootfs() 挂载 rootfs (tmpfs) 执行 /init 加载驱动/模块 检测根设备 switch_root() 挂载真实根 执行 /sbin/init 说明 switch_root() 会清理 initramfs 占用的内存,将根挂载点切换到真实设备,然后执行目标根上的 init 程序。

2.5 内核启动参数对根文件系统的影响

内核通过 root= 参数指定真实根文件系统的位置。常见的参数形式如下:

参数示例 说明 适用场景
root=/dev/sda2 直接指定块设备节点 传统 SATA/SCSI 磁盘
root=UUID=xxxx-xxxx 通过 UUID 标识设备,避免设备名漂移 现代发行版默认推荐
root=LABEL=MyRoot 通过文件系统标签指定 多系统环境
root=/dev/nfs 网络根文件系统(NFS) 无盘工作站、嵌入式系统
root=/dev/ram0 使用 initrd 作为根 旧式 initrd 方案
故障排查:如果内核启动时出现 VFS: Cannot open root device 或 Kernel panic - not syncing: VFS: Unable to mount root fs,通常原因包括:
- root= 参数指定的设备不存在或驱动未加载。
- initramfs 中缺少必要的存储控制器驱动(如 ahci、nvme、virtio)。
- 根文件系统损坏或文件系统类型不被内核支持。

2.6 本章小结

  • vmlinuz 是压缩内核,包含自解压代码,解压后执行 start_kernel()。
  • initramfs 是基于 tmpfs 的临时根文件系统,用于加载驱动和准备真实根设备。
  • initrd 是旧式 RAM 磁盘方案,已被 initramfs 取代。
  • 内核通过 root= 参数定位真实根文件系统,initramfs 中的 /init 脚本负责最终的根切换。
  • 根文件系统挂载失败时,应优先检查 root= 参数、initramfs 内容以及存储驱动支持。