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 处),然后跳转到解压入口。解压过程分为以下步骤:
- 实模式入口:CPU 处于实模式,执行
arch/x86/boot/header.S中的start_of_setup,设置堆栈、读取启动参数。 - 切换到保护模式:通过
go_to_protected_mode()开启 A20 地址线、加载 GDT,进入 32 位保护模式。 - 解压内核:执行
decompress_kernel(),将压缩的内核数据解压到0x100000或更高地址(CONFIG_RELOCATABLE时地址可浮动)。 - 跳转到解压后的内核:解压完成后,调用
__startup_64()(64 位)或startup_32(),进入内核主入口。
调试提示:如果内核在解压阶段崩溃,通常表现为“早期控制台无输出”或“解压失败”错误。可以通过
earlyprintk 内核参数在解压阶段输出调试信息。
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() 阶段,具体流程如下:
- 内核解包 initramfs:
populate_rootfs()函数将 Bootloader 传递的 initramfs cpio 归档解压到rootfs(一个基于 tmpfs 的虚拟根文件系统)。 - 挂载为根文件系统:内核将
rootfs设置为当前进程的根目录。 - 执行 /init:内核在 initramfs 中查找并执行
/init脚本或程序。这个脚本负责加载必要的内核模块(如存储控制器驱动、文件系统驱动)、检测硬件、解密分区、组装 RAID/LVM 等。 - 切换到真实根文件系统:
/init脚本最终通过switch_root或pivot_root系统调用,将根文件系统切换到真正的存储设备(如/dev/sda2),然后执行/sbin/init(PID 1)。
关键文件:initramfs 中的
/init 脚本是启动修复的核心。如果该脚本出错(如找不到根设备、模块加载失败),系统将无法挂载真实根文件系统,进入紧急 shell 或 kernel panic。
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 方案 |
故障排查:如果内核启动时出现
-
- initramfs 中缺少必要的存储控制器驱动(如 ahci、nvme、virtio)。
- 根文件系统损坏或文件系统类型不被内核支持。
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 内容以及存储驱动支持。