Linux启动过程中的initramfs和rootfs详解
目录
- 为什么需要initramfs?
- initramfs是否包含在内核镜像中?
- initramfs的挂载阶段和流程
- initramfs的init程序和rootfs的init程序的区别
- 如何制作initramfs镜像?
- initramfs内容会复制到rootfs吗?
- 单镜像vs双镜像方案
- 为什么复杂系统不适合单镜像?
- 为什么initramfs需要解压而rootfs只需挂载?
- 什么是归档文件?
- 为什么还需要制作双镜像系统?
1. 为什么需要initramfs?
问题
在挂载根文件系统之前为什么还需要initramfs文件系统?
核心答案
initramfs (Initial RAM File System) 是Linux内核启动过程中的关键组件,在挂载真正的根文件系统之前提供临时的用户空间环境。
主要原因
1.1 硬件初始化和驱动加载
- 真正的根文件系统可能位于需要特殊驱动的设备上(RAID、LVM、加密分区、网络存储等)
- 内核无法将所有可能的驱动都编译进去
- initramfs提供临时环境用于加载这些必要的驱动模块
1.2 设备检测和准备
- 需要时间来检测和初始化存储设备
- 某些设备(如USB驱动器)可能需要几秒钟才能被识别
- initramfs可以等待设备就绪后再挂载根文件系统
1.3 复杂存储配置支持
- LVM (逻辑卷管理):需要扫描和激活卷组
- 软RAID:需要组装RAID阵列
- 加密分区:需要解密操作(如LUKS)
- 网络文件系统:需要配置网络接口和协议栈
1.4 引导参数处理
- 解析内核命令行参数
- 根据不同的启动选项执行不同的初始化流程
- 支持从不同的设备或分区启动
1.5 根文件系统切换
- initramfs提供完整的用户空间环境
- 执行必要的检查和准备工作
- 安全地切换到真正的根文件系统(pivot_root/switch_root)
启动流程简述
BIOS/UEFI → Bootloader → 加载内核 → 解压initramfs到内存
→ 执行initramfs中的init脚本 → 加载必要驱动 → 挂载真正的根文件系统
→ 切换到真正的根文件系统 → 执行/sbin/init → 系统启动完成
2. initramfs是否包含在内核镜像中?
问题
initramfs是包括在Linux kernel镜像中的吗?
答案
是的,initramfs通常包含在内核镜像中,但具体情况需要分几种场景说明。
2.1 内置initramfs (Built-in initramfs)
现代Linux内核镜像通常将initramfs直接打包在内核镜像文件中:
- 编译时集成 :在编译内核时通过
CONFIG_INITRAMFS_SOURCE配置选项直接编译进内核 - 单一镜像 :生成的内核镜像(如
vmlinuz或bzImage)已经包含了initramfs - 自动解压:内核启动时会自动将内置的initramfs解压到内存中
2.2 外部initramfs (External initramfs)
也可以将initramfs作为单独的文件,由bootloader加载:
- 独立文件 :initramfs作为独立的
.img或.cpio文件存在(如initramfs.img) - Bootloader加载:GRUB或其他bootloader会将内核和initramfs分别加载到内存
- 传递给内核:Bootloader通过特定的协议告诉内核initramfs的内存位置
2.3 在嵌入式系统中
对于嵌入式系统(如本IPC项目),常见做法:
方式1:内核 + 内置initramfs = 单一镜像
- 优点:简化部署,只需一个文件
- 缺点:更新initramfs需要重新编译内核
方式2:内核镜像 + 独立initramfs文件
- 优点:可以独立更新initramfs
- 缺点:需要管理两个文件,bootloader配置更复杂
2.4 两种方式的比较
| 特性 | 内置initramfs | 外部initramfs |
|---|---|---|
| 文件数量 | 单个内核镜像 | 内核 + initramfs |
| 更新灵活性 | 需重新编译内核 | 可独立更新 |
| Bootloader配置 | 简单 | 需指定两个文件 |
| 适用场景 | 嵌入式、固定配置 | 桌面、服务器 |
3. initramfs的挂载阶段和流程
问题
initramfs文件系统是内核启动的哪一阶段挂载的?挂载流程是怎样的?
关键时间点
initramfs在内核初始化的最后阶段被解压和"挂载",具体在 start_kernel() 函数执行完毕后,进入第一个用户空间进程之前。
3.1 内核代码调用栈
c
start_kernel() // init/main.c
└─> rest_init() // 内核初始化的最后一步
└─> kernel_init() // PID=1 的内核线程
└─> kernel_init_freeable()
├─> do_basic_setup()
│ └─> do_initcalls() // 执行各模块初始化
│
└─> populate_rootfs() // ★★★ initramfs解压挂载 ★★★
│
└─> unpack_to_rootfs() // 解压initramfs到rootfs
3.2 详细挂载流程
阶段1:rootfs创建(在initramfs之前)
c
vfs_caches_init()
└─> mnt_init()
└─> init_rootfs() // 注册rootfs文件系统类型
└─> init_mount_tree() // 创建初始的rootfs挂载点
关键点:
- rootfs是一个基于内存的文件系统(tmpfs/ramfs)
- 这是内核中最早被挂载的文件系统
- 所有后续的文件系统挂载都基于这个rootfs
阶段2:initramfs解压(核心阶段)
c
populate_rootfs()
{
// 1. 解压编译时内置的initramfs(如果有)
if (__initramfs_start < __initramfs_end) {
unpack_to_rootfs(__initramfs_start,
__initramfs_end - __initramfs_start);
}
// 2. 解压bootloader传递的外部initramfs(如果有)
if (initrd_start) {
unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start);
}
}
unpack_to_rootfs()
{
// 识别压缩格式: gzip, bzip2, lzma, xz, lzo, lz4
// 解压缩数据
// 解析cpio格式
// 创建文件、目录、设备节点、符号链接等
while (解析cpio头) {
switch (文件类型) {
case S_IFDIR: // 目录
ksys_mkdir(name, mode);
break;
case S_IFREG: // 普通文件
fd = ksys_open(name, O_WRONLY|O_CREAT, mode);
ksys_write(fd, data, size);
ksys_close(fd);
break;
case S_IFLNK: // 符号链接
ksys_symlink(target, name);
break;
case S_IFBLK: // 块设备
case S_IFCHR: // 字符设备
ksys_mknod(name, mode, dev);
break;
}
}
}
阶段3:切换到initramfs执行
c
kernel_init()
{
kernel_init_freeable();
// 尝试执行initramfs中的init程序
if (!try_to_run_init_process("/init")) // initramfs标准路径
return 0;
if (!try_to_run_init_process("/sbin/init"))
return 0;
// ... 其他路径尝试
panic("No working init found.");
}
3.3 完整时间线
T0: Bootloader加载内核到内存
T1: 内核解压(如果是压缩内核)
T2: start_kernel() - 内核初始化开始
T3: 各子系统初始化(mm, sched, irq等)
T4: VFS初始化 - vfs_caches_init()
T5: rootfs创建 - 创建空的rootfs
T6: rest_init() - 创建kernel_init线程
─────────────────────────────────────
T7: kernel_init() - ★ 关键阶段开始 ★
T8: populate_rootfs() - ★ initramfs解压 ★
T9: unpack_to_rootfs() - cpio解包到rootfs
T10: 解压完成 - rootfs现在有内容了
─────────────────────────────────────
T11: run_init_process - 执行/init
T12: 用户空间init - 加载驱动、挂载真正rootfs
T13: switch_root - 切换到真正的根文件系统
T14: 系统启动完成 - 进入正常运行状态
3.4 initramfs与rootfs的关系
重要概念:
- rootfs:内核创建的根文件系统框架(空的)
- initramfs:解压到rootfs中的实际内容
- 解压后,rootfs中就包含了initramfs的所有文件
4. initramfs的init程序和rootfs的init程序的区别
问题
initramfs中的init程序和根文件系统rootfs中的/sbin/init有什么区别?作用是什么?
4.1 基本区别对比表
| 特性 | initramfs的/init | rootfs的/sbin/init |
|---|---|---|
| 执行时机 | 内核启动后第一个执行 | 切换根文件系统后执行 |
| 生命周期 | 临时的,完成任务后退出 | 永久运行,PID=1 |
| 主要任务 | 准备挂载真正的rootfs | 系统初始化和进程管理 |
| 文件位置 | /init(initramfs根目录) | /sbin/init(真正rootfs) |
| 实现方式 | 通常是shell脚本或小程序 | systemd/SysVinit/OpenRC等 |
| 复杂度 | 简单,功能单一 | 复杂,功能完整 |
| 存储位置 | 内存(RAM) | 磁盘/存储设备 |
4.2 initramfs中的/init程序
主要职责
1. 挂载必要的虚拟文件系统
bash
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t tmpfs none /run
2. 加载必要的内核模块
bash
modprobe ahci # SATA控制器
modprobe sd_mod # SCSI磁盘
modprobe usb-storage # USB存储
modprobe ext4 # 文件系统驱动
modprobe dm-crypt # 磁盘加密
3. 设备检测和等待
bash
udevd --daemon
udevadm trigger
udevadm settle
# 等待特定设备出现
while [ ! -e /dev/sda1 ]; do
sleep 0.1
done
4. 处理特殊存储配置
bash
# LVM激活
vgchange -ay
# RAID组装
mdadm --assemble --scan
# 解密加密分区
cryptsetup luksOpen /dev/sda2 root
5. 挂载真正的根文件系统
bash
mount -t ext4 /dev/sda1 /newroot
6. 切换到真正的根文件系统
bash
exec switch_root /newroot /sbin/init
完整示例脚本
bash
#!/bin/sh
# /init - initramfs的init程序
# 基础环境设置
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
# 挂载虚拟文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
# 解析内核命令行参数
read -r cmdline < /proc/cmdline
for param in $cmdline; do
case "$param" in
root=*)
ROOT="${param#root=}"
;;
rootfstype=*)
ROOTFSTYPE="${param#rootfstype=}"
;;
esac
done
# 启动udev管理设备
/sbin/udevd --daemon
udevadm trigger --action=add
udevadm settle
# 加载必要的内核模块
modprobe -q ext4
modprobe -q usb-storage
# 等待根设备就绪
echo "Waiting for root device $ROOT..."
while [ ! -e "$ROOT" ]; do
sleep 0.1
done
# 挂载真正的根文件系统
mkdir -p /newroot
mount -t ${ROOTFSTYPE:-auto} "$ROOT" /newroot
# 检查挂载是否成功
if [ ! -x /newroot/sbin/init ]; then
echo "ERROR: Cannot find /sbin/init on root filesystem"
exec /bin/sh
fi
# 清理
killall udevd
umount /dev
umount /sys
umount /proc
# 切换到真正的根文件系统
echo "Switching to real root filesystem..."
exec switch_root /newroot /sbin/init
4.3 根文件系统的/sbin/init程序
主要职责
1. 系统初始化
- 设置主机名
- 配置网络
- 挂载所有文件系统(/home, /var等)
- 初始化系统日志
- 设置时区和语言环境
2. 服务管理
- 启动系统服务(网络、数据库、Web服务器等)
- 管理服务依赖关系
- 并行启动服务
- 监控服务状态
3. 进程管理
- 作为所有用户进程的祖先
- 回收孤儿进程
- 处理SIGCHLD信号
4. 运行级别管理
bash
# SysVinit运行级别
0 - 关机
1 - 单用户模式
3 - 多用户模式(有网络)
5 - 图形界面
6 - 重启
# systemd目标(target)
multi-user.target # 对应运行级别3
graphical.target # 对应运行级别5
5. 系统关闭
- 处理关机/重启请求
- 优雅地停止所有服务
- 卸载文件系统
4.4 执行流程对比
完整的启动序列:
T0: Bootloader 加载内核 + initramfs
───────────────────────────────────────
T1: 内核启动并解压initramfs到rootfs
执行 /init (initramfs的init) ← 第一个用户空间进程
───────────────────────────────────────
T2: initramfs的/init运行
├─ mount /proc, /sys, /dev
├─ 加载存储驱动
├─ 激活LVM/RAID
├─ 解密加密分区
├─ mount真正的rootfs到/newroot
└─ exec switch_root /newroot /sbin/init
───────────────────────────────────────
T3: 根文件系统的/sbin/init运行
├─ 系统初始化
├─ 启动各种服务
├─ 启动登录管理器
└─ 持续运行,管理所有进程
───────────────────────────────────────
T4: 系统正常运行
/sbin/init (PID=1) 一直运行
4.5 switch_root的关键作用
switch_root做了什么:
1. 将/newroot变成新的根目录
2. 删除旧的initramfs内容(释放内存)
3. 执行新的init程序(PID保持为1)
4.6 总结
initramfs的/init:
- 定位:临时的"引导助手"
- 任务:加载驱动、准备环境、挂载rootfs
- 生命:完成任务后退出,被替换
- 复杂度:简单(几十到几百行)
rootfs的/sbin/init:
- 定位:系统的"总管家"
- 任务:初始化系统、管理服务、监控进程
- 生命:从启动到关机一直运行
- 复杂度:复杂(数千到数十万行)
关键理解 :两者是接力关系,initramfs的/init完成使命后通过exec switch_root将自己替换为rootfs的/sbin/init。
5. 如何制作initramfs镜像?
问题
那么,initramfs文件系统是如何制作的?制作这个镜像依赖内核镜像吗?
简短回答
制作initramfs镜像通常不依赖内核镜像。initramfs是一个独立的文件归档(cpio格式),可以单独制作。但在某些情况下(如编译时内置),它会在内核编译阶段被集成到内核镜像中。
5.1 initramfs的文件格式
initramfs本质上是一个压缩的cpio归档文件
未压缩: initramfs.cpio
压缩后: initramfs.cpio.gz (gzip压缩)
initramfs.cpio.bz2 (bzip2压缩)
initramfs.cpio.xz (xz压缩)
initramfs.cpio.lz4 (lz4压缩)
5.2 制作initramfs的多种方法
方法1:手工制作(最基础)
bash
#!/bin/bash
# 从零开始手工创建initramfs
# 1. 创建工作目录
mkdir -p initramfs
cd initramfs
# 2. 创建基本目录结构
mkdir -p bin sbin lib lib64 usr/bin usr/sbin
mkdir -p proc sys dev etc/init.d
mkdir -p newroot run tmp var
# 3. 复制必要的二进制文件(使用busybox)
cp /path/to/busybox bin/
cd bin
ln -s busybox sh
ln -s busybox mount
ln -s busybox umount
ln -s busybox switch_root
cd ..
# 4. 复制必要的库文件
ldd bin/busybox # 查找依赖的库
cp /lib/x86_64-linux-gnu/libc.so.6 lib64/
cp /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 lib64/
# 5. 复制内核模块(可选)
mkdir -p lib/modules/$(uname -r)
cp -r /lib/modules/$(uname -r)/kernel/drivers/ata lib/modules/$(uname -r)/
# 6. 创建init脚本
cat > init << 'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
modprobe ahci
modprobe ext4
sleep 2
mount -t ext4 /dev/sda1 /newroot
exec switch_root /newroot /sbin/init
EOF
chmod +x init
# 7. 创建设备节点
mknod dev/console c 5 1
mknod dev/null c 1 3
# 8. 打包成cpio归档
find . | cpio -o -H newc > ../initramfs.cpio
# 9. 压缩
cd ..
gzip initramfs.cpio
# 现在你有了 initramfs.cpio.gz
方法2:使用dracut(Fedora/RHEL/CentOS)
bash
# 安装dracut
sudo yum install dracut
# 基本用法 - 自动创建initramfs
sudo dracut /boot/initramfs-$(uname -r).img $(uname -r)
# 强制重新生成
sudo dracut --force /boot/initramfs-$(uname -r).img $(uname -r)
# 添加特定模块
sudo dracut --add-drivers "ahci ext4 xfs" \
--add "lvm dm crypt" \
/boot/initramfs-custom.img $(uname -r)
# 列出内容
lsinitrd /boot/initramfs-$(uname -r).img
方法3:使用mkinitramfs(Debian/Ubuntu)
bash
# 安装工具
sudo apt-get install initramfs-tools
# 为当前内核创建initramfs
sudo mkinitramfs -o /boot/initrd.img-$(uname -r) $(uname -r)
# 更新所有内核的initramfs
sudo update-initramfs -u
# 查看内容
lsinitramfs /boot/initrd.img-$(uname -r)
方法4:使用mkinitcpio(Arch Linux)
bash
# 安装
sudo pacman -S mkinitcpio
# 生成initramfs
sudo mkinitcpio -p linux
# 配置文件:/etc/mkinitcpio.conf
MODULES=(i915 ext4)
HOOKS=(base udev autodetect modconf block filesystems keyboard fsck)
COMPRESSION="xz"
方法5:编译时内置到内核
bash
# 在内核编译时集成initramfs
# 1. 准备initramfs目录结构
mkdir -p /path/to/my-initramfs
# ... 填充文件和目录 ...
# 2. 配置内核
cd /path/to/linux-source
make menuconfig
# 在menuconfig中设置:
# General setup --->
# [*] Initial RAM filesystem and RAM disk support
# (/path/to/my-initramfs) Initramfs source file(s)
# 或直接修改 .config
echo 'CONFIG_INITRAMFS_SOURCE="/path/to/my-initramfs"' >> .config
# 3. 编译内核
make -j$(nproc)
# 结果:生成的内核镜像已经包含了initramfs
方法6:针对嵌入式系统(Buildroot/Yocto)
Buildroot方式:
bash
# 配置Buildroot
make menuconfig
# Filesystem images --->
# [*] cpio the root filesystem
# (xz) Compression method
# 构建
make
# 输出:output/images/rootfs.cpio.xz
5.3 是否依赖内核镜像?
回答:通常不依赖,但有特例
| 场景 | 是否依赖内核 | 说明 |
|---|---|---|
| 独立制作 | ❌ 不依赖 | initramfs可以完全独立制作 |
| 模块兼容性 | ⚠️ 需要匹配 | 内核模块必须与内核版本匹配 |
| 编译时内置 | ✅ 依赖 | CONFIG_INITRAMFS_SOURCE方式需要重新编译内核 |
| 固件文件 | ⚠️ 可能需要 | 某些驱动需要特定版本的固件 |
5.4 工具对比
| 工具 | 发行版 | 复杂度 | 自动化程度 | 是否依赖内核镜像 |
|---|---|---|---|---|
| 手工cpio | 通用 | 高 | 低 | ❌ 不依赖 |
| dracut | RHEL/Fedora | 中 | 高 | ❌ 不依赖(需要版本号) |
| mkinitramfs | Debian/Ubuntu | 中 | 高 | ❌ 不依赖(需要版本号) |
| mkinitcpio | Arch Linux | 中 | 高 | ❌ 不依赖(需要版本号) |
| Buildroot | 嵌入式 | 低 | 非常高 | ❌ 不依赖 |
| 内核集成 | 通用 | 低 | 高 | ✅ 依赖(编译时集成) |
6. initramfs内容会复制到rootfs吗?
问题
initramfs镜像中的目录文件会在挂载rootfs后复制到rootfs文件系统中去吗?
简短回答
不会! initramfs中的文件不会被复制 到真正的rootfs中。相反,initramfs在切换根文件系统后会被完全删除和释放。
6.1 内存布局变化
阶段1:内核启动后,解压initramfs
┌─────────────────────────────────┐
│ rootfs (ramfs/tmpfs) │ ← 这是初始的rootfs
│ ├── init │ ← initramfs的内容
│ ├── bin/ │
│ ├── sbin/ │
│ └── lib/ │
└─────────────────────────────────┘
内存占用:例如 10MB
阶段2:挂载真正的rootfs到/newroot
┌─────────────────────────────────┐
│ rootfs (ramfs/tmpfs) │ ← 旧的,还在内存中
│ ├── init │
│ ├── bin/ │
│ ├── newroot/ ← 挂载点 │
│ │ ├── sbin/ │ ← 真正rootfs的内容
│ │ ├── usr/ │
│ │ └── etc/ │
└─────────────────────────────────┘
阶段3:switch_root后
┌─────────────────────────────────┐
│ 新的根文件系统 │ ← /newroot变成了/
│ ├── sbin/ │ ← 真正rootfs的内容
│ ├── usr/ │ (initramfs的内容已被删除)
│ └── etc/ │
└─────────────────────────────────┘
旧的initramfs内容已被释放!
6.2 switch_root的工作原理
c
// switch_root的伪代码逻辑
switch_root(const char *newroot, const char *init)
{
// 1. 切换当前工作目录到新的根
chdir(newroot);
// 2. 将newroot挂载点移动到/
mount(newroot, "/", NULL, MS_MOVE, NULL);
// 3. 切换根目录
chroot(".");
// 4. ★★★ 删除旧的initramfs内容 ★★★
delete_contents("/", dev);
// 5. 执行新的init程序
execv(init, argv);
}
6.3 为什么要删除initramfs内容?
- 释放内存:initramfs通常占用5MB-50MB的内存,删除后可被系统其他部分使用
- 避免混淆:防止同名文件/目录冲突
- 清晰的边界:临时启动环境vs永久运行环境,物理分离
6.4 如何在切换前传递数据
如果确实需要传递数据:
方法1:挂载共享目录
bash
mount -t tmpfs tmpfs /newroot/run/initramfs
cp /important_data.txt /newroot/run/initramfs/
方法2:写入目标文件系统
bash
mount /dev/sda1 /newroot
echo "Generated during init" > /newroot/var/log/boot-info.txt
7. 单镜像vs双镜像方案
问题
既然initramfs中的文件不会复制到rootfs中,那么在制作initramfs后还需要制作rootfs镜像吗?
答案
通常需要分别制作initramfs和rootfs镜像,但具体取决于系统的设计方案。
7.1 两种主流方案
方案A:双镜像方案(常见于通用Linux系统)
需要制作:
1. initramfs镜像(临时启动环境)
2. rootfs镜像(永久运行环境)
启动流程:
Bootloader → 内核 + initramfs → 切换到 → rootfs
方案B:单镜像方案(常见于小型嵌入式系统)
只需要制作:
1. initramfs镜像(作为最终运行环境)
启动流程:
Bootloader → 内核 + initramfs → 系统在initramfs中运行
7.2 双镜像方案详解
文件系统布局:
┌─────────────────────────────────────┐
│ initramfs.cpio.gz │ 5-30 MB
│ ├─ /init │ 精简的启动环境
│ ├─ /bin/busybox │ 基础命令
│ ├─ /lib/modules/ │ 必要的驱动模块
│ └─ /sbin/ │
└─────────────────────────────────────┘
↓ 挂载并切换
┌─────────────────────────────────────┐
│ rootfs.ext4 │ 100MB - 数GB
│ ├─ /sbin/init (systemd/sysvinit) │ 完整的系统环境
│ ├─ /usr/ │ 用户程序
│ ├─ /lib/ │ 完整的库
│ └─ /etc/ │ 配置文件
└─────────────────────────────────────┘
7.3 单镜像方案详解
适用场景:
- 极简嵌入式设备
- 功能单一(如网络摄像头、路由器)
- 资源受限
- 只读系统
实现方式:
bash
#!/bin/sh
# initramfs的init不进行switch_root
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
# 挂载数据分区(如果需要)
mount /dev/mmcblk0p2 /data
# 启动应用
/usr/bin/my_application &
# 保持运行
exec /bin/sh
7.4 方案对比
| 特性 | 单镜像方案 | 双镜像方案 |
|---|---|---|
| 系统大小 | < 50MB | 任意大小 |
| 内存需求 | 全部常驻 | 按需加载 |
| 启动速度 | 较慢(大文件解压) | 较快 |
| 更新灵活性 | 差(需重做镜像) | 好(可单独更新) |
| 可维护性 | 差 | 好 |
| 适用场景 | 单一功能设备 | 通用系统 |
8. 为什么复杂系统不适合单镜像?
问题
既然系统可以有initramfs单镜像方案,也可以用initramfs和rootfs双镜像方案,那么为什么复杂的系统不适合使用单镜像方案?
核心原因
原因1:内存限制(最关键)⭐⭐⭐⭐⭐
单镜像方案的致命问题:整个系统必须常驻内存
小型系统:
RAM: 128MB
├─ 内核: 5MB
├─ initramfs: 15MB ← 可接受
└─ 运行空间: 108MB
复杂系统:
RAM: 512MB
├─ 内核: 10MB
├─ initramfs: 300MB ← 太大了!
└─ 运行空间: 202MB ← 不够用
双镜像方案:
RAM: 512MB
├─ 内核: 10MB
├─ initramfs: 20MB ← 启动后释放
├─ rootfs缓存: 动态 ← 按需加载
└─ 运行空间: ~450MB ← 充足
原因2:灵活性和可维护性⭐⭐⭐⭐
单镜像方案的更新流程:
修改配置 → 重新制作整个initramfs → 重新部署 → 重启系统
时间:10-30分钟
双镜像方案的更新流程:
修改配置 → 直接修改rootfs → 可能无需重启
时间:1分钟
原因3:存储利用率⭐⭐⭐⭐
单镜像方案:
压缩的initramfs: 50MB
解压后在内存: 150MB ← 需要这么多RAM!
双镜像方案:
内核: 10MB
initramfs: 8MB
rootfs: 100MB(在磁盘上,按需加载到内存)
原因4:动态加载和模块化⭐⭐⭐⭐
复杂系统的特点:不是所有功能都会用到
单镜像方案:
所有软件必须全部加载到内存
即使你只用其中一个功能
双镜像方案:
只加载你启动的服务
其他软件留在磁盘上
按需加载到内存
原因5:用户数据和日志⭐⭐⭐⭐
复杂系统需要存储:
- 日志文件(可能很大)
- 用户数据
- 数据库文件
- 缓存文件
单镜像方案:
initramfs通常是只读的或tmpfs
需要额外挂载可写分区
但系统本身仍在RAM中
双镜像方案:
rootfs在磁盘上
可以任意增长
8.1 决策指南
使用单镜像方案的条件(必须全部满足):
1. ✓ 系统总大小 < 50MB
2. ✓ 可用内存 > 系统大小 × 3
3. ✓ 功能单一或服务数量 < 5
4. ✓ 不需要频繁更新
5. ✓ 不需要持久化大量数据
6. ✓ 不需要安装额外软件包
必须使用双镜像方案的情况(满足任一):
1. ✗ 系统总大小 > 100MB
2. ✗ 需要运行多种服务(> 10个)
3. ✗ 需要数据库或大量数据存储
4. ✗ 需要包管理器(apt/yum/opkg)
5. ✗ 需要开发环境
6. ✗ 需要频繁更新和维护
7. ✗ 运行多用户系统
9. 为什么initramfs需要解压而rootfs只需挂载?
问题
initramfs文件系统需要解压,为什么rootfs不需要解压,只需要挂载?
核心原因
两者的存储格式和使用方式完全不同:
- initramfs :是一个归档文件(cpio格式),必须解压才能访问
- rootfs :是一个文件系统(ext4/xfs/squashfs等),可以直接挂载
9.1 数据组织方式的本质区别
initramfs (归档文件):
┌─────────────────────────────────────┐
│ initramfs.cpio.gz │ ← 单个文件
│ [压缩数据流] │
│ ├─ 文件1的元数据 + 数据 │ ← 所有文件连续存储
│ ├─ 文件2的元数据 + 数据 │
│ └─ 文件3的元数据 + 数据 │
└─────────────────────────────────────┘
特点:
- 线性存储,连续的数据流
- 没有索引结构
- 必须顺序解压才能访问
rootfs (文件系统):
┌─────────────────────────────────────┐
│ rootfs.ext4 │
│ ┌─────────────────┐ │
│ │ 超级块 │ │ ← 文件系统元数据
│ ├─────────────────┤ │
│ │ inode表 │ │ ← 文件索引
│ ├─────────────────┤ │
│ │ 数据块 │ │ ← 实际文件数据
│ └─────────────────┘ │
└─────────────────────────────────────┘
特点:
- 有完整的索引结构
- 可以随机访问任何文件
- 内核可以直接理解这个结构
9.2 访问方式对比
initramfs的访问过程:
c
// 1. 读取整个cpio归档
// 2. 解压缩
// 3. 解析cpio格式,逐个文件提取
// 4. 在rootfs中创建文件和写入数据
// 5. 释放原始cpio数据
rootfs的访问过程:
c
// 1. 识别文件系统类型
// 2. 读取超级块
// 3. 验证文件系统
// 4. 加载inode表的元数据
// 5. 建立VFS数据结构
// 完成!文件数据留在磁盘上,按需读取
9.3 为什么设计不同?
initramfs的设计考量:
- 尽可能小(节省内核镜像大小)
- 格式简单(内核早期无完整文件系统)
- 快速部署到内存
- 一次性使用后丢弃
- 选择:归档格式(cpio + 压缩)
rootfs的设计考量:
- 随机访问性能好
- 支持原地修改文件
- 数据持久化
- 支持大规模文件和目录
- 选择:完整的文件系统(ext4/xfs/btrfs等)
9.4 按需加载 vs 全部加载
initramfs(全部加载):
解压后的文件全部在内存中
即使不使用,也占用内存
rootfs(按需加载):
文件在磁盘上,按需加载到内存
不用的文件不占用内存
示例:
$ cat /etc/fstab
rootfs的操作:
1. 查找/etc目录的inode
2. 在目录中查找fstab文件
3. 读取fstab的inode
4. 从磁盘读取数据块
5. 返回内容
9.5 特殊情况:squashfs
squashfs的特性:
├─ 是一个真正的文件系统(不是归档)
├─ 但是只读的
├─ 高度压缩
└─ 可以直接挂载(不需要解压)
工作原理:
- 元数据已解压(在内存)
- 数据按需解压
- 可以随机访问任何文件
10. 什么是归档文件?
问题
什么是归档文件?是不是可以这样理解:initramfs其实不是真正的文件系统,只是归档文件形式的虚拟文件系统;如果是这样,那么是不是可以理解为,归档文件是可以被挂载的?
10.1 归档文件的定义
归档文件:将多个文件和目录打包成单个文件的格式
常见的归档文件格式:
├─ tar (Tape Archive)
├─ cpio (Copy In/Out) ← initramfs使用这个
├─ ar (Archive,用于.a静态库)
└─ zip (包含压缩)
归档文件的特征:
✓ 将多个文件合并成一个文件
✓ 保留文件的元数据
✓ 保留目录结构
✗ 不是文件系统
✗ 不能直接挂载
✗ 不支持随机访问
10.2 你的理解需要修正
理解1:"initramfs不是真正的文件系统"
部分正确,但需要澄清:
❌ 不准确的说法:
"initramfs不是真正的文件系统"
✅ 准确的说法:
"initramfs.cpio.gz(归档文件)不是文件系统,
但解压后,它的内容存储在rootfs中,
rootfs是一个真正的文件系统(tmpfs/ramfs)"
完整的过程:
initramfs.cpio.gz (归档文件)
↓ 解压
rootfs (tmpfs文件系统)
理解2:"归档文件形式的虚拟文件系统"
这个说法不准确:
❌ 错误的理解:
"归档文件形式的虚拟文件系统"
✅ 正确的理解:
"initramfs.cpio.gz是一个归档文件,
它被解压后,内容存储在一个真正的虚拟文件系统(rootfs/tmpfs)中"
理解3:"归档文件是可以被挂载的"
这个理解是错误的:
❌ 错误:归档文件通常不能直接挂载
一般情况:
tar.gz文件 ← 不能挂载
cpio.gz文件 ← 不能挂载
zip文件 ← 不能挂载
这些必须先解压,才能访问
特殊情况:
squashfs ← 可以挂载(虽然是压缩的)
iso文件 ← 可以挂载(ISO9660文件系统)
ext4镜像 ← 可以挂载
关键区别:
能否挂载取决于它是否是"文件系统格式",
而不是它是否被压缩或打包
10.3 什么能被挂载?
能被挂载的(必须是文件系统):
✓ 块设备上的文件系统
- /dev/sda1 (ext4文件系统)
✓ 文件系统镜像文件(通过loop设备)
- rootfs.ext4 (ext4文件系统镜像)
- rootfs.squashfs (squashfs文件系统)
✓ 虚拟文件系统
- tmpfs (基于RAM的文件系统)
- proc (进程信息文件系统)
不能被挂载的(归档文件):
✗ tar文件
✗ cpio文件
✗ zip文件
这些必须解压后才能使用!
10.4 正确的概念模型
完整的层次结构:
物理层:
├─ 硬盘/SSD
└─ 内存(RAM)
块设备层:
├─ /dev/sda1
└─ /dev/loop0
文件系统层:
├─ ext4
├─ tmpfs
└─ squashfs
VFS层:
└─ 统一的文件系统接口
应用层:
├─ 归档工具 (tar, cpio)
└─ 用户程序
关系:
- 文件系统可以被挂载(在VFS层)
- 归档文件是应用层的数据格式
- 归档文件必须通过工具解压
10.5 总结:准确的理解
准确的表述:
-
归档文件的定义:
- 将多个文件打包成单个文件的格式
- 不是文件系统
-
initramfs的本质:
- initramfs.cpio.gz是一个压缩的cpio归档文件
- 它不是文件系统
- 内核启动时将它解压到rootfs中
- rootfs才是真正的文件系统(tmpfs/ramfs)
-
挂载的对象:
- 只有文件系统可以被挂载
- 归档文件不能被挂载
- 归档文件必须先解压
-
正确的概念模型:
initramfs.cpio.gz (归档文件) ↓ 解压 rootfs (tmpfs,真正的文件系统) ↓ 可以被访问 文件和目录
11. 为什么还需要制作双镜像系统?
问题
既然rootfs是内核自己创建的,为什么还需要我们自己去制作双镜像系统呢?
核心误解的澄清
有两个不同的"rootfs"概念:
-
内核启动时创建的初始rootfs
- 这是一个空的tmpfs/ramfs文件系统
- 内核自动创建
- 只是一个容器/框架
- 没有任何实际内容
-
真正的根文件系统(通常也叫rootfs)
- 包含完整Linux系统的文件系统
- 存储在磁盘/Flash上
- 我们需要制作
- 包含所有系统文件和应用程序
11.1 详细解释
rootfs #1:内核创建的初始rootfs(临时的、空的)
c
// 内核代码
void __init mnt_init(void)
{
init_rootfs(); // 注册rootfs文件系统类型
init_mount_tree(); // 创建并挂载初始的rootfs
}
// 此时的rootfs:
┌─────────────────────────────────────┐
│ rootfs (tmpfs/ramfs) - 空的! │
│ (没有任何文件或目录) │
│ 大小:0字节(除了文件系统元数据) │
└─────────────────────────────────────┘
rootfs #2:我们制作的真正根文件系统(永久的、完整的)
bash
rootfs.ext4:
┌─────────────────────────────────────┐
│ 完整的Linux系统 │
│ ├─ /bin/ (几百个命令) │
│ ├─ /sbin/ (系统管理工具) │
│ ├─ /lib/ (几千个库文件) │
│ ├─ /usr/ │
│ ├─ /etc/ (配置文件) │
│ └─ /home/, /var/, /tmp/ ... │
│ 大小:100MB - 数GB │
└─────────────────────────────────────┘
11.2 完整的启动流程
T0: Bootloader运行
T1: 内核开始执行
T2: VFS初始化
└─ 创建空的rootfs ← 内核创建的rootfs是空的!
T3: 解压initramfs到rootfs
└─ 现在rootfs有initramfs的内容
T4: 执行initramfs的/init
└─ 挂载真正的rootfs到/newroot ← 我们制作的rootfs!
T5: switch_root
└─ 真正的rootfs成为新的/
11.3 为什么需要制作真正的rootfs?
原因1:内核创建的rootfs是空的
内核创建的rootfs:
- 只是一个空的文件系统框架
- 没有任何程序
- 没有shell、库文件
- 没有任何可以运行的东西
如果没有initramfs和真正的rootfs:
内核启动后 → rootfs是空的 → 没有init可执行 → 内核panic!
原因2:内核创建的rootfs在内存中
内核创建的初始rootfs:
- 类型:tmpfs或ramfs
- 位置:内存(RAM)中
- 大小限制:受内存限制
- 持久性:重启后丢失
我们制作的rootfs:
- 类型:ext4/xfs/squashfs等
- 位置:磁盘/Flash存储上
- 大小限制:可以很大(几GB)
- 持久性:永久保存
原因3:需要完整的Linux系统
一个可用的Linux系统需要:
最小系统(几十MB):
├─ /bin/sh (shell)
├─ /sbin/init (初始化程序)
├─ /lib/libc.so (C库)
├─ /etc/passwd (用户信息)
└─ /dev/console (设备节点)
实用系统(几百MB):
├─ 上述基础文件
├─ /usr/bin/* (用户命令)
├─ /usr/lib/* (库文件)
├─ /etc/* (配置文件)
└─ 应用程序和服务
这些都需要我们自己制作!
内核不会为你创建这些!
11.4 双镜像系统的必要性
阶段1:早期启动(使用initramfs)
需求:
- 加载存储设备驱动
- 检测和初始化硬件
- 处理复杂的存储配置
- 挂载真正的rootfs
特点:
- 必须很小
- 只需要基础工具
- 临时使用
阶段2:正常运行(使用真正的rootfs)
需求:
- 提供完整的操作系统环境
- 运行各种服务和应用
- 存储用户数据
- 长期运行
特点:
- 可以很大
- 包含所有软件
- 永久存储
11.5 概念总结
内核创建的rootfs = 空房子
initramfs = 搬家工人和临时工具
真正的rootfs = 你的家具和所有物品
搬家过程:
1. 内核建造了一个空房子(空rootfs)
2. 搬家工人进入(initramfs解压到rootfs)
3. 工人打开仓库门(挂载真正的rootfs)
4. 工人把家具搬进来(切换根文件系统)
5. 工人离开(删除initramfs)
6. 你开始在新家生活(系统在真正的rootfs中运行)
最终答案
即使内核创建了一个rootfs,我们仍然需要制作实际的内容来填充它。
- 内核创建的rootfs = 空容器
- initramfs = 我们制作的临时内容
- 真正的rootfs = 我们制作的永久系统
这就是为什么需要制作双镜像系统。
总结
通过以上11个问题的详细解答,我们全面了解了Linux启动过程中initramfs和rootfs的作用、区别、制作方法以及它们之间的关系。
关键要点
- initramfs的作用:临时启动环境,用于加载驱动和挂载真正的根文件系统
- initramfs的形式:cpio归档文件,可以内置在内核或作为独立文件
- rootfs的概念 :有两个rootfs概念需要区分
- 内核创建的空rootfs(临时容器)
- 我们制作的真正rootfs(永久系统)
- init程序的区别:initramfs的/init是临时助手,rootfs的/sbin/init是系统管家
- 制作方法:多种工具可供选择,从手工到自动化
- 数据传递:initramfs内容不会复制到rootfs,会被删除释放
- 方案选择:根据系统复杂度选择单镜像或双镜像方案
- 归档vs文件系统:归档文件需要解压,文件系统可以挂载
- 为什么需要双镜像:内核创建的rootfs是空的,需要我们制作实际内容
这套机制是Linux系统启动的核心,理解它对于嵌入式开发、系统优化和故障排查都至关重要。