4、文件系统裁剪:BusyBox构建最小根文件系统、SquashFS与OverlayFS

车载嵌入式系统的存储空间,说白了就是「寸土寸金」。Flash芯片就那么几GB,既要放系统、又要放应用、还得给用户数据留空间。我早年做第一代车机时,就因为根文件系统大了20MB,不得不砍掉一个语音引擎——那种痛,至今记忆犹新。

这一章,咱们就聊聊怎么把根文件系统「瘦身」,以及怎么用SquashFS和OverlayFS来平衡只读保护与可写需求。你想想看,一个能稳定运行、又能OTA升级的系统,文件系统裁剪是基本功。

核心目标: 用BusyBox构建一个小于2MB的最小根文件系统,再通过SquashFS压缩固件,最后用OverlayFS实现「只读基座+可写层」的经典车载方案。

4.1 BusyBox:麻雀虽小,五脏俱全

BusyBox是什么?它把200多个Linux常用命令打包成一个二进制文件。你平时用的ls、cp、vi、mount,在BusyBox里都是同一个程序的符号链接。我习惯叫它「瑞士军刀」——功能齐全,体积却小得惊人。

4.1.1 编译BusyBox

从官网下载源码,配置时注意三点:

  • 静态编译:避免依赖动态库,减少运行时故障
  • 选择需要的命令:车载环境用不到telnetd、ftpget,果断关掉
  • 启用精简选项:去掉调试符号、禁用无用模块
# 下载并解压
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 menuconfig

# 关键配置项:
# -> Settings -> Build static binary (no shared libs) [*]
# -> Settings -> Strip unnecessary symbols [*]
# -> Networking Utilities -> 只保留 ifconfig, ping, route
# -> Linux System Utilities -> 保留 mount, umount, dmesg

# 编译
make -j$(nproc)
make install

编译完成后,_install目录里就是根文件系统的骨架。我测过,一个只包含核心命令的BusyBox二进制,体积大约在800KB到1.2MB之间。

我的经验: 别贪心。车载调试阶段,我建议保留vi、top、ps、df这几个命令。真到了产线,再砍掉也不迟。

4.1.2 构建最小根文件系统

有了BusyBox,还需要几个关键目录和文件:

# 创建目录结构
mkdir -p _install/{proc,sys,dev,etc,lib,usr,var,tmp,mnt}
chmod 1777 tmp

# 创建必要的设备节点
sudo mknod _install/dev/console c 5 1
sudo mknod _install/dev/null c 1 3
sudo mknod _install/dev/zero c 1 5

# 创建初始化脚本(inittab)
cat > _install/etc/inittab << 'EOF'
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
EOF

# 创建rcS启动脚本
mkdir -p _install/etc/init.d
cat > _install/etc/init.d/rcS << 'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
echo "Welcome to CarLinux Mini System"
EOF
chmod +x _install/etc/init.d/rcS

嗯,这里要注意:/dev/console和/dev/null是必须的,否则内核启动时会卡死在「No working init found」。

4.2 SquashFS:只读压缩,省空间保安全

SquashFS是一个只读的、压缩的文件系统。它特别适合存放固件——你想想看,系统分区一旦烧录进去,正常情况下不应该被修改。用SquashFS,既能压缩体积,又能防止意外篡改。

4.2.1 制作SquashFS镜像

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

# 将_install目录打包成只读镜像
mksquashfs _install/ rootfs.squashfs \
  -comp xz \
  -b 131072 \
  -noappend \
  -all-root

# 查看压缩效果
ls -lh rootfs.squashfs
# 输出示例:-rw-r--r-- 1 root root 1.8M rootfs.squashfs

我常用的压缩算法是xz,压缩率最高,但解压时CPU开销也大。车载场景下,如果CPU性能够(比如Cortex-A55以上),用xz没问题。如果是老平台,建议用gzip。

压缩算法 压缩率(典型值) 解压速度 推荐场景
gzip ~50% 快 低端CPU、启动速度敏感
lz4 ~40% 极快 需要频繁读取的日志分区
xz ~65% 慢 固件分区、存储空间极度紧张
zstd ~55% 较快 平衡场景(我目前最常用)
我曾经踩过的坑: 有一次我把SquashFS的块大小设成了64KB,结果系统启动时解压一个小配置文件都要读整个块,I/O延迟飙升。后来改成4KB块大小,问题解决。记住:块越小,随机读取越快,但压缩率会下降。

