4. 文件系统裁剪:BusyBox构建最小根文件系统、glibc vs musl vs uclibc选择、只读文件系统(SquashFS)

文件系统裁剪,说白了就是给嵌入式设备「瘦身」。你想想看,一个路由器或者IoT传感器,Flash空间可能就4MB、8MB。把完整的Ubuntu塞进去?门儿都没有。我们需要一个最小化的根文件系统,只保留系统启动和运行必需的东西。

这一章,我带你走一遍我这些年积累下来的实战经验。从BusyBox构建最小根文件系统开始,到C库的三选一难题,最后聊聊只读文件系统SquashFS。嗯,都是硬骨头,但啃下来之后,你对嵌入式系统的理解会上一个台阶。

核心目标:构建一个可启动、功能完整、体积最小化的根文件系统。通常目标是把根文件系统控制在1-2MB以内(不含应用程序)。

文件系统裁剪知识体系 最小根文件系统 BusyBox构建 C库三选一 只读文件系统 编译配置 安装部署 glibc musl/uclibc SquashFS制作 三者配合:BusyBox提供命令 → C库提供运行时 → SquashFS打包成只读镜像

4.1 BusyBox:瑞士军刀式的工具集

BusyBox是什么?它把200多个常用的Linux命令打包成一个二进制文件。ls、cp、mv、ps、ifconfig……全在里面。我习惯叫它「嵌入式系统的瑞士军刀」。一个完整的BusyBox二进制,静态编译后也就1MB左右。

构建BusyBox根文件系统,我一般分三步走:

  1. 下载源码并配置:从busybox.net下载最新稳定版,运行make menuconfig。
  2. 编译安装:设置交叉编译工具链,然后make && make install。
  3. 补充目录结构:手动创建/dev、/proc、/sys、/etc等目录。

我的习惯:配置BusyBox时,我会把Settings → Build static binary (no shared libs)选上。这样生成的二进制不依赖外部动态库,部署起来省心很多。不过代价是体积会大一些,大概多200KB。

下面是一个典型的构建脚本片段:

#!/bin/bash
# 交叉编译BusyBox
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm

make defconfig
# 手动修改配置:静态编译 + 精简命令集
make -j4
make install CONFIG_PREFIX=./_install

# 补充根文件系统目录
cd _install
mkdir -p dev proc sys etc/init.d tmp var/log
# 创建关键设备节点
sudo mknod dev/console c 5 1
sudo mknod dev/null c 1 3

嗯,这里要注意:dev/console和dev/null这两个设备节点是必须的。没有它们,内核启动时会在挂载根文件系统后直接panic。我曾经在这个坑里爬了整整一个下午。

4.2 C库三选一:glibc vs musl vs uclibc

C库是根文件系统的「心脏」。所有应用程序都依赖它。选错了,后面全白干。我整理了一个对比表,你一看就明白:

特性 glibc musl uclibc-ng
体积 ~1.5MB(动态库) ~400KB ~300KB
兼容性 最好,POSIX标准全覆盖 良好,大部分Linux应用可用 一般,部分高级API缺失
性能 最优,经过数十年优化 良好,接近glibc 一般,部分场景有损耗
适用场景 性能敏感、兼容性要求高 嵌入式、容器、安全敏感 极端资源受限(<4MB Flash)
调试支持 完善(gdb、valgrind等) 中等 较弱

我个人习惯这样选:

  • 产品原型阶段:用glibc。调试工具支持好,遇到问题容易定位。
  • 量产阶段,Flash > 8MB:继续用glibc。没必要为了省几百KB给自己找麻烦。
  • Flash < 4MB:上uclibc-ng。但要做好心理准备,有些第三方库可能编译不过。
  • 安全敏感场景:musl。它的代码质量高,安全审计做得比glibc好。

避坑指南:我曾经在一个项目里,从glibc切换到uclibc,结果发现pthread_cancel()行为不一致,导致一个网络服务在压力测试下频繁崩溃。花了三天才定位到问题。所以,切换C库后,一定要做完整的回归测试。

4.3 只读文件系统:SquashFS

嵌入式设备为什么要用只读文件系统?两个原因:一是防篡改,二是省空间。SquashFS是Linux内核原生支持的只读压缩文件系统,压缩率通常能达到2:1到3:1。

制作SquashFS镜像的命令很简单:

# 安装工具
sudo apt-get install squashfs-tools

# 制作镜像
mksquashfs ./rootfs_root/ rootfs.squashfs \
  -comp xz \
  -b 131072 \
  -noappend

# 查看镜像信息
unsquashfs -s rootfs.squashfs

这里我解释一下参数:

  • -comp xz:使用XZ压缩算法,压缩率最高,但解压速度稍慢。
  • -b 131072:块大小设为128KB。块越大压缩率越高,但随机读取性能会下降。
  • -noappend:覆盖已有镜像,避免误操作。

内核配置里需要开启SquashFS支持:

File systems → Miscellaneous filesystems → <*> SquashFS 4.0
  [*] Squashfs XZ compression
  [*] Squashfs LZ4 compression

我的经验:对于大多数嵌入式设备,我推荐用LZ4压缩。虽然压缩率比XZ低10-15%,但解压速度快了3-5倍。系统启动时,解压根文件系统的时间能缩短一半以上。启动速度在嵌入式领域可是硬指标。

实际部署时,我通常把根文件系统分成两部分:

  • SquashFS只读分区:存放系统程序、库文件、配置文件模板。
  • 可写overlay分区:通常是JFFS2或UBIFS,存放日志、用户配置、运行时数据。

内核启动参数里这样配置:

root=/dev/mtdblock2 rootfstype=squashfs ro

配合overlay文件系统(OverlayFS),就能实现「只读根文件系统 + 可写数据分区」的经典架构。嗯,这个组合我在至少五个量产项目里用过,稳定得很。