4. 最小 Rootfs 构建:从零搭起文件系统骨架
好,咱们进入实战环节。前面几章讲了裁剪思路和工具链,这一章我们亲手搭一个最小的 rootfs。说白了,就是给内核准备一个能跑起来的「家」——有目录、有工具、有初始化进程。
我记得刚入行那会儿,总觉得 rootfs 很神秘,以为得用发行版那一套。后来被一个老工程师点醒:嵌入式系统里,rootfs 可以小到只有几个目录和一个 busybox。嗯,今天我们就来干这件事。
4.1 基础目录结构:五脏俱全
Linux 文件系统有约定俗成的目录布局,叫 FHS(Filesystem Hierarchy Standard)。但嵌入式场景下,我们不需要完全遵守,只保留最核心的即可。
我个人习惯的最小目录集是这样的:
rootfs/
├── bin # 用户命令(ls, cp, sh 等)
├── sbin # 系统管理命令(init, mount, ifconfig 等)
├── etc # 配置文件
├── dev # 设备节点
├── proc # 进程文件系统挂载点
├── sys # sysfs 挂载点
├── tmp # 临时文件
├── usr
│ ├── bin
│ └── sbin
└── lib # 动态库(如果 busybox 是动态链接)
创建命令很简单:
mkdir -p rootfs/{bin,sbin,etc,dev,proc,sys,tmp,usr/{bin,sbin},lib}
这里有个细节:/usr/bin 和 /usr/sbin 在最小系统中其实可以省略,因为 busybox 默认装在 /bin 和 /sbin。但我建议保留,方便后续扩展。
4.2 拷贝 Busybox 二进制:一个二进制搞定一切
Busybox 被称为「嵌入式瑞士军刀」。它把上百个 Linux 命令打包成一个二进制文件,通过符号链接来区分行为。
编译好 busybox 后(上一章讲过),你会得到一个 busybox 可执行文件。把它拷贝到 rootfs 的 /bin 下:
cp busybox rootfs/bin/
chmod 755 rootfs/bin/busybox
然后创建符号链接。比如 ls 命令,其实就是指向 busybox 的软链接:
ln -s /bin/busybox rootfs/bin/ls
ln -s /bin/busybox rootfs/bin/cp
ln -s /bin/busybox rootfs/bin/mv
# ... 其他命令同理
手动一个个创建太麻烦。Busybox 提供了自动安装功能:
cd rootfs
/bin/busybox --install -s
这条命令会在当前目录的 /bin、/sbin、/usr/bin、/usr/sbin 下自动创建所有符号链接。
/lib 下。用 ldd busybox 查看依赖,比如 libc.so、ld-linux.so 等。我曾经踩过这个坑——系统启动后 busybox 报 "No such file or directory",其实就是缺库。
检查依赖的命令:
ldd rootfs/bin/busybox
输出类似:
linux-vdso.so.1 (0x00007fff...)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)
ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007f...)
把这些库拷贝到 rootfs 的 /lib 下:
cp /lib/x86_64-linux-gnu/libc.so.6 rootfs/lib/
cp /lib64/ld-linux-x86-64.so.2 rootfs/lib/
4.3 配置 init 进程:系统启动的起点
内核启动完成后,会尝试执行第一个用户空间程序——init。默认路径是 /sbin/init,如果找不到,会依次尝试 /bin/init、/bin/sh。
Busybox 本身就提供了 init 功能。我们只需要创建符号链接:
ln -s /bin/busybox rootfs/sbin/init
然后编写 /etc/inittab 文件,告诉 init 进程该做什么。一个最简的 inittab 如下:
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
解释一下:
::sysinit:系统初始化时执行/etc/init.d/rcS脚本::askfirst:在控制台启动一个 shell,启动前询问用户
接着创建 /etc/init.d/rcS 脚本:
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
echo "Welcome to Minimal Rootfs!"
别忘了加执行权限:
chmod 755 rootfs/etc/init.d/rcS
exec /bin/sh 兜底,确保 shell 不会意外退出。
4.4 设备节点:让内核与用户空间对话
/dev 目录下需要最基本的设备节点。最精简的情况下,你只需要:
console:控制台设备,内核输出信息用null:空设备,丢弃不需要的输出
创建方式:
mknod rootfs/dev/console c 5 1
mknod rootfs/dev/null c 1 3
参数说明:c 表示字符设备,后面的数字是主设备号和次设备号。
如果你后续要挂载 devtmpfs(内核自动管理设备节点),那 /dev 甚至可以只留一个 console。但为了保险,我习惯把 console 和 null 都手动创建好。
4.5 整体流程梳理
下面这张图展示了最小 rootfs 的构建流程和启动逻辑:
4.6 验证与测试
构建完成后,可以用 QEMU 快速验证:
qemu-system-x86_64 -kernel bzImage -initrd rootfs.cpio.gz -append "console=ttyS0" -nographic
如果一切正常,你会看到内核启动日志,然后进入 shell 提示符。输入 ls /bin,应该能看到 busybox 和一堆符号链接。
/proc,结果 ps 命令报错。还有一次把 inittab 的格式写错了——每行必须用 :: 分隔,少一个冒号都不行。启动后直接 panic,查了半天才发现。
4.7 本章小结
最小 rootfs 的构建,本质上就是三件事:
- 目录结构:/bin、/sbin、/etc、/dev、/proc、/sys、/tmp、/lib
- Busybox:一个二进制 + 符号链接 + 动态库(如果需要)
- init 配置:/sbin/init → /etc/inittab → /etc/init.d/rcS