深入理解 initrd:从零手动构建 Linux 初始内存磁盘

深入理解 initrd:从零手动构建 Linux 初始内存磁盘

什么是 initrd?

在 Linux 系统启动过程中,initrd(Initial RAM Disk) 是一个临时的根文件系统,它在内核启动阶段被加载到内存中。initrd 包含了在挂载真实根文件系统之前所必需的核心驱动程序、工具和初始化脚本。

为什么需要 initrd?

1. 模块化内核的依赖

现代 Linux 内核采用模块化设计,很多硬件驱动程序都被编译为模块。如果根文件系统位于特殊的硬件上(如 SCSI 硬盘、RAID 阵列、LVM 卷等),而这些硬件的驱动是内核模块,就会出现”鸡生蛋蛋生鸡”的问题:

需要驱动程序才能访问根文件系统但驱动程序存储在根文件系统中

initrd 完美解决了这个矛盾,它在内核启动早期提供一个包含必要驱动的临时环境。

2. 复杂的根文件系统准备

有些情况下,挂载真正的根文件系统需要先执行一些准备工作:

解密加密的根文件系统配置网络以挂载 NFS 根文件系统组装 RAID 阵列或 LVM 逻辑卷

手动制作 initrd 映像的完整流程

下面我们详细解析制作 initrd 映像的每一步。

环境准备

首先确保你的系统安装了必要的工具:


# Ubuntu/Debian
sudo apt-get install util-linux e2fsprogs

# CentOS/RHEL
sudo yum install util-linux e2fsprogs

步骤 1:创建空白映像文件


dd if=/dev/zero of=./initrd.img count=2048 bs=1024

详细解释:


dd
:底层数据复制工具
if=/dev/zero
:输入文件,
/dev/zero
是一个特殊的虚拟设备,读取时会返回无限的零字节
of=./initrd.img
:输出文件,在当前目录创建 initrd.img
count=2048 bs=1024
:定义文件大小

bs=1024
:块大小为 1024 字节(1KB)
count=2048
:块数量为 2048总大小:2048 × 1KB = 2048KB = 2MB

验证创建结果:


ls -lh initrd.img
# 输出:-rw-r--r-- 1 user user 2.0M date initrd.img

file initrd.img
# 输出:initrd.img: data

步骤 2:创建文件系统


mke2fs -F -m0 initrd.img

参数详解:


mke2fs
:创建 ext2/ext3/ext4 文件系统的工具
-F
:强制创建。因为 mke2fs 通常期望在块设备上操作,而 initrd.img 是普通文件,需要强制标志
-m0
:为 root 用户保留 0% 的磁盘空间,最大化利用有限的 2MB 空间
initrd.img
:目标文件

默认创建的是 ext2 文件系统,这是因为它:

结构简单,占用空间小不需要日志功能,减少开销内核原生支持,无需额外模块

验证文件系统:


file initrd.img
# 输出:initrd.img: Linux rev 1.0 ext2 filesystem data

步骤 3:挂载映像文件


mkdir -p /mnt/initrd-temp
mount initrd.img /mnt/initrd-temp -o loop

关键技术:回环设备(Loop Device)


-o loop
:使用回环设备选项工作原理:Linux 内核允许将普通文件虚拟成块设备查看回环设备


losetup -a
# 输出:/dev/loop0: [] (/path/to/initrd.img)

验证挂载:


df -h /mnt/initrd-temp
# 输出:Filesystem Size Used Avail Use% Mounted on
#      /dev/loop0  2.0M 1.1M  796K  58% /mnt/initrd-temp

mount | grep initrd
# 输出:/dev/loop0 on /mnt/initrd-temp type ext2 (rw,relatime)

步骤 4:构建根文件系统内容

这是最关键的一步,我们需要创建 initrd 的目录结构和必要文件。

创建标准目录结构

# 创建基本目录
cd /mnt/initrd-temp
mkdir -p bin dev etc lib proc sys tmp usr/bin usr/sbin var/log

# 创建设备节点(关键!)
sudo mknod dev/console c 5 1    # 控制台设备
sudo mknod dev/null c 1 3       # 空设备
sudo mknod dev/zero c 1 5       # 零设备
sudo mknod dev/tty c 5 0        # 终端设备
sudo mknod dev/tty0 c 4 0       # 第一个虚拟终端
添加 BusyBox(瑞士军刀)

BusyBox 是一个集成了大量常用命令的单一可执行文件,非常适合 initrd。


# 拷贝 busybox 到 initrd
cp /bin/busybox bin/           # 假设系统已安装 busybox
# 或者从源码编译:
# wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
# tar -xf busybox-1.36.1.tar.bz2
# cd busybox-1.36.1
# make defconfig && make -j4
# cp busybox /mnt/initrd-temp/bin/

# 创建符号链接
cd /mnt/initrd-temp/bin
for cmd in sh ls mkdir mount umount cat echo ps; do
    ln -s busybox $cmd
done
创建初始化脚本

cat > init << 'EOF'
#!/bin/sh

# 早期初始化
echo "Initrd starting..."
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev

# 创建设备节点(如果 devtmpfs 不可用)
if [ ! -c /dev/console ]; then
    mknod /dev/console c 5 1
