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)
典型场景 资源极度受限、快速原型 中等复杂度、量产产品 大型复杂系统、长期维护
我的建议: 如果你做的是 32MB Flash、64MB RAM 的 IoT 设备,BusyBox + 手动构建是最实在的。如果是带网络、图形、多进程的工控板,Buildroot 能帮你省大量时间。至于 Yocto,嗯,它适合那种需要维护 3 年以上、有多个硬件变体的产品线。

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                  # 临时文件

构建步骤其实不复杂:

  1. 编译 BusyBox:选静态编译,省去动态库依赖。配置时只勾选你需要的 applet,比如 init、sh、mount、ls、cp、cat、echo。
  2. 准备 C 库:如果不用静态编译,至少把 libc.so.6 和 ld-linux.so.3 拷进去。我建议用 musl libc,体积比 glibc 小一半以上。
  3. 创建设备节点:用 mknod 命令创建 console、null、zero 等基本设备。
  4. 配置 init:inittab 文件里写上 ::sysinit:/etc/init.d/rcS 和 ::askfirst:/bin/sh。
关键指标: 一个完整的最小根文件系统,包含 BusyBox(静态编译)+ musl libc + 基本设备节点,总大小可以控制在 1.5MB 以内。我曾在 4MB Flash 的板子上跑过,启动时间不到 2 秒。

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
注意: EROFS 需要内核开启 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 的升级流程:

  1. 当前运行 A 分区,Overlay 挂载在 /overlay,所有修改写在这里。
  2. 下载新固件,写入 B 分区(后台进行,不影响运行)。
  3. 校验通过后,设置 bootloader 下次启动从 B 分区启动。
  4. 重启,系统从 B 分区启动,Overlay 继续挂载。
  5. 如果启动失败,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
避坑指南: 我曾经在 Overlay 的 upperdir 里放太多临时文件,导致 Flash 写寿命提前耗尽。后来我把 /var/log、/tmp 这些频繁写入的目录单独挂载到 tmpfs 上,问题就解决了。记住:Overlay 只放配置修改和用户数据,别放日志和缓存。

3.5 本章知识体系

下面这张图总结了根文件系统构建与精简的核心逻辑:

根文件系统构建与精简知识体系 根文件系统构建与精简知识体系 构建工具选择 最小系统构建 只读文件系统 BusyBox Buildroot Yocto init + shell + libc SquashFS EROFS OverlayFS + A/B 分区 OTA 升级 只读层 + 可写层叠加 (配置修改不丢失) 双分区冗余启动 (升级失败自动回退) tmpfs 分离高频写入 (延长 Flash 寿命) 核心原则:只读保稳定,Overlay 保灵活,双分区保可靠 构建工具选型 → 最小系统裁剪 → 只读文件系统固化 → OverlayFS 实现 OTA

从这张图可以看得很清楚:根文件系统的工作不是孤立的。你先选好构建工具,做出最小系统,然后用只读文件系统把它固化,最后用 OverlayFS 解决升级问题。每一步都环环相扣。