initramfs(Initial RAMdisk File System)是现代 Linux 系统启动过程中的关键组件,它充当了内核与真实根文件系统之间的一座桥梁。当内核完成初始化后,在挂载真实根文件系统之前,需要一个临时的文件系统来加载必要的驱动程序、运行初始化脚本,以及处理各种硬件配置,这个临时文件系统就是 initramfs。
不同于早期的 initrd(Initial RAM Disk),initramfs 是一个完整的 cpio 压缩归档文件,被嵌入到内核镜像中或作为单独的文件加载。它包含了启动所需的最小根文件系统,通常包括 init 进程、必要的二进制工具、驱动程序和配置文件。initramfs 的存在让 Linux 系统能够支持复杂的启动场景:加载磁盘驱动、配置网络、解密根分区、等待设备就绪等。
本文将从数据结构、启动流程、实现机制等多个维度深入剖析 initramfs 的工作原理,帮助你理解现代 Linux 启动过程中这个看似神秘但又至关重要的组件。
核心概念详解
initramfs 与 initrd 的区别
在讨论 initramfs 之前,必须搞清楚它与前辈 initrd 的区别。这两个名词经常被混用,但它们代表着不同时代的启动方案。
initrd(Initial RAM Disk)是一个虚拟块设备,内核将其视为一个真实的磁盘。它包含了一个完整的文件系统镜像(通常是 ext2 格式),内核需要通过传统的块设备驱动来访问它。这意味着即使启动 initrd,内核也需要加载磁盘驱动程序,这形成了一个“鸡生蛋,蛋生鸡”的问题。initrd 的大小也受到限制,因为它必须完整地加载到内存中。
initramfs 是一个 cpio 格式的压缩归档文件,内核对其有特殊的处理方式。在内核启动时,initramfs 被直接加载到内存中,内核然后将其解压到一个虚拟文件系统(VFS, Virtual File System)上。这个过程不需要任何块设备驱动,解决了 initrd 的自引用问题。initramfs 可以被编译到内核镜像中,也可以作为单独的文件由引导加载程序(bootloader)加载。
简单来说,initrd 是“假块设备”,initramfs 是“直接的文件系统”。
initramfs 的工作阶段
initramfs 的生命周期分为三个关键阶段:
构建阶段: 通过工具(如 dracut、mkinitramfs)收集必要的驱动、工具和配置文件,生成一个 cpio 压缩包。
加载阶段: 引导加载程序(如 GRUB)从磁盘读取 initramfs 文件并加载到内存中,或 initramfs 已经被编译到内核镜像中。内核识别并解压 initramfs 到 rootfs(根文件系统)中。
执行阶段: 内核执行 initramfs 中的 init 脚本或二进制程序,这个程序负责挂载真实根文件系统、运行必要的初始化脚本,然后“切换根”(chroot)到真实的根文件系统。
VFS 与 rootfs 的关系
要理解 initramfs,必须理解 Linux 虚拟文件系统(VFS)和 rootfs 的概念。VFS 是 Linux 内核中的一个抽象层,它为所有文件系统提供了统一的接口,使得不同的文件系统(ext4、btrfs、NFS 等)可以以相同的方式被访问。
rootfs 是一个基于内存的文件系统实现,它是所有 Linux 系统的根基。当内核启动时,它创建一个 rootfs 实例,然后将 initramfs 的内容解压到这个 rootfs 中。因此,initramfs 本质上是对 rootfs 的初始化填充。理解这些 计算机科学基础 概念,能帮助我们更好地把握操作系统启动的脉络。
实现机制深度剖析
内核中的 initramfs 处理流程
让我们从内核源码的角度来看 initramfs 是如何被处理的。Linux 内核在启动过程中有一个特殊的阶段来处理 initramfs,这个过程主要发生在 init/main.c 的 start_kernel() 和后续的启动函数中。
/* kernel/init/main.c - 简化版示意 */
static int __init do_populate_rootfs(void)
{
/* 初始化 rootfs, 解压 initramfs 内容 */
unpack_to_rootfs(__initramfs_start, __initramfs_size);
/* 执行 init 进程 */
if (execute_command) {
/* 如果提供了自定义 init 程序 */
ret = run_init_process(execute_command);
} else {
/* 尝试多个标准 init 路径 */
ret = run_init_process("/sbin/init");
if (ret) ret = run_init_process("/etc/init");
if (ret) ret = run_init_process("/bin/init");
if (ret) ret = run_init_process("/bin/sh");
}
return ret;
}
这段代码展示了几个关键步骤:首先, unpack_to_rootfs() 函数将内核中编译进来的 initramfs(通过符号 __initramfs_start 和 __initramfs_size 标记)解压到 rootfs 中。然后,内核尝试执行一个 init 程序,这个程序将接管系统的初始化。
initramfs 文件格式:cpio 与压缩
initramfs 使用 cpio 格式进行打包。cpio 是一个古老但仍然广泛使用的归档格式,它的优点是简单、易于解析,对于内核这样的低级代码来说特别适合。让我们看看 cpio 的基本结构:
+--------+--+--------+--+--------+--+
| Header | Padding | Data | Padding |
+--------+--+--------+--+--------+--+
现代 Linux 通常使用 newc 格式(也叫 SVR4 cpio 格式),这是一个 ASCII 十六进制格式,便于调试和验证。一个典型的 cpio header 如下:
/* cpio header 结构 - newc 格式 */
struct cpio_newc_header {
char c_magic[6]; /* "070701" for newc format */
char c_ino[8]; /* inode number */
char c_mode[8]; /* file mode */
char c_uid[8]; /* user id */
char c_gid[8]; /* group id */
char c_nlink[8]; /* number of hard links */
char c_mtime[8]; /* modification time */
char c_filesize[8]; /* file size */
char c_devmajor[8]; /* device major number */
char c_devminor[8]; /* device minor number */
char c_rdevmajor[8]; /* rdev major number */
char c_rdevminor[8]; /* rdev minor number */
char c_namesize[8]; /* length of filename */
char c_check[8]; /* checksum */
};
每个文件在 cpio 归档中都有这样的 header,后跟文件名、padding、文件数据和更多 padding。整个归档以一个名为 "TRAILER!!!" 的特殊条目结尾,标志着归档的完成。
# 使用 cpio 命令创建 initramfs
find . -print0 | cpio -0 -o -H newc -F ../initramfs.cpio
# 或者使用 cpio 直接从文件列表创建
# 然后通过 gzip 压缩
gzip initramfs.cpio
initramfs 的加载流程
当 GRUB 或其他引导加载程序加载 Linux 内核时,如果指定了 initramfs 文件,引导加载程序会将其加载到内存中,并通过 multiboot 协议(或其他引导协议)将其位置和大小信息传递给内核。内核在启动时会识别这个 initramfs 并进行解压。
[引导加载程序]
|
v
[加载内核镜像到内存]
|
v
[加载 initramfs 到内存]
|
v
[内核启动并识别 initramfs]
|
v
[解压 initramfs 到 rootfs]
|
v
[执行 initramfs 中的 init]
|
v
[init 挂载真实根文件系统]
|
v
[执行 switch_root 切换到真实根]
内核中处理 initramfs 的核心函数在 init/initramfs.c 中:
/* init/initramfs.c - initramfs 解压核心逻辑 */
static int __init unpack_to_rootfs(char *buf, unsigned long len)
{
long written;
dry_run = 1;
written = unpack_to_rootfs(buf, len); /* 第一遍: 检查完整性 */
if (written != len) {
printk(KERN_WARNING "initramfs unpacking failed");
return -1;
}
dry_run = 0;
written = unpack_to_rootfs(buf, len); /* 第二遍: 真正解压 */
if (written != len) {
panic("initramfs unpacking failed");
}
return 0;
}
static int __init unpack_to_rootfs(char *buf, unsigned long len)
{
long written = 0;
while (len) {
/* 处理 cpio header */
struct cpio_newc_header *hdr = (void *)buf;
/* 验证魔数 */
if (memcmp(hdr->c_magic, "070701", 6) != 0) {
break; /* 可能是压缩格式,或者归档结束 */
}
/* 解析 header,提取文件名大小、文件大小等 */
unsigned long namesize = hex2bin(hdr->c_namesize);
unsigned long filesize = hex2bin(hdr->c_filesize);
/* 如果是 TRAILER,表示 cpio 归档结束 */
if (!strcmp(filename, "TRAILER!!!")) {
break;
}
/* 创建文件或目录 */
if (S_ISDIR(mode)) {
sys_mkdir(filename, mode);
} else if (S_ISLNK(mode)) {
sys_symlink(symlink_target, filename);
} else {
/* 创建普通文件 */
sys_open(filename, O_CREAT | O_WRONLY, mode);
sys_write(fd, file_data, filesize);
sys_close(fd);
}
/* 移动到下一个 cpio 条目 */
buf += header_size + name_size + file_size;
len -= header_size + name_size + file_size;
}
return written;
}
initramfs 内部结构示意
一个典型的 initramfs 包含以下目录结构:
initramfs/
├── bin/ # 必要的二进制工具
│ ├── busybox # 提供 sh, ls, cat, mount 等基础命令
│ └── init # 初始化脚本或二进制
├── sbin/
│ ├── insmod # 加载内核模块
│ ├── modprobe # 高级模块加载
│ └── ...其他工具
├── etc/ # 配置文件
│ ├── modprobe.d/ # 模块配置
│ └── fstab # 文件系统配置
├── lib/ # 库文件和内核模块
│ ├── modules/ # 编译的内核模块
│ └── ld-linux.so # C 库
├── dev/ # 设备文件(由内核自动创建)
├── proc/ # procfs 挂载点
├── sys/ # sysfs 挂载点
├── root/ # 临时 root 用户目录
└── newroot/ # 用于 switch_root 的挂载点
这个结构虽然看起来复杂,但本质上是一个最小的 Linux 根文件系统。initramfs 的大小通常在几 MB 到几十 MB 之间,取决于需要包含的驱动和工具的数量。
switch_root:从 initramfs 切换到真实根
当 initramfs 中的 init 程序完成了必要的初始化任务(加载驱动、挂载根文件系统等)后,它需要“切换根”到真实的根文件系统。这是通过 switch_root 命令或系统调用来完成的。
/* 核心思路: switch_root 的实现 */
int switch_root(const char *new_root)
{
/* 1. 改变根目录 */
if (chroot(new_root) < 0) {
perror("chroot failed");
return -1;
}
/* 2. 改变当前工作目录到新根的根目录 */
if (chdir("/") < 0) {
perror("chdir failed");
return -1;
}
/* 3. 执行新根中的 init 程序
* 使用 execve 替换当前进程的镜像,这样就不需要启动新进程
*/
if (execve("/sbin/init", argv, envp) < 0) {
if (execve("/etc/init", argv, envp) < 0) {
if (execve("/bin/init", argv, envp) < 0) {
execve("/bin/sh", argv, envp);
}
}
}
/* 如果 execve 失败,才会执行到这里 */
return -1;
}
从流程图来看,整个过程非常清晰:

