4、构建最小根文件系统:BusyBox编译、initramfs制作与根文件系统挂载
在QEMU + KGDB调试环境中,内核启动后必须挂载一个可用的根文件系统,否则内核会在Kernel panic - not syncing: VFS: Unable to mount root fs处崩溃。本章将构建一个基于BusyBox的最小根文件系统,并将其打包为initramfs,最终通过QEMU传递给内核。
4.1 根文件系统在调试中的作用
根文件系统(RootFS)是内核启动后挂载的第一个文件系统,它提供:
- init进程:内核启动用户空间的第一个程序(PID 1)。
- Shell环境:提供交互式调试命令(如
ls、cat、ps)。 - 设备节点:通过
/dev目录访问硬件。 - 调试工具:如
kgdb、gdb、strace等。
调试要点:在KGDB调试场景中,根文件系统不需要完整发行版,只需一个能启动Shell并加载内核模块的最小环境即可。这可以显著减少构建时间和内存占用。
4.2 BusyBox编译:打造瑞士军刀
BusyBox是一个集成了数百个常用Unix命令的单一可执行文件,被称为“嵌入式Linux的瑞士军刀”。
4.2.1 下载与配置
# 下载BusyBox源码(以1.36.1为例)
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xjf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
# 使用默认配置,然后进行定制
make defconfig
make menuconfig
在menuconfig中,必须确保以下选项被正确设置:
| 配置项 | 路径 | 推荐值 | 说明 |
|---|---|---|---|
| 静态编译 | Settings → Build static binary (no shared libs) | 选中 | 避免在initramfs中复制动态库,减小体积 |
| 交叉编译前缀 | Settings → Cross compiler prefix | arm-linux-gnueabi- |
根据目标架构设置(ARM为例) |
| 安装路径 | Settings → Destination path for 'make install' | ../rootfs |
指定根文件系统临时目录 |
| 调试符号 | Settings → Debugging symbols | 选中 | 保留符号便于调试BusyBox本身 |
4.2.2 编译与安装
# 编译(使用-jN加速)
make -j$(nproc)
# 安装到指定目录
make install
# 检查安装结果
ls -la ../rootfs/
# 应看到 bin/ sbin/ usr/ 等目录
关键输出:安装完成后,
../rootfs/bin/busybox 是核心可执行文件,其他命令(如ls、sh)都是指向它的符号链接。
4.3 构建initramfs目录结构
initramfs是一个基于cpio归档的临时根文件系统,内核在挂载真正的根文件系统前会先解压并执行其中的/init脚本。
4.3.1 创建必要目录
cd ../rootfs
mkdir -p dev proc sys etc/init.d lib/modules
4.3.2 创建设备节点
设备节点是内核与硬件交互的接口,initramfs中至少需要控制台和null设备:
# 创建控制台设备节点(主号5,次号1)
sudo mknod dev/console c 5 1
# 创建null设备节点(主号1,次号3)
sudo mknod dev/null c 1 3
# 设置权限
sudo chmod 666 dev/null
sudo chmod 600 dev/console
4.3.3 编写init脚本
/init是initramfs的入口点,内核启动后第一个执行的用户空间程序:
#!/bin/sh
# 挂载必要的虚拟文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
# 打印内核版本信息
cat /proc/version
# 启动交互式Shell
exec /bin/sh
赋予执行权限:
chmod +x init
常见错误:如果
/init脚本没有执行权限,内核会报错Failed to execute /init并进入Kernel panic。务必使用chmod +x设置可执行位。
4.4 打包initramfs
将整个根文件系统目录打包为cpio归档,并可选进行gzip压缩:
# 进入rootfs目录
cd ../rootfs
# 使用find + cpio打包
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
# 检查打包结果
ls -lh ../initramfs.cpio.gz
# 预期输出:约1~2MB(取决于BusyBox配置)
打包流程示意图:
4.5 QEMU启动验证
使用QEMU加载编译好的内核和initramfs,验证根文件系统是否正常工作:
# 启动QEMU(ARM架构示例)
qemu-system-arm \
-M virt \
-kernel ../linux/arch/arm/boot/zImage \
-initrd ../initramfs.cpio.gz \
-append "console=ttyAMA0,115200" \
-nographic
# 启动后应看到Shell提示符
# / # ls
# bin dev etc init proc root sbin sys usr
验证要点:
- 内核启动日志末尾应出现
VFS: Mounted root (initramfs filesystem)。 - Shell提示符出现后,输入
ls /bin应看到BusyBox提供的命令列表。 - 输入
cat /proc/meminfo可查看内存信息,验证/proc已挂载。
4.6 根文件系统挂载机制解析
内核挂载initramfs的完整流程如下:
- 内核解压阶段:内核启动早期,将
initramfs.cpio.gz从内存中解压到rootfs(一个基于内存的tmpfs)。 - VFS初始化:虚拟文件系统(VFS)注册rootfs文件系统类型。
- 挂载rootfs:内核调用
mount_root(),将rootfs挂载到根目录/。 - 执行/init:内核查找并执行
/init程序,如果不存在则尝试/sbin/init、/etc/init等。 - 用户空间接管:
/init脚本挂载/proc、/sys、/dev,最终启动Shell或init系统。
挂载流程示意图:
4.7 常见问题与调试技巧
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
Kernel panic - not syncing: VFS: Unable to mount root fs |
initramfs未正确加载或格式错误 | 检查QEMU命令行-initrd参数路径;确认cpio归档格式为newc |
Failed to execute /init |
/init脚本无执行权限或解释器错误 |
执行chmod +x init;确认脚本第一行为#!/bin/sh |
| Shell提示符出现但命令找不到 | BusyBox符号链接未正确创建 | 重新执行make install;检查bin/目录下是否有符号链接 |
/dev/null: No such device |
设备节点未创建或devtmpfs未挂载 | 在/init中添加mount -t devtmpfs none /dev |
调试技巧:如果initramfs启动失败,可以在内核命令行中添加
rdinit=/bin/sh直接启动Shell,跳过/init脚本的执行,从而隔离问题。
4.8 本章小结
通过本章实践,我们完成了:
- 使用BusyBox编译了一个静态链接的最小根文件系统。
- 构建了包含
/dev、/proc、/sys和/init脚本的initramfs目录结构。 - 使用cpio和gzip打包生成
initramfs.cpio.gz。 - 通过QEMU验证了根文件系统的正常挂载和Shell交互。