fi

# 设置环境
export PATH=/bin:/usr/bin

# 加载必要的内核模块
echo "Loading kernel modules..."
insmod /lib/modules/$(uname -r)/kernel/drivers/scsi/scsi_mod.ko
insmod /lib/modules/$(uname -r)/kernel/drivers/scsi/sd_mod.ko

# 尝试挂载真正的根文件系统
echo "Mounting real root filesystem..."
while [ ! -e /dev/sda1 ]; do
    echo "Waiting for root device..."
    sleep 1
done

mount -t ext4 /dev/sda1 /mnt/root

# 切换到真正的根文件系统
echo "Switching to real root..."
exec switch_root /mnt/root /sbin/init

# 如果切换失败,启动救援 shell
echo "Failed to switch root! Starting rescue shell..."
exec /bin/sh
EOF

chmod +x init
添加内核模块(如果需要)

# 拷贝必要的内核模块
mkdir -p lib/modules/$(uname -r)
cp -r /lib/modules/$(uname -r)/kernel/drivers/scsi lib/modules/$(uname -r)/kernel/drivers/

# 生成模块依赖关系
depmod -b /mnt/initrd-temp $(uname -r)

步骤 5:卸载并压缩


# 卸载文件系统
umount /mnt/initrd-temp

# 压缩以减小体积
gzip -9 initrd.img
# 生成 initrd.img.gz

压缩的重要性:

减少启动时从存储设备读取的数据量节省内存空间典型的压缩率可以达到 60-70%

现代 initrd 的替代方案

initramfs

现代 Linux 系统更多使用 initramfs,它与 initrd 的主要区别:

特性 initrd initramfs
实现方式 块设备映像文件 cpio 归档文件
内存使用 需要固定大小的内存 按需分配内存
访问方式 通过回环设备挂载 直接解压到 tmpfs
灵活性 较差 更好

使用 dracut 自动生成

现代发行版通常使用工具自动生成 initramfs:


# 为当前内核生成 initramfs
dracut /boot/initramfs-$(uname -r).img $(uname -r)

# 查看 initramfs 内容
lsinitrd /boot/initramfs-$(uname -r).img

# 生成精简版本(只包含必要模块)
dracut --omit-drivers "iscsi_target_mod" /boot/initramfs-minimal.img

在启动流程中的使用

配置 BootLoader

在 GRUB 配置中指定 initrd:


# /boot/grub/grub.cfg 或 /etc/grub.d/ 中的配置
menuentry 'Linux with Custom Initrd' {
    linux /boot/vmlinuz-$(uname -r) root=/dev/sda1 ro
    initrd /boot/initrd.img.gz
}

启动流程详解

BIOS/UEFI → 加载 BootLoaderBootLoader → 加载内核和 initrd 到内存内核 → 解压 initrd,执行
/init
脚本initrd → 加载驱动,挂载真正的根文件系统切换 → 执行
switch_root
切换到真实根文件系统systemd/init → 继续正常的启动过程

调试技巧

检查 initrd 内容


# 查看 initrd 文件类型
file initrd.img.gz

# 如果是 gzip 压缩的 cpio 归档
mkdir temp && cd temp
zcat ../initrd.img.gz | cpio -idmv

# 如果是传统 initrd
mount -o loop initrd.img /mnt/temp

在启动时调试

在内核命令行添加参数:


# 让 initrd 暂停等待调试
linux /boot/vmlinuz root=/dev/sda1 ro initrd_debug

# 在初始化脚本中暂停
linux /boot/vmlinuz root=/dev/sda1 ro break=premount

实际应用场景

1. 加密根文件系统


# 在 initrd 中添加加密工具
cp /sbin/cryptsetup bin/
cp $(ldd /sbin/cryptsetup | grep -o '/[^ ]*') lib/

# 修改 init 脚本添加解密逻辑
cat >> init << 'EOF'
echo "Unlocking encrypted root..."
cryptsetup luksOpen /dev/sda2 cryptroot
mount /dev/mapper/cryptroot /mnt/root
EOF

2. 网络根文件系统


# 添加网络工具和驱动
cp /sbin/ip bin/
cp /sbin/dhclient bin/

# 添加网络初始化
cat >> init << 'EOF'
echo "Setting up network..."
ip link set eth0 up
dhclient eth0
mount -t nfs 192.168.1.100:/nfs/root /mnt/root
EOF

总结

通过手动制作 initrd,我们深入理解了 Linux 启动过程的关键环节。虽然现代系统使用自动化工具有效地简化了这个过程,但掌握其原理对于系统调试、定制化开发和故障排查都具有重要意义。

关键要点:

initrd 解决了内核模块与根文件系统访问的依赖问题手动制作涉及创建映像、格式化、构建文件系统和压缩等步骤BusyBox 是构建轻量级 initrd 的理想工具现代系统倾向于使用 initramfs 替代传统的 initrd理解 initrd 工作原理有助于处理复杂的启动场景

掌握 initrd 的构建和使用,是深入理解 Linux 系统启动机制的重要一步,也是进行系统级开发和调试的必备技能。

© 版权声明

相关文章

暂无评论

none
暂无评论...