4. 构建最小 initramfs:使用 BusyBox 手动构建一个可用的最小 initramfs
在理解了内核参数传递机制之后,我们需要一个可用的 initramfs 来承载根文件系统并执行初始化逻辑。BusyBox 是一个集成了数百个精简版 Unix 工具的单二进制文件,非常适合构建最小化的 initramfs。本节将带领你从零开始,手动构建一个包含 BusyBox 的最小 initramfs,并验证其能够成功挂载根文件系统并启动 /init 进程。
4.1 构建流程概览
构建最小 initramfs 的核心流程分为四个阶段:准备目录结构、编译 BusyBox、编写初始化脚本、打包为 cpio 归档。下图展示了完整的构建流水线:
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/ |
附加工具和库 | 可选 |
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 等)。
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 的完整目录结构和关键文件关系:
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配置。