3. 构建最小根文件系统
根文件系统是内核启动后挂载的第一个文件系统,它提供了 /sbin/init、Shell、基础命令以及必要的设备节点。在 QEMU 调试场景中,我们通常使用两种形态:initramfs(内存中的临时根文件系统)和 rootfs.ext4(磁盘镜像中的持久根文件系统)。本章将使用 BusyBox 构建这两种形态,并配置内核启动参数使其正确挂载。
核心目标: 使用 BusyBox 编译出最小化的用户态工具集,打包为 initramfs 或写入 ext4 镜像,最终通过 QEMU 启动并进入 Shell。
3.1 构建流程总览
下图展示了从 BusyBox 源码到 QEMU 启动的完整构建与部署链路:
3.2 使用 BusyBox 构建用户态工具集
3.2.1 下载与配置
从 busybox.net 获取稳定版本(本文以 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 menuconfig 进行配置,关键选项如下表:
| 配置路径 | 推荐值 | 说明 |
|---|---|---|
| Settings → Build static binary (no shared libs) | 选中 | 静态编译,避免依赖动态库,简化部署 |
| Settings → Cross compiler prefix | arm-linux-gnueabi- | 交叉编译时指定,本地编译留空 |
| Settings → Destination path for 'make install' | ./_install | 安装到本地 _install 目录 |
| Linux Module Utilities → Simplified modutils | 选中 | 支持内核模块加载(调试驱动时必需) |
调试建议: 静态编译的 BusyBox 二进制体积约 800KB–1.2MB,虽然比动态版大,但可以避免在 initramfs 中复制 libc,显著降低根文件系统的构建复杂度。
3.2.2 编译与安装
# 本地编译(x86_64 调试用)
make -j$(nproc)
make install
# 交叉编译(ARM 目标)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j$(nproc)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- install
安装完成后,_install 目录结构如下:
_install/
├── bin/
│ ├── sh -> busybox
│ ├── ls -> busybox
│ ├── mount -> busybox
│ └── ... (所有命令均为指向 busybox 的符号链接)
├── sbin/
│ └── init -> ../bin/busybox
├── usr/
│ ├── bin/
│ └── sbin/
└── linuxrc -> bin/busybox
3.3 构建 initramfs
initramfs 是一个 cpio 归档,内核在初始化阶段将其解压到 tmpfs 中作为临时根文件系统。构建步骤如下:
3.3.1 创建必要目录与设备节点
cd _install
mkdir -p proc sys dev etc/init.d
# 创建控制台设备节点(必须,否则无法进入 Shell)
sudo mknod dev/console c 5 1
sudo mknod dev/null c 1 3
3.3.2 编写 init 脚本
创建 etc/init.d/rcS 作为启动脚本:
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
# 打印内核版本信息
cat /proc/version
# 启动 Shell
exec /bin/sh
赋予执行权限:chmod +x etc/init.d/rcS
创建 etc/inittab(BusyBox init 默认读取):
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
3.3.3 打包为 cpio 归档
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
生成的 initramfs.cpio.gz 可直接作为内核的 initramfs 源。
3.4 制作 rootfs.ext4 磁盘镜像
当需要持久化存储或测试块设备驱动时,使用 ext4 镜像更合适。
3.4.1 创建空白镜像文件
dd if=/dev/zero of=rootfs.ext4 bs=1M count=64
mkfs.ext4 -F rootfs.ext4
参数说明:
bs=1M count=64:创建 64MB 的镜像文件,适合最小系统-F:强制格式化,忽略非块设备警告
3.4.2 挂载并复制文件
mkdir -p /mnt/rootfs
sudo mount -o loop rootfs.ext4 /mnt/rootfs
sudo cp -a _install/* /mnt/rootfs/
# 创建必要目录
sudo mkdir -p /mnt/rootfs/{proc,sys,dev,etc/init.d}
# 创建设备节点
sudo mknod /mnt/rootfs/dev/console c 5 1
sudo mknod /mnt/rootfs/dev/null c 1 3
# 复制 init 脚本
sudo cp etc/init.d/rcS /mnt/rootfs/etc/init.d/
sudo chmod +x /mnt/rootfs/etc/init.d/rcS
sudo umount /mnt/rootfs
3.5 内核启动参数设置
内核通过 initrd 或 root= 参数指定根文件系统来源。下表总结了两种形态的 QEMU 启动参数:
| 根文件系统形态 | 内核启动参数 | QEMU 命令行示例 |
|---|---|---|
| initramfs | rdinit=/sbin/init(默认即可,无需 root=) |
|
| rootfs.ext4 | root=/dev/vda rwrootfstype=ext4 |
|
常见陷阱:
- 使用 initramfs 时,如果内核没有内嵌 initramfs,必须通过
-initrd参数显式指定 cpio 归档。 - 使用 ext4 镜像时,
root=/dev/vda对应 virtio 块设备;若使用-hda则设备名为/dev/sda。 - 忘记创建
/dev/console会导致内核 panic,无法进入 Shell。
3.6 启动验证与常见问题
3.6.1 验证步骤
- 启动 QEMU 后观察内核日志,确认
VFS: Mounted root (ext4 filesystem) on device 254:0.或Unpacking initramfs...信息。 - 进入 Shell 后执行
ls /bin确认 BusyBox 命令可用。 - 执行
cat /proc/mounts查看已挂载的文件系统。
3.6.2 常见异常与对策
| 异常现象 | 根因 | 解决方案 |
|---|---|---|
| Kernel panic - not syncing: VFS: Unable to mount root fs | 内核找不到根文件系统 | 检查 -initrd 路径或 root= 设备名是否正确 |
| Failed to execute /sbin/init (error -8) | init 文件格式错误或缺少执行权限 | 确认 BusyBox 静态编译,chmod +x _install/sbin/init |
| sh: can't access tty; job control turned off | 缺少 /dev/console 或 console 参数 | 添加 mknod dev/console c 5 1,QEMU 加 -serial stdio |
| init: must be run on PID 1 (error -1) | 内核参数 init= 指定了非 init 程序 | 使用 rdinit=/sbin/init 或 init=/sbin/init |
3.7 两种形态的对比与选型
选型建议:
- 调试内核早期启动、内存管理、进程调度 → 使用 initramfs,减少变量。
- 调试块设备驱动、文件系统、存储栈 → 使用 rootfs.ext4,模拟真实存储路径。
- 生产环境最小系统 → 通常将 initramfs 作为过渡,最终挂载磁盘上的 rootfs。