rootfs 文件系统类型对比:选对文件系统,性能就赢了一半
大家好,我是你们的嵌入式系统讲师。今天我们来聊聊 rootfs 文件系统的选型问题。说实话,我在这个领域摸爬滚打十几年,见过太多因为文件系统选错导致项目返工的案例。你想想看,一个嵌入式设备跑起来,rootfs 是它访问存储的第一道关卡,选错了,后面再怎么优化都是白搭。
嵌入式场景下,存储介质五花八门:NOR Flash、NAND Flash、eMMC、SD 卡,甚至有些场景直接跑在内存里。不同的介质,对文件系统的要求天差地别。今天我就把最常见的五种文件系统——SquashFS、JFFS2、UBIFS、ext4、ramfs/tmpfs——掰开揉碎了讲清楚。
核心观点:没有最好的文件系统,只有最适合你硬件和场景的文件系统。选型时,先看存储介质,再看读写模式,最后考虑空间和可靠性要求。
SquashFS:只读场景的压缩王者
SquashFS 是我个人非常喜欢的一个文件系统。它的设计哲学很简单:只读、高压缩、快速解压。你想想看,嵌入式设备里很多 rootfs 其实不需要频繁修改——路由器固件、工业控制器、IoT 终端,这些设备固件烧进去之后,可能几年都不会动一次。
SquashFS 的压缩率非常可观。我记得有个项目,原始 rootfs 有 64MB,用 SquashFS 压缩后只剩 18MB,直接省了 70% 的 Flash 空间。对于 NOR Flash 这种寸土寸金的介质来说,这个优势太明显了。
我的经验:如果你用 SquashFS 做 rootfs,建议搭配 overlayfs 或 aufs 使用。只读的 SquashFS 做底层,上层挂一个可写的 tmpfs 或小分区,这样既能享受高压缩率,又能保存运行时配置。OpenWrt 就是这么干的。
SquashFS 的缺点也很明显——不能写。所以它只适合做静态 rootfs。另外,它的块大小选择很关键:块越大,压缩率越高,但随机读取性能会下降。我一般选 128KB 或 256KB 的块大小,平衡得比较好。
JFFS2:NAND Flash 的老将
JFFS2 是专门为 NAND Flash 设计的日志结构文件系统。它解决了 Flash 的两个核心问题:坏块管理和磨损均衡。说白了,Flash 的每个块都有擦写寿命,JFFS2 会尽量均匀地使用每个块,避免某些块提前报废。
不过,JFFS2 有个致命伤——挂载时间。它需要在挂载时扫描整个 Flash 介质来重建文件系统状态。我记得有个 256MB 的 NAND Flash,JFFS2 挂载花了将近 30 秒。这在需要快速启动的嵌入式设备里,简直是灾难。
避坑指南:我曾经在一个工业数据采集器项目里用了 JFFS2,结果每次设备重启都要等半分钟才能进入主程序。后来换成 UBIFS,挂载时间从 30 秒降到了 2 秒。如果你的 NAND Flash 大于 64MB,我强烈建议你直接跳过 JFFS2,用 UBIFS。
JFFS2 的另一个问题是内存占用。它需要在内存中维护文件系统的索引结构,Flash 越大,占用的内存越多。对于只有 32MB RAM 的 MCU 来说,JFFS2 可能直接吃掉一半内存。
UBIFS:NAND Flash 的现代选择
UBIFS 是 JFFS2 的接班人,由 Nokia 和 Linux 社区共同开发。它解决了 JFFS2 的几乎所有痛点:挂载快、内存占用低、支持大容量 Flash。
UBIFS 的工作原理和 JFFS2 完全不同。它不扫描整个 Flash,而是通过 UBI(Unsorted Block Images)层来管理坏块和磨损均衡。UBI 层把物理块映射成逻辑块,UBIFS 只关心逻辑块,坏块处理完全由 UBI 层搞定。
我做过一个对比测试:同样是 128MB NAND Flash,JFFS2 挂载需要 15 秒,UBIFS 只需要 1.2 秒。而且 UBIFS 支持 write-back 缓存,写入性能比 JFFS2 高出 3-5 倍。
关键参数:UBIFS 的 LEB(Logical Erase Block)大小和子页面大小需要根据 Flash 的物理特性来配置。一般 LEB 大小设为物理擦除块大小减去 UBI 头开销(通常 2 个 PEB)。子页面大小设为 NAND 页大小。配错了会导致性能严重下降。
ext4:eMMC 和 SD 卡的首选
ext4 是 Linux 桌面和服务器上的标准文件系统,在嵌入式领域同样有一席之地。它主要用在 eMMC、SD 卡这类块设备上,因为这些设备内部已经有 FTL(Flash Translation Layer)处理坏块和磨损均衡,ext4 不需要关心 Flash 的物理特性。
ext4 的优势是成熟、稳定、工具链完善。fsck、resize2fs、tune2fs 这些工具在嵌入式开发中非常实用。我习惯在 ext4 上关闭日志功能(data=writeback),因为 eMMC 本身有掉电保护,日志反而增加了写放大。
我的配置建议:在嵌入式 ext4 上,用以下挂载参数可以显著提升性能:noatime,nodiratime,data=writeback,commit=60。noatime 禁止更新访问时间,减少写操作;commit=60 把数据同步间隔从 5 秒延长到 60 秒,减少频繁刷盘。
ext4 的缺点是对小文件的支持不如专门为 Flash 设计的文件系统。如果你有大量小文件(比如日志文件、配置文件),ext4 的 inode 分配和目录索引效率会下降。这时候可以考虑用 UBIFS 或者 F2FS。
ramfs / tmpfs:内存中的极速方案
ramfs 和 tmpfs 都是基于内存的文件系统,数据不落盘。它们的区别在于:ramfs 大小固定,会一直占用物理内存;tmpfs 可以动态调整大小,并且支持 swap 交换。
我一般在两种场景下用 tmpfs:一是存放临时文件,比如编译过程中的中间文件、日志缓冲区;二是做 overlayfs 的上层可写层,配合 SquashFS 使用。
ramfs 我用的比较少,因为它太「野蛮」了——它会一直占用内存直到系统 OOM。有一次我在调试时不小心把一个大文件写进了 ramfs,结果系统直接卡死。从那以后,我统一用 tmpfs 并设置 size 限制,比如 mount -t tmpfs -o size=32M tmpfs /tmp。
注意:tmpfs 的数据在掉电后会全部丢失。不要把重要数据放在 tmpfs 里,除非你有同步机制定期刷到持久存储。我曾经见过有人把数据库的 WAL 日志放在 tmpfs 里,结果一次意外断电导致数据库崩溃。
横向对比:一张表看懂差异
| 特性 | SquashFS | JFFS2 | UBIFS | ext4 | tmpfs |
|---|---|---|---|---|---|
| 读写模式 | 只读 | 读写 | 读写 | 读写 | 读写 |
| 适用介质 | NOR/NAND | NAND | NAND | eMMC/SD | 内存 |
| 压缩 | 是(gzip/lzma/lzo) | 是(zlib) | 是(lzo/zlib) | 否 | 否 |
| 挂载速度 | 极快 | 慢(扫描全盘) | 快 | 快 | 极快 |
| 坏块管理 | 无 | 有 | 有(UBI 层) | 无(依赖 FTL) | 无 |
| 磨损均衡 | 无 | 有 | 有(UBI 层) | 无(依赖 FTL) | 无 |
| 掉电保护 | 天然(只读) | 有 | 有 | 有(日志) | 无 |
| 内存占用 | 低 | 高 | 中 | 中 | 动态 |
| 典型场景 | 固件 rootfs | 小容量 NAND | 大容量 NAND | eMMC 系统盘 | 临时文件 |
如何选择?我的实战建议
说了这么多,到底怎么选?我总结了一个简单的决策流程:
- 先看存储介质:NOR Flash 还是 NAND Flash?eMMC 还是 SD 卡?还是纯内存?
- 再看读写需求:rootfs 需要写吗?如果不需要,SquashFS 是最优解。
- 考虑容量:Flash 小于 64MB 且是 NAND,JFFS2 勉强能用;大于 64MB 直接上 UBIFS。
- 考虑可靠性:需要掉电保护吗?需要磨损均衡吗?UBIFS 和 ext4(开日志)都行。
- 考虑性能:启动时间敏感吗?UBIFS 和 ext4 的挂载速度远快于 JFFS2。
我的个人偏好:如果让我给一个新项目选 rootfs 文件系统,我会优先考虑 SquashFS + overlayfs(只读 rootfs + 可写 overlay)或者 UBIFS(可读写 NAND)。这两个组合在我经历的项目中表现最稳定,性能也最可控。