设计思想与架构
为什么需要 initramfs
在 initramfs 出现之前,系统启动有一个根本性的问题:内核需要访问根文件系统来加载驱动程序,但要访问根文件系统首先需要驱动程序,这形成了一个死循环。早期的解决方案是将根文件系统硬编码到内核中,这样的缺点是内核体积庞大,且灵活性很差。
initramfs 优雅地解决了这个问题。它包含了启动所需的最小驱动集合和初始化工具,使得内核可以:
- 动态加载驱动:initramfs 中的工具(如 insmod、modprobe)可以加载额外的驱动程序,不需要将所有驱动都编译进内核。
- 支持复杂启动场景:LUKS 加密根分区、LVM 逻辑卷、网络启动(PXE)、USB 启动等复杂场景都可以在 initramfs 中处理。
- 灵活性和可维护性:不同的硬件配置可以有不同的 initramfs,而内核保持不变。
- 最小化内核体积:不需要的驱动和工具可以从 initramfs 中省略。
initramfs 与 initrd 的权衡
虽然 initramfs 基本上取代了 initrd,但理解它们之间的设计权衡很有意义。
| 方面 |
initrd |
initramfs |
| 存储格式 |
块设备镜像(ext2/ext3) |
cpio 压缩归档 |
| 大小灵活性 |
固定大小 |
更灵活,支持更大的内容 |
| 访问方式 |
块设备驱动 |
VFS 直接访问 |
| 自引用问题 |
存在(需要驱动才能访问) |
不存在 |
| 加载速度 |
较慢(需要驱动初始化) |
较快(直接解压) |
| 兼容性 |
某些旧系统 |
现代 Linux 标准 |
生成工具的对比
现代 Linux 发行版使用不同的工具来生成 initramfs,每种工具有其特点:
| 工具 |
特点 |
使用场景 |
| dracut |
功能完整,高度可配置,模块化 |
Fedora, RHEL, CentOS 等 |
| mkinitramfs |
脚本式,简洁,易于理解 |
Debian, Ubuntu |
| mkinitcpio |
轻量级,专为 Arch Linux 设计 |
Arch Linux |
| genkernel |
与 Gentoo 包管理集成 |
Gentoo Linux |
每种工具本质上做的事情相同:收集必要的驱动、二进制文件、库和配置,打包成 cpio 格式。但它们的实现策略、模块化程度和配置方式各不相同。
局限性与可能的改进方向
initramfs 的当前实现也有一些局限:
- 大小成本:即使是一个“最小”的 initramfs 也通常有几 MB 大小,这对于某些嵌入式或资源受限的设备来说可能很大。
- 复杂性:initramfs 越复杂,出现问题的可能性就越大。某些引导问题很难调试,因为你无法像调试用户空间程序那样容易地进入 initramfs 环境。
- 更新维护:initramfs 需要随着内核模块的更新而更新。如果驱动版本与内核版本不兼容,可能导致启动失败。
- 动态设备发现的局限:在某些硬件复杂的场景下(如大量网络设备或 RAID),initramfs 的启动等待时间可能很长。
可能的改进方向包括:使用统一固件接口(UEFI)的自定义启动环境、容器技术的应用、以及更智能的模块依赖管理。
实践示例
实例:构建一个最小的 initramfs
让我们从头开始构建一个最小的 initramfs,来深化理解。这个示例使用 busybox 作为基础工具。
第一步:准备目录结构
#!/bin/bash
# 创建 initramfs 工作目录
INITRAMFS_DIR="initramfs"
mkdir -p $INITRAMFS_DIR/{bin,sbin,etc,lib,lib64,dev,proc,sys,root,newroot}
# 复制必要的工具(使用 busybox)
cp /bin/busybox $INITRAMFS_DIR/bin/
# 创建 busybox 的符号链接(模拟各种命令)
cd $INITRAMFS_DIR/bin
for cmd in sh ls cat mount umount mkdir cp rm; do
ln -sf busybox $cmd
done
cd -
# 复制必要的库
cp /lib64/ld-linux-x86-64.so.2 $INITRAMFS_DIR/lib64/
cp /lib64/libc.so.6 $INITRAMFS_DIR/lib64/
cp /lib64/libm.so.6 $INITRAMFS_DIR/lib64/
第二步:创建初始化脚本
#!/bin/sh
# $INITRAMFS_DIR/init - initramfs 的核心启动脚本
# 挂载必要的伪文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
# 尝试挂载根文件系统
# 假设根分区是 /dev/sda1
if ! mount -r /dev/sda1 /newroot; then
echo "Failed to mount root filesystem"
# 进入应急 shell
/bin/sh
exit 1
fi
# 切换到真实根文件系统
cd /newroot
pivot_root . initramfs # 或使用 switch_root 工具
# 卸载 initramfs(现在位于 /initramfs)
umount -l /initramfs
# 执行真实根中的 init
exec /sbin/init
第三步:打包成 cpio 格式
#!/bin/bash
# 生成 cpio 格式的 initramfs
cd $INITRAMFS_DIR
find . -print0 | \
cpio -0 -o -H newc -F ../initramfs.cpio
# 压缩
gzip -f ../initramfs.cpio
mv ../initramfs.cpio.gz ../initramfs.img
echo "initramfs 已生成: initramfs.img"
第四步:配置引导加载程序
对于 GRUB 2,编辑 /etc/default/grub:
# /etc/default/grub
GRUB_CMDLINE_LINUX="root=/dev/sda1 ro"
# 其他配置...
然后在 GRUB 菜单项中指定 initramfs:
menuentry 'Linux with Custom Initramfs' {
search --no-floppy --label --set root mylinux
echo 'Loading Linux kernel...'
insmod gzio
insmod part_msdos
insmod ext2
set root='(hd0,msdos1)'
linux16 /boot/vmlinuz-5.x.x root=/dev/sda1 ro
echo 'Loading initial ramdisk...'
initrd16 /boot/initramfs.img
}
第五步:调试和验证
# 1. 查看 initramfs 内容
gunzip -c initramfs.img | cpio -t
# 2. 解压 initramfs 进行检查
mkdir -p extracted
cd extracted
gunzip -c ../initramfs.img | cpio -idmv
ls -la
# 3. 检查库依赖
ldd init
ldd bin/busybox
# 4. 使用 initramfs-tools 的内置验证
lsinitramfs initramfs.img
这个例子虽然简化了很多细节(实际的 initramfs 需要处理多种设备、文件系统等),但展示了 initramfs 的核心工作原理。现代工具如 dracut 本质上就是自动化这些步骤。
工具与调试
| 工具/命令 |
用途 |
示例 |
lsinitramfs |
列出 initramfs 内容 |
lsinitramfs /boot/initramfs-5.10.0.img |
cpio |
打包/解包 cpio 归档 |
cpio -idmv < initramfs.cpio |
dracut |
生成 initramfs |
dracut /boot/initramfs-$(uname -r).img |
mkinitramfs |
Debian 风格的 initramfs 生成 |
mkinitramfs -o /boot/initramfs.img |
gunzip |
解压 gzip 压缩 |
gunzip -c initramfs.img.gz |
file |
识别文件格式 |
file initramfs.img |
strings |
提取文本字符串 |
strings initramfs.img | grep -i "version" |
strace |
追踪系统调用 |
strace -e trace=open,read init |
grub-mkconfig |
自动生成 GRUB 配置 |
grub-mkconfig -o /boot/grub/grub.cfg |
调试技巧
查看 initramfs 解压过程
在内核启动参数中添加 rd.debug,可以看到 initramfs 解压的详细日志:
# /etc/default/grub
GRUB_CMDLINE_LINUX="root=/dev/sda1 rd.debug"
进入 initramfs 调试 shell
如果启动失败,可以进入 initramfs 的调试 shell:
# 在内核参数中添加
rd.break
这会在 initramfs 中间断,打开一个 shell 供调试使用。此时根文件系统还未挂载或切换,可以手动运行命令来诊断问题。
查看 udev 事件
# 在 initramfs 中查看 udev 事件(device enumeration)
udevadm control --log-priority=debug
# 或者查看内核日志
dmesg | grep -i "udev\|device"
架构总览
下面是 Linux 系统启动中 initramfs 的完整架构:

