4. 构建最小 initramfs:使用 BusyBox 手动构建一个可用的最小 initramfs

在理解了内核参数传递机制之后,我们需要一个可用的 initramfs 来承载根文件系统并执行初始化逻辑。BusyBox 是一个集成了数百个精简版 Unix 工具的单二进制文件,非常适合构建最小化的 initramfs。本节将带领你从零开始,手动构建一个包含 BusyBox 的最小 initramfs,并验证其能够成功挂载根文件系统并启动 /init 进程。

4.1 构建流程概览

构建最小 initramfs 的核心流程分为四个阶段:准备目录结构、编译 BusyBox、编写初始化脚本、打包为 cpio 归档。下图展示了完整的构建流水线:

最小 initramfs 构建流程 1. 创建目录骨架 2. 编译 BusyBox 3. 编写 /init 脚本 4. 打包 cpio bin/ dev/ etc/ lib/ proc/ sys/ usr/ sbin/ make defconfig make menuconfig make && make install #!/bin/sh mount -t proc exec switch_root find . | cpio -H newc -o > initramfs.cpio initramfs.cpio.gz

4.2 准备目录骨架

initramfs 本质上是一个小型的根文件系统,需要包含必要的目录结构。创建一个临时工作目录,并建立以下子目录:

mkdir -p /tmp/my_initramfs
cd /tmp/my_initramfs
mkdir -p bin dev etc lib lib64 proc sbin sys usr/bin usr/sbin usr/lib
目录 用途 必需性
bin/ 存放 BusyBox 二进制及符号链接 必需
dev/ 设备节点(console, null, tty 等) 必需
etc/ 配置文件(inittab, fstab 等) 可选
lib/ lib64/ 动态链接库(glibc 或 musl) 动态链接时必需
proc/ 挂载点:proc 文件系统 必需
sys/ 挂载点:sysfs 文件系统 必需
sbin/ 系统管理工具(init, mount 等) 必需
usr/ 附加工具和库 可选
提示:如果使用静态编译的 BusyBox,可以省略 lib/ 和 lib64/ 目录,从而进一步减小 initramfs 体积。静态编译是生产环境中的常见做法。

4.3 编译并安装 BusyBox

从 busybox.net 下载源码,或使用系统包管理器安装。以下步骤演示从源码编译并安装到 initramfs 目录:

# 下载并解压(以 1.36.1 为例)
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 menuconfig  # 进入 Settings → Build static binary (no shared libs) → 选中
# 或者直接修改 .config:
sed -i 's/# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config

# 编译并安装到 initramfs 目录
make -j$(nproc)
make CONFIG_PREFIX=/tmp/my_initramfs install

安装完成后,/tmp/my_initramfs/bin/ 下会出现 busybox 二进制文件,以及指向它的符号链接(如 sh、mount、ls 等)。

注意:如果使用动态编译,必须将 BusyBox 依赖的共享库(如 libc.so.6、ld-linux-x86-64.so.2)复制到 lib/ 或 lib64/ 目录。可以使用 ldd bin/busybox 查看依赖列表。

4.4 创建设备节点

initramfs 启动时需要基本的设备节点。使用 mknod 命令手动创建:

cd /tmp/my_initramfs
sudo mknod -m 622 dev/console c 5 1
sudo mknod -m 666 dev/null c 1 3
sudo mknod -m 666 dev/zero c 1 5
sudo mknod -m 444 dev/random c 1 8
sudo mknod -m 444 dev/urandom c 1 9
sudo mknod -m 666 dev/tty c 5 0
sudo mknod -m 666 dev/tty0 c 4 0
设备节点 主设备号 次设备号 权限 用途
dev/console 5 1 622 系统控制台,内核输出
dev/null 1 3 666 丢弃写入数据
dev/tty 5 0 666 当前终端
dev/tty0 4 0 666 当前虚拟控制台

4.5 编写 /init 脚本

/init 是 initramfs 的入口点,内核在挂载 initramfs 后执行的第一个用户空间程序。以下是一个最小化的 /init 脚本,它挂载必要的文件系统、创建 /dev 节点,然后切换到真正的根文件系统:

#!/bin/sh

# 挂载 proc 和 sysfs
mount -t proc none /proc
mount -t sysfs none /sys

# 挂载 devtmpfs 自动创建设备节点
mount -t devtmpfs none /dev

# 在 /dev 下创建关键节点(如果 devtmpfs 未自动创建)
mknod /dev/console c 5 1 2>/dev/null
mknod /dev/null c 1 3 2>/dev/null

