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配置)

打包流程示意图:

initramfs 打包流程 BusyBox 编译 make install 目录结构构建 dev/ proc/ sys/ etc/ init 脚本编写 mount + exec sh cpio 归档打包 find . | cpio -o -H newc gzip 压缩 gzip > initramfs.cpio.gz 输出:initramfs.cpio.gz

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的完整流程如下:

  1. 内核解压阶段:内核启动早期,将initramfs.cpio.gz从内存中解压到rootfs(一个基于内存的tmpfs)。
  2. VFS初始化:虚拟文件系统(VFS)注册rootfs文件系统类型。
  3. 挂载rootfs:内核调用mount_root(),将rootfs挂载到根目录/。
  4. 执行/init:内核查找并执行/init程序,如果不存在则尝试/sbin/init、/etc/init等。
  5. 用户空间接管:/init脚本挂载/proc、/sys、/dev,最终启动Shell或init系统。

挂载流程示意图:

内核挂载initramfs流程 内核解压 initramfs.cpio.gz VFS注册 rootfs类型 挂载rootfs mount_root() 执行/init run_init_process() 用户空间初始化 挂载proc/sys/dev → 启动Shell

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交互。