initramfs 在这个启动过程中的角色是桥梁:它连接内核的早期初始化和真实根文件系统的引导。关键点包括:
- 独立性:initramfs 是一个完全独立的、自给自足的最小系统。
- 临时性:initramfs 的使命在 switch_root 后就完成了,其内容被卸载。
- 灵活性:不同硬件配置可以有完全不同的 initramfs,但使用相同的内核。
- 可定制性:通过 dracut 或 mkinitramfs 的模块系统,可以灵活地添加或删除功能。
下面是内存中各个组件的布局示意:

系统内存管理与优化
当 initramfs 被加载和解压时,内存管理是一个重要的考虑因素。特别是对于有限内存的系统,initramfs 可能会消耗大量的 RAM。让我们深入看看这个方面。
内存使用分析
initramfs 在内存中有几个阶段:
- 压缩态:initramfs.img(gzip 压缩)被加载到内存。
- 解压态:解压后的 cpio 数据存在于内存中。
- 文件系统态:文件被解压到 rootfs,形成 VFS 的目录树。
/* kernel/init/initramfs.c - 内存计算示意 */
static int __init unpack_to_rootfs(char *buf, unsigned long len)
{
unsigned long mem_usage = 0;
/* 第一遍(干运行): 计算内存占用 */
dry_run = 1;
written = unpack_cpio(buf, len, &mem_usage);
/* 输出内存使用情况 */
printk(KERN_INFO "Initramfs needs %lu bytes of memory\n", mem_usage);
if (written != len) {
return -EINVAL;
}
/* 第二遍(真正解压): 释放压缩数据 */
free_init_pages("initramfs",
(unsigned long)&__initramfs_start,
(unsigned long)&__initramfs_end);
return 0;
}
对于内存受限的系统,可以:
- 精简 initramfs:只包含绝对必要的驱动和工具。
- 动态模块加载:将不常用的驱动放在根文件系统中,需要时才加载。
- 压缩策略:选择压缩率更高的压缩算法(如 xz、lz4)。
大型 initramfs 的优化
# 查看 initramfs 的大小和压缩率
ls -lh /boot/initramfs*
gunzip -l /boot/initramfs-$(uname -r).img
# 使用 xz 压缩(压缩率更高,但速度慢)
dracut --hostonly -H -f /boot/initramfs-$(uname -r).img
# 或者在 dracut 配置中设置
# echo 'COMPRESS="xz"' >> /etc/dracut.conf.d/compress.conf
设备检测与驱动加载
initramfs 中一个关键的工作是设备检测和驱动加载。现代 Linux 使用 udev 和 hotplug 机制来自动化这个过程。
udev 的角色
udev 是用户空间的设备管理器,它从内核接收 uevent(设备事件)并据此创建、删除或配置设备文件。在 initramfs 中,udev 的核心任务是:
#!/bin/sh
# initramfs 中的设备初始化流程
# 启动 udevd daemon
/sbin/udevd --daemon --resolve-names=never
# 触发设备事件重新扫描
udevadm trigger --action=add
# 等待设备规则被应用
udevadm settle --timeout=30
# 现在 /dev 中应该有了必要的设备文件
ls -la /dev/sda*
udev 规则可以根据设备属性自动加载相应的驱动:
# /etc/udev/rules.d/60-module-load.rules
# 当检测到 USB 存储设备时自动加载 usb_storage 模块
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \
RUN+="/sbin/modprobe usb_storage"
# 当检测到 SATA 驱动器时加载 ata_piix 模块
ACTION=="add", SUBSYSTEM=="ata_port", RUN+="/sbin/modprobe ata_piix"
全文总结
initramfs 是现代 Linux 启动过程中的关键组件,它优雅地解决了内核自引用的问题,并提供了支持复杂硬件配置和启动场景的灵活性。理解 initramfs 的工作原理对于系统管理员、内核开发者和嵌入式工程师都是至关重要的。
核心技术要点总结
| 技术点 |
关键理解 |
实践意义 |
| cpio 格式 |
简单的归档格式,易于内核解析 |
直接使用 cpio 工具可验证和调试 |
| VFS 与 rootfs |
所有文件系统的抽象层,initramfs 初始化 rootfs |
理解文件系统的统一接口 |
| 模块加载 |
insmod/modprobe 在 initramfs 中加载驱动 |
驱动加载顺序和依赖关系很重要 |
| 设备检测 |
udev 自动化设备文件创建和驱动加载 |
复杂硬件需要配置 udev 规则 |
| switch_root |
chroot + execve 的组合,切换到真实根 |
理解进程映像替换的原理 |
| 引导加载程序集成 |
bootloader 负责加载内核和 initramfs |
不同引导方案(BIOS vs UEFI)差异大 |
常见问题排查思路
启动失败 → 查看内核日志(dmesg)
→ 进入 initramfs shell(rd.break)
→ 手动挂载测试(mount 命令)
→ 检查驱动是否加载(lsmod)
→ 检查 udev 事件(udevadm)
→ 最后确认 init 进程存在
未来演进方向
随着技术的发展,initramfs 的实现也在不断演进:
- 统一的固件接口(UEFI)降低了对不同启动方案的需求。
- 容器化思想被应用于 initramfs,使其更易于版本管理。
- 自适应 initramfs 根据硬件自动定制,减少不必要的内容。
- 快速启动优化使用预计算和缓存来加速启动过程。
最后,initramfs 虽然在现代 Linux 中是必需的基础设施,但它的复杂性也常常被系统管理员忽视。理解它的工作原理,能帮助我们更好地排查启动问题、优化系统性能,以及在需要时定制专业的 Linux 部署方案。如果你对更多底层系统或 网络与内核 知识感兴趣,例如在 云栈社区 这样的技术论坛上,常常可以找到关于系统启动、驱动开发乃至 编译原理与操作系统 的深度讨论与资源分享。