Linux启动过程中的initramfs和rootfs详解

Linux启动过程中的initramfs和rootfs详解

目录

  1. 为什么需要initramfs?
  2. initramfs是否包含在内核镜像中?
  3. initramfs的挂载阶段和流程
  4. initramfs的init程序和rootfs的init程序的区别
  5. 如何制作initramfs镜像?
  6. initramfs内容会复制到rootfs吗?
  7. 单镜像vs双镜像方案
  8. 为什么复杂系统不适合单镜像?
  9. 为什么initramfs需要解压而rootfs只需挂载?
  10. 什么是归档文件?
  11. 为什么还需要制作双镜像系统?

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 配置选项直接编译进内核
  • 单一镜像 :生成的内核镜像(如 vmlinuzbzImage)已经包含了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内容?

  1. 释放内存:initramfs通常占用5MB-50MB的内存,删除后可被系统其他部分使用
  2. 避免混淆:防止同名文件/目录冲突
  3. 清晰的边界:临时启动环境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 总结:准确的理解

准确的表述

  1. 归档文件的定义

    • 将多个文件打包成单个文件的格式
    • 不是文件系统
  2. initramfs的本质

    • initramfs.cpio.gz是一个压缩的cpio归档文件
    • 它不是文件系统
    • 内核启动时将它解压到rootfs中
    • rootfs才是真正的文件系统(tmpfs/ramfs)
  3. 挂载的对象

    • 只有文件系统可以被挂载
    • 归档文件不能被挂载
    • 归档文件必须先解压
  4. 正确的概念模型

    复制代码
    initramfs.cpio.gz (归档文件)
            ↓ 解压
    rootfs (tmpfs,真正的文件系统)
            ↓ 可以被访问
    文件和目录

11. 为什么还需要制作双镜像系统?

问题

既然rootfs是内核自己创建的,为什么还需要我们自己去制作双镜像系统呢?

核心误解的澄清

有两个不同的"rootfs"概念:

  1. 内核启动时创建的初始rootfs

    • 这是一个空的tmpfs/ramfs文件系统
    • 内核自动创建
    • 只是一个容器/框架
    • 没有任何实际内容
  2. 真正的根文件系统(通常也叫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的作用、区别、制作方法以及它们之间的关系。

关键要点

  1. initramfs的作用:临时启动环境,用于加载驱动和挂载真正的根文件系统
  2. initramfs的形式:cpio归档文件,可以内置在内核或作为独立文件
  3. rootfs的概念 :有两个rootfs概念需要区分
    • 内核创建的空rootfs(临时容器)
    • 我们制作的真正rootfs(永久系统)
  4. init程序的区别:initramfs的/init是临时助手,rootfs的/sbin/init是系统管家
  5. 制作方法:多种工具可供选择,从手工到自动化
  6. 数据传递:initramfs内容不会复制到rootfs,会被删除释放
  7. 方案选择:根据系统复杂度选择单镜像或双镜像方案
  8. 归档vs文件系统:归档文件需要解压,文件系统可以挂载
  9. 为什么需要双镜像:内核创建的rootfs是空的,需要我们制作实际内容

这套机制是Linux系统启动的核心,理解它对于嵌入式开发、系统优化和故障排查都至关重要。

相关推荐
zzzsde2 小时前
【Linux】库的制作与使用(1):库的概念及动静态库
linux·运维·服务器
2601_949814692 小时前
ubuntu 安装 Redis
linux·redis·ubuntu
薛定谔的悦2 小时前
站控显示下级从控EMS的版本信息开发(设计多线程和TCP通讯)
linux·网络·数据库·网络协议·tcp/ip·ems
看海的四叔2 小时前
【Linux】命令行常规操作全攻略:入门+实战+速查
linux·运维·github·命令行·batch命令
RrEeSsEeTt2 小时前
【HackTheBox】- BoardLight 靶机学习
linux·学习·网络安全·渗透测试·kali·红队·hackthebox
ruiang2 小时前
如何在 Ubuntu 22.04 上安装 MySQL
linux·mysql·ubuntu
ZzzZZzzzZZZzzzz…2 小时前
MySQL备份还原方法1---mysqldump
linux·运维·数据库·mysql·还原备份
果果燕2 小时前
ARM嵌入式学习(四)--- C语言应用:led、beep、key
linux·运维·算法
落羽的落羽3 小时前
【Linux系统】入门线程:线程介绍与线程控制
linux·服务器·c++·人工智能·stm32·单片机·机器学习