3. 根文件系统构建与精简:BusyBox vs Yocto vs Buildroot对比、最小根文件系统构建、只读文件系统(EROFS/SquashFS)的应用、OverlayFS实现OTA升级
根文件系统,说白了就是 Linux 启动后挂载的第一个文件系统。它里面装着 init 程序、shell、库文件、配置文件,还有你所有应用依赖的运行时环境。我见过不少团队,内核调得飞起,根文件系统却塞了一堆用不上的东西,结果启动慢、占空间大、还容易出安全漏洞。这一章,我们就来聊聊怎么把根文件系统做小、做稳、做可靠。
3.1 三大构建工具:BusyBox vs Yocto vs Buildroot
选工具之前,先想清楚你的项目到底要什么。是快速出原型?还是长期维护的产品?是单板机还是复杂 SoC?我个人的经验是:没有最好的工具,只有最合适的工具。
| 特性 | BusyBox | Buildroot | Yocto |
|---|---|---|---|
| 定位 | 单一二进制,替代 GNU 工具集 | 自动化构建嵌入式 Linux 系统 | 完整发行版构建框架 |
| 学习曲线 | 低(配置菜单简单) | 中(Kconfig + make) | 高(BitBake + 多层架构) |
| 构建速度 | 极快(几分钟) | 快(几十分钟) | 慢(首次数小时) |
| 定制粒度 | 粗(只能开关功能) | 中(包级别选择) | 细(源码级 patch) |
| 包管理 | 无 | 无(静态编译为主) | 有(RPM/DEB/IPK) |
| 典型场景 | 资源极度受限、快速原型 | 中等复杂度、量产产品 | 大型复杂系统、长期维护 |
3.2 最小根文件系统构建
什么是最小根文件系统?就是能让内核启动到 shell 的最小集合。我当年第一次做这个,以为要放一堆东西进去,结果发现其实只需要 4 样东西:init 程序、shell、C 库、设备节点。
下面是我常用的最小目录结构:
rootfs/
├── bin
│ ├── busybox # 单二进制,包含 sh、ls、cp 等
│ └── sh -> busybox
├── dev
│ ├── console # 控制台设备
│ ├── null # 空设备
│ └── ttyS0 # 串口设备
├── etc
│ ├── inittab # BusyBox init 配置
│ └── fstab # 挂载表
├── lib
│ ├── ld-linux.so.3 # 动态链接器
│ └── libc.so.6 # C 库
├── proc # 挂载点
├── sbin
│ └── init -> ../bin/busybox
├── sys # 挂载点
└── tmp # 临时文件
构建步骤其实不复杂:
- 编译 BusyBox:选静态编译,省去动态库依赖。配置时只勾选你需要的 applet,比如 init、sh、mount、ls、cp、cat、echo。
- 准备 C 库:如果不用静态编译,至少把 libc.so.6 和 ld-linux.so.3 拷进去。我建议用 musl libc,体积比 glibc 小一半以上。
- 创建设备节点:用
mknod命令创建 console、null、zero 等基本设备。 - 配置 init:inittab 文件里写上
::sysinit:/etc/init.d/rcS和::askfirst:/bin/sh。
3.3 只读文件系统:EROFS 与 SquashFS
工业设备最怕什么?突然断电导致文件系统损坏。只读文件系统就是来解决这个问题的。你想想看,系统分区只读,怎么断电都不会坏,多省心。
目前主流的选择有两个:SquashFS 和 EROFS。
| 特性 | SquashFS | EROFS |
|---|---|---|
| 内核支持 | Linux 2.6+(非常成熟) | Linux 5.4+(较新) |
| 压缩算法 | gzip/lzma/lzo/zstd | lz4/lzma/zstd |
| 随机读取性能 | 中等 | 优秀(比 SquashFS 快 20-50%) |
| 压缩率 | 高(gzip 默认) | 中(lz4 更快但压缩率略低) |
| 适用场景 | 传统嵌入式、兼容性优先 | 高性能、低延迟场景 |
我个人更倾向 EROFS,尤其是跑在 NAND Flash 或 eMMC 上的设备。为什么?因为 EROFS 的 原地解压 特性,读文件时不需要先解压到内存再返回,延迟更低。我曾经在一个工控项目里,把 SquashFS 换成 EROFS 后,应用启动时间缩短了 30%。
构建 EROFS 镜像很简单:
# 安装 erofs-utils
sudo apt install erofs-utils
# 制作 EROFS 镜像(lz4 压缩)
mkfs.erofs -zlz4hc rootfs.erofs rootfs/
# 烧录到分区后,挂载
mount -t erofs /dev/mmcblk0p1 /mnt/rootfs
CONFIG_EROFS_FS 和对应的压缩算法支持。如果你的内核版本低于 5.4,老老实实用 SquashFS 吧,别折腾。
3.4 OverlayFS 实现 OTA 升级
只读文件系统虽然稳,但没法直接升级。怎么办?用 OverlayFS 把只读层和可写层叠起来。系统启动时,只读层是基础,可写层放修改和新增的文件。升级时,只需要替换只读层,可写层保留用户数据。
这是我常用的分区布局:
Flash 分区结构:
+------------------+ <- mtdblock0 (Bootloader)
+------------------+ <- mtdblock1 (Kernel)
+------------------+ <- mtdblock2 (Rootfs A - 只读 EROFS)
+------------------+ <- mtdblock3 (Rootfs B - 只读 EROFS)
+------------------+ <- mtdblock4 (Overlay - 可读写)
+------------------+ <- mtdblock5 (Data - 可读写)
双分区(A/B)加 OverlayFS 的升级流程:
- 当前运行 A 分区,Overlay 挂载在
/overlay,所有修改写在这里。 - 下载新固件,写入 B 分区(后台进行,不影响运行)。
- 校验通过后,设置 bootloader 下次启动从 B 分区启动。
- 重启,系统从 B 分区启动,Overlay 继续挂载。
- 如果启动失败,bootloader 回退到 A 分区,保证设备不砖。
OverlayFS 的挂载命令:
# 假设当前 rootfs 在 /dev/mmcblk0p2(只读)
mount -t erofs /dev/mmcblk0p2 /mnt/rootfs
# 挂载 Overlay
mount -t overlay overlay \
-o lowerdir=/mnt/rootfs,upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work \
/mnt/merged
/var/log、/tmp 这些频繁写入的目录单独挂载到 tmpfs 上,问题就解决了。记住:Overlay 只放配置修改和用户数据,别放日志和缓存。
3.5 本章知识体系
下面这张图总结了根文件系统构建与精简的核心逻辑:
从这张图可以看得很清楚:根文件系统的工作不是孤立的。你先选好构建工具,做出最小系统,然后用只读文件系统把它固化,最后用 OverlayFS 解决升级问题。每一步都环环相扣。