4.3 OverlayFS:只读基座 + 可写层

车载系统有个矛盾:根文件系统希望是只读的(防篡改、防损坏),但运行时又需要写/etc、/var、/tmp。OverlayFS就是来解决这个问题的。

它的原理很简单:把只读的SquashFS作为lowerdir,一个可读写的tmpfs或ext4分区作为upperdir,再合并成一个merged目录。对应用层来说,看到的是一个完整的、可读写的文件系统。

OverlayFS 分层结构 Upperdir(可写层) tmpfs / ext4 分区 存储修改的文件:/etc/passwd, /var/log/* Merged(合并视图) 应用看到的完整文件系统 读操作:优先查upperdir,未命中则查lowerdir Lowerdir(只读层) SquashFS 压缩镜像 包含系统固件:/bin, /lib, /usr 写操作流程 写文件 → 自动复制到upperdir(copy-up)→ 修改在upperdir中生效

3.3.1 挂载OverlayFS

# 假设:
# - /mnt/rootfs.squashfs 是只读固件
# - /data/overlay 是可写分区(ext4)
# - /mnt/root 是最终挂载点

# 第一步:挂载SquashFS
mount -t squashfs /dev/mmcblk0p2 /mnt/rootfs

# 第二步:创建upperdir和workdir
mkdir -p /data/overlay/{upper,work}

# 第三步:挂载OverlayFS
mount -t overlay overlay \
  -o lowerdir=/mnt/rootfs,upperdir=/data/overlay/upper,workdir=/data/overlay/work \
  /mnt/root

# 验证
df -h /mnt/root
# 输出示例:overlay  overlay  1.8G  200M  1.6G  11% /mnt/root
我建议: workdir必须和upperdir在同一个文件系统上。另外,workdir目录不能是upperdir的子目录,否则会死循环。这个坑我当年调试了整整一个下午才找到原因。

4.4 车载场景的典型分区方案

结合上面三个技术,一个成熟的车载Linux分区布局长这样:

分区 文件系统 大小 挂载点 说明
mmcblk0p1 vfat 64MB /boot 内核、设备树、uboot
mmcblk0p2 squashfs 256MB /mnt/rootfs 只读根文件系统(lowerdir)
mmcblk0p3 ext4 512MB /data 可写数据分区(upperdir + 用户数据)
mmcblk0p4 ext4 128MB /var/log 日志分区(独立挂载,防止日志写满影响系统)

启动时,init脚本先挂载SquashFS,再挂载OverlayFS,最后pivot_root切换到合并后的根文件系统。整个过程,从uboot到用户态,大约3-5秒。

关键指标: 经过裁剪的根文件系统(BusyBox + 核心库 + 基础配置)可以控制在2-4MB。加上SquashFS压缩,实际占用的Flash空间不到1.5MB。相比标准的Ubuntu根文件系统(约500MB),压缩了300倍以上。

4.5 避坑指南与调试技巧

做文件系统裁剪,我遇到过不少「灵异事件」。这里列几个常见的:

  • 缺少动态库:BusyBox静态编译可以避免,但如果你的应用用了glibc或musl,记得把.so文件拷到/lib下。用ldd命令检查依赖。
  • 设备节点缺失:除了/dev/console和/dev/null,如果用到串口、GPIO、I2C,记得提前创建或启用devtmpfs。
  • OverlayFS的copy-up性能:第一次写一个文件时,OverlayFS会把整个文件从lowerdir复制到upperdir。如果文件很大(比如数据库文件),第一次写入会很慢。我建议把这类文件直接放在upperdir对应的分区里。
  • SquashFS的块对齐:制作镜像时,块大小最好和Flash的擦除块大小对齐(通常是128KB或256KB),否则会有写放大问题。
我曾经犯过的错: 有一次OTA升级后,系统启动失败。查了半天,发现是upperdir里残留了旧版本的配置文件,覆盖了lowerdir里的新配置。解决方案:OTA升级时,清空upperdir中对应的目录,或者用mount -o remount重新挂载。

好了,文件系统裁剪的核心思路就这些。BusyBox给你最小体积,SquashFS给你只读保护,OverlayFS给你可写能力。三者配合,就是车载Linux文件系统的「黄金三角」。