# 打印内核引导参数
echo "Kernel command line: $(cat /proc/cmdline)"

# 解析 root= 参数,获取根设备
ROOT_DEVICE=""
for param in $(cat /proc/cmdline); do
    case "$param" in
        root=*)
            ROOT_DEVICE="${param#root=}"
            ;;
    esac
done

if [ -z "$ROOT_DEVICE" ]; then
    echo "ERROR: No root= parameter found!"
    exec /bin/sh
fi

# 挂载真正的根文件系统
echo "Mounting root device: $ROOT_DEVICE"
mount -o rw "$ROOT_DEVICE" /mnt/root 2>/dev/null || {
    echo "Failed to mount $ROOT_DEVICE"
    exec /bin/sh
}

# 清理并切换到真正的根
exec switch_root /mnt/root /sbin/init

赋予执行权限:

chmod +x /tmp/my_initramfs/init
关键点:switch_root 是 BusyBox 提供的工具,用于从 initramfs 切换到真正的根文件系统。它会将当前根目录移动到 /mnt/root,然后执行 /sbin/init。注意:switch_root 要求目标根文件系统必须包含 /sbin/init 或 /init。

4.6 打包为 cpio 归档

将整个 initramfs 目录树打包为 cpio 归档,并可选地使用 gzip 压缩:

cd /tmp/my_initramfs
find . -print0 | cpio --null -ov --format=newc 2>/dev/null | gzip -9 > /boot/initramfs-busybox.cpio.gz

验证归档内容:

zcat /boot/initramfs-busybox.cpio.gz | cpio -t 2>/dev/null | head -20

输出示例:

.
bin
bin/busybox
bin/sh
dev
dev/console
dev/null
init
proc
sys
...

4.7 测试启动

使用 QEMU 快速验证 initramfs 能否正常启动:

qemu-system-x86_64 \
    -kernel /boot/vmlinuz-$(uname -r) \
    -initrd /boot/initramfs-busybox.cpio.gz \
    -append "root=/dev/sda1 console=ttyS0" \
    -nographic

如果一切正常,你会看到内核启动日志,随后进入 BusyBox 的 shell 提示符(如果 /init 脚本中未找到根设备或挂载失败,会 fallback 到 shell)。

4.8 常见问题与调试

问题现象 可能原因 解决方案
Kernel panic - not syncing: No init found /init 不存在或不可执行 检查 /init 是否在 cpio 根目录,权限是否为 755
switch_root: not found BusyBox 未编译 switch_root 工具 在 menuconfig 中启用 CONFIG_SWITCH_ROOT
mount: can't find /dev/sda1 缺少设备驱动或 root= 参数错误 确认内核包含块设备驱动,检查 /proc/cmdline
BusyBox: can't load library 动态链接库缺失 使用静态编译,或复制所有依赖库到 lib/
调试技巧:在 /init 脚本开头添加 exec /bin/sh 可以进入交互式 shell,方便手动执行命令排查问题。使用 cat /proc/self/mountinfo 查看当前挂载状态。

4.9 最小 initramfs 结构总览

下图展示了构建完成后 initramfs 的完整目录结构和关键文件关系:

最小 initramfs 目录结构 / (root) bin/ dev/ etc/ lib/ proc/ sys/ busybox sh → busybox mount → busybox ... (符号链接) console null tty tty0 inittab (可选) libc.so.6 ld-linux.so (动态链接时) 挂载点 挂载点 /init (入口脚本) /init 是内核启动 initramfs 后执行的第一个用户空间程序,负责挂载根文件系统并调用 switch_root 所有 BusyBox 工具通过符号链接指向 bin/busybox 单一二进制文件

4.10 优化建议

  • 静态编译:减少对动态库的依赖,降低 initramfs 体积和启动失败风险。
  • 精简工具集:在 make menuconfig 中禁用不需要的 applet,只保留 mount、switch_root、sh、ls、cat 等核心工具。
  • 使用 musl libc:如果追求极致体积,可以使用 musl 替代 glibc 编译 BusyBox,生成的二进制更小。
  • 压缩方式:使用 gzip -9 或 xz 压缩 cpio 归档,减少内存占用。
  • 内核内置 initramfs:可以将 cpio 归档直接链接到内核镜像中,通过 CONFIG_INITRAMFS_SOURCE 配置。
生产环境参考:大多数 Linux 发行版(如 Ubuntu、Debian、RHEL)的 initramfs 体积在 10-50 MB 之间,而手动构建的最小 initramfs 可以控制在 1-3 MB(静态编译 + 精简工具集)。这为嵌入式系统和容器场景提供了极大的灵活性。