rootfs 文件系统类型对比:选对文件系统,性能就赢了一半

大家好,我是你们的嵌入式系统讲师。今天我们来聊聊 rootfs 文件系统的选型问题。说实话,我在这个领域摸爬滚打十几年,见过太多因为文件系统选错导致项目返工的案例。你想想看,一个嵌入式设备跑起来,rootfs 是它访问存储的第一道关卡,选错了,后面再怎么优化都是白搭。

嵌入式场景下,存储介质五花八门:NOR Flash、NAND Flash、eMMC、SD 卡,甚至有些场景直接跑在内存里。不同的介质,对文件系统的要求天差地别。今天我就把最常见的五种文件系统——SquashFS、JFFS2、UBIFS、ext4、ramfs/tmpfs——掰开揉碎了讲清楚。

核心观点:没有最好的文件系统,只有最适合你硬件和场景的文件系统。选型时,先看存储介质,再看读写模式,最后考虑空间和可靠性要求。

rootfs 文件系统选型决策树 存储介质类型 只读 / 高压缩比 NAND Flash eMMC / SD 卡 SquashFS UBIFS / JFFS2 ext4 ramfs / tmpfs 选型要点: • 只读系统(固件升级不频繁)→ SquashFS(压缩率高,省空间) • NAND Flash 有坏块 → UBIFS(磨损均衡 + 掉电保护) • eMMC/SD 卡 → ext4(成熟稳定,日志功能可选) • 纯内存运行 → 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 系统盘 临时文件

如何选择?我的实战建议

说了这么多,到底怎么选?我总结了一个简单的决策流程:

  1. 先看存储介质:NOR Flash 还是 NAND Flash?eMMC 还是 SD 卡?还是纯内存?
  2. 再看读写需求:rootfs 需要写吗?如果不需要,SquashFS 是最优解。
  3. 考虑容量:Flash 小于 64MB 且是 NAND,JFFS2 勉强能用;大于 64MB 直接上 UBIFS。
  4. 考虑可靠性:需要掉电保护吗?需要磨损均衡吗?UBIFS 和 ext4(开日志)都行。
  5. 考虑性能:启动时间敏感吗?UBIFS 和 ext4 的挂载速度远快于 JFFS2。

我的个人偏好:如果让我给一个新项目选 rootfs 文件系统,我会优先考虑 SquashFS + overlayfs(只读 rootfs + 可写 overlay)或者 UBIFS(可读写 NAND)。这两个组合在我经历的项目中表现最稳定,性能也最可控。