跳到主要内容
Linux initramfs 原理与实现:从内核启动到根文件系统 | 极客日志
Shell / Bash
Linux initramfs 原理与实现:从内核启动到根文件系统 综述由AI生成 initramfs 是 Linux 启动过程中连接内核与真实根文件系统的临时文件系统。它采用 cpio 压缩归档格式,被加载到内存并解压至 rootfs,用于加载驱动、配置硬件及执行初始化脚本。通过 switch_root 机制,系统最终切换到真实根文件系统。深入剖析了 initramfs 的构建、加载、执行流程,对比了 initrd 区别,并提供了基于 busybox 的最小化构建实践及调试方法。
Ne0 发布于 2026/2/24 更新于 2026/5/31 29 浏览Linux initramfs 原理与实现:从内核启动到根文件系统
概览摘要
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() 和后续的启动函数中。
static int __init do_populate_rootfs ( ) {
unpack_to_rootfs(__initramfs_start, __initramfs_size);
(execute_command) {
ret = run_init_process(execute_command);
} {
ret = run_init_process( );
(ret) ret = run_init_process( );
(ret) ret = run_init_process( );
(ret) ret = run_init_process( );
}
ret;
}
void
if
else
"/sbin/init"
if
"/etc/init"
if
"/bin/init"
if
"/bin/sh"
return
这段代码展示了几个关键步骤:首先,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 如下:
struct cpio_newc_header {
char c_magic[6 ];
char c_ino[8 ];
char c_mode[8 ];
char c_uid[8 ];
char c_gid[8 ];
char c_nlink[8 ];
char c_mtime[8 ];
char c_filesize[8 ];
char c_devmajor[8 ];
char c_devminor[8 ];
char c_rdevmajor[8 ];
char c_rdevminor[8 ];
char c_namesize[8 ];
char c_check[8 ];
};
每个文件在 cpio 归档中都有这样的 header,后跟文件名、padding、文件数据和更多 padding。整个归档以一个名为 'TRAILER!!!' 的特殊条目结尾,标志着归档的完成。
find . -print0 | cpio -0 -o -H newc -F ../initramfs.cpio
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 中:
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) {
struct cpio_newc_header *hdr = (void *)buf;
if (memcmp (hdr->c_magic, "070701" , 6 ) != 0 ) {
break ;
}
unsigned long namesize = hex2bin(hdr->c_namesize);
unsigned long filesize = hex2bin(hdr->c_filesize);
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);
}
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 命令或系统调用来完成的。
int switch_root (const char * new_root) {
if (chroot(new_root) < 0 ) {
perror("chroot failed" );
return -1 ;
}
if (chdir("/" ) < 0 ) {
perror("chdir failed" );
return -1 ;
}
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);
}
}
}
return -1 ;
}
内核启动
解压 initramfs,执行 init
加载驱动程序
挂载根文件系统
设置新根目录
switch_root,执行真实 init
继续系统初始化
设计思想与架构
为什么需要 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 也通常有几 MB 大小,这对于某些嵌入式或资源受限的设备来说可能很大。
复杂性 : initramfs 越复杂,出现问题的可能性就越大。某些引导问题很难调试,因为你无法像调试用户空间程序那样容易地进入 initramfs 环境。
更新维护 : initramfs 需要随着内核模块的更新而更新。如果驱动版本与内核版本不兼容,可能导致启动失败。
动态设备发现的局限 : 在某些硬件复杂的场景下(如大量网络设备或 RAID),initramfs 的启动等待时间可能很长。
可能的改进方向包括:使用统一固件接口(UEFI)的自定义启动环境、容器技术的应用、以及更智能的模块依赖管理。
实践示例
实例:构建一个最小的 initramfs 让我们从头开始构建一个最小的 initramfs,来深化理解。这个示例使用 busybox 作为基础工具。
#!/bin/bash
INITRAMFS_DIR="initramfs"
mkdir -p $INITRAMFS_DIR /{bin,sbin,etc,lib,lib64,dev,proc,sys,root,newroot}
cp /bin/busybox $INITRAMFS_DIR /bin/
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
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
if ! mount -r /dev/sda1 /newroot; then
echo "Failed to mount root filesystem"
exit 1
fi
cd /newroot
pivot_root . initramfs
umount -l /initramfs
exec /sbin/init
#!/bin/bash
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:
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
}
gunzip -c initramfs.img | cpio -t
mkdir -p extracted
cd extracted
gunzip -c ../initramfs.img | cpio -idmv
ls -la
ldd init
ldd bin/busybox
lsinitramfs initramfs.img
这个例子虽然简化了很多细节(实际的 initramfs 需要处理多种设备、文件系统等),但展示了 initramfs 的核心工作原理。现代工具如 dracut 本质上就是自动化这些步骤。
工具与调试 工具/命令 用途 示例 lsinitramfs列出 initramfs 内容 lsinitramfs /boot/initramfs-5.10.0.imgcpio打包/解包 cpio 归档 cpio -idmv < initramfs.cpiodracut生成 initramfs dracut /boot/initramfs-$(uname -r).imgmkinitramfsDebian 风格的 initramfs 生成 mkinitramfs -o /boot/initramfs.imggunzip解压 gzip 压缩 gunzip -c initramfs.img.gzfile识别文件格式 file initramfs.imgstrings提取文本字符串 `strings initramfs.img strace追踪系统调用 strace -e trace=open,read initgrub-mkconfig自动生成 GRUB 配置 grub-mkconfig -o /boot/grub/grub.cfg
调试技巧 在内核启动参数中添加 rd.debug,可以看到 initramfs 解压的详细日志:
GRUB_CMDLINE_LINUX="root=/dev/sda1 rd.debug"
如果启动失败,可以进入 initramfs 的调试 shell:
这会在 initramfs 中间断,打开一个 shell 供调试使用。此时根文件系统还未挂载或切换,可以手动运行命令来诊断问题。
udevadm control --log-priority=debug
dmesg | grep -i "udev\|device"
架构总览 下面是 Linux 系统启动中 initramfs 的完整架构:
第一阶段:引导
BIOS/UEFI -> 引导加载程序 (GRUB/LILO)
第二阶段:内核和 initramfs 加载
加载内核镜像 -> 加载 initramfs -> 内核初始化 -> 内核创建 rootfs -> 解压 initramfs 到 rootfs
第三阶段:rootfs 和 initramfs
执行 init 进程 -> 挂载 proc/sys/dev -> 加载驱动模块 -> 检测硬件设备
第四阶段:initramfs init 执行
挂载真实根文件系统 -> 执行 switch_root
第五阶段:真实根和系统初始化
执行真实 init (/sbin/init) -> 加载系统服务 -> 用户登录
initramfs 在这个启动过程中的角色是桥梁 :它连接内核的早期初始化和真实根文件系统的引导。关键点包括:
独立性 : initramfs 是一个完全独立的、自给自足的最小系统。
临时性 : initramfs 的使命在 switch_root 后就完成了,其内容被卸载。
灵活性 : 不同硬件配置可以有完全不同的 initramfs,但使用相同的内核。
可定制性 : 通过 dracut 或 mkinitramfs 的模块系统,可以灵活地添加或删除功能。
内核早期阶段
内核代码 0x1000000+
initramfs 被加载的位置
内核栈
内核堆
rootfs VFS
initramfs 内容解压到 rootfs
/bin 目录
/lib 目录 (模块和库)
/etc 配置文件
运行 init 进程
/newroot (真实根挂载点)
执行 switch_root
系统内存管理与优化 当 initramfs 被加载和解压时,内存管理是一个重要的考虑因素。特别是对于有限内存的系统,initramfs 可能会消耗大量的 RAM。让我们深入看看这个方面。
内存使用分析
压缩态 : initramfs.img(gzip 压缩)被加载到内存。
解压态 : 解压后的 cpio 数据存在于内存中。
文件系统态 : 文件被解压到 rootfs,形成 VFS 的目录树。
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 的优化
ls -lh /boot/initramfs*
gunzip -l /boot/initramfs-$(uname -r).img
dracut --hostonly -f /boot/initramfs-$(uname -r).img
echo 'COMPRESS="xz"' >> /etc/dracut.conf.d/compress.conf
设备检测与驱动加载 initramfs 中一个关键的工作是设备检测和驱动加载。现代 Linux 使用 udev 和 hotplug 机制来自动化这个过程。
udev 的角色 udev 是用户空间的设备管理器,它从内核接收 uevent(设备事件)并据此创建、删除或配置设备文件。在 initramfs 中,udev 的核心任务是:
#!/bin/sh
/sbin/udevd --daemon --resolve-names=never
udevadm trigger --action=add
udevadm settle --timeout =30
ls -la /dev/sda*
udev 规则可以根据设备属性自动加载相应的驱动:
ACTION=="add" , SUBSYSTEM=="usb" , ENV{DEVTYPE}=="usb_device" , \nRUN+="/sbin/modprobe usb_storage"
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 部署方案。
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online