欢迎光临
我们一直在努力

嵌入式Linux文件系统选型实战:squashfs、UBIFS、ext4到底怎么选?

嵌入式Linux文件系统选型实战:squashfs、UBIFS、ext4到底怎么选?

一句话总结:根文件系统用squashfs(只读+压缩),数据分区用UBIFS(NAND Flash)或ext4(eMMC/SD卡),日志分区用tmpfs——这个组合解决了90%嵌入式产品的存储需求。本文给出完整的选型决策树和配置方案。

做嵌入式Linux产品,迟早会被一个问题卡住:文件系统用什么?

我第一次做量产产品的时候就踩过这个坑。选了ext4做根文件系统,结果产品在客户现场异常断电几次后,文件系统损坏起不来了。后来换成squashfs+overlay的方案,再没出过这类问题。

今天就把我这些年做嵌入式Linux文件系统选型的经验全写出来。


第一步:先搞清楚你的存储介质

嵌入式设备常用的存储介质就三种:

介质特点适合的文件系统
NAND Flash 有坏块、需要ECC、按页读写 UBIFS, YAFFS2
eMMC 自带FTL、像普通块设备 ext4, squashfs
SD/NOR 简单、容量小 ext4, FAT

选文件系统的第一件事不是看功能,是看存储介质。NAND Flash绝对不能直接上ext4——ext4没有坏块管理,在NAND上用几天就坏了。


第二步:分区方案是核心

我做过的最稳定的分区方案长这样:

分区布局(4GB eMMC为例):
┌─────────────────┐
│ boot (16MB) │ ← FAT16/vfat, 放kernel+DTB
├─────────────────┤
│ rootfs (256MB) │ ← squashfs, 只读根文件系统
├─────────────────┤
│ overlay (128MB) │ ← ext4, overlayfs上层
├─────────────────┤
│ data (剩余空间) │ ← ext4, 用户数据
├─────────────────┤
│ log (64MB) │ ← tmpfs, 运行时日志
└─────────────────┘

这个方案的精髓是:根文件系统只读,运行时可写层叠在overlay分区上。 这样根系统永远不会因为异常断电损坏,恢复出厂设置也简单——清空overlay分区就行。


第三步:各种文件系统详解

squashfs — 根文件系统的首选

squashfs是一个只读、压缩的文件系统。它是我做嵌入式Linux首选根文件系统的原因有三个:

1. 压缩率高
一个200MB的rootfs,用squashfs能压到60-80MB。对于存储资源紧张的嵌入式设备,这个压缩率太香了。

# 制作squashfs根文件系统
mksquashfs /path/to/rootfs rootfs.squashfs \\
-comp zstd \\
-b 131072 \\
-all-root

2. 只读即安全
因为文件系统本身是只读的,异常断电不会损坏它。你再也不用担心客户拔电源把设备搞挂了。

3. 加载快
squashfs支持随机访问,内核只需要解压被访问到的数据块,不是整个解压。启动速度比解压整个initramfs快得多。

UBIFS — NAND Flash专用

如果你的产品用NAND Flash,UBIFS是最好的选择。它比YAFFS2新,支持更好的压缩和磨损均衡。

# 制作UBIFS镜像
mkfs.ubifs -r /path/to/rootfs \\
-m 2048 \\ # 页大小
-e 124KiB \\ # 擦除块大小
-c 1024 \\ # 最大逻辑擦除块数
-o rootfs.ubifs

# 打包成ubi镜像
ubinize -o rootfs.ubi \\
-m 2048 \\
-p 128KiB \\
ubinize.cfg

关键参数说明:

  • -m(页大小):从NAND芯片手册看,常见2048或4096
  • -e(擦除块大小):一般是页大小×64
  • -c(最大擦除块数):估算你需要的最大容量

ext4 — eMMC/SD卡的标准答案

eMMC自带FTL(Flash Translation Layer),坏块管理和磨损均衡都由eMMC控制器处理了。所以你可以把它当普通硬盘用,ext4是最成熟的选择。

# ext4调优参数(适合嵌入式)
mkfs.ext4 -O ^has_journal \\ # 去掉日志(省空间,但异常断电可能丢数据)
-b 4096 \\ # 块大小4K
-E stride=16,stripe_width=16 \\ # eMMC对齐优化
/dev/mmcblk0p3

要不要关掉journal? 如果分区是用来放临时数据或缓存,关掉journal能省不少空间和写入。如果放重要数据,留着。

tmpfs — 日志和临时文件

# /etc/fstab 配置
tmpfs /var/log tmpfs defaults,noatime,size=64M,mode=0755 0 0
tmpfs /tmp tmpfs defaults,noatime,size=32M,mode=1777 0 0

日志放tmpfs的好处:系统重启后日志自动清空,不会积累,不会写坏Flash。


第四步:overlayfs实现"只读根+可写层"

这是我现在用的标准方案,在Buildroot和Yocto里都支持。

# overlayfs挂载脚本(init阶段执行)
mount -t squashfs /dev/mmcblk0p2 /mnt/rootfs
mount -t ext4 /dev/mmcblk0p3 /mnt/overlay
mkdir -p /mnt/overlay/upper /mnt/overlay/work

mount -t overlay overlay \\
-o lowerdir=/mnt/rootfs,upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work \\
/mnt/merged

# 最后switch_root到/mnt/merged
exec switch_root /mnt/merged /sbin/init

恢复出厂设置: 只需要格式化overlay分区,根文件系统自动恢复出厂状态。


选型决策树

做新项目的时候,直接按这个流程走:

#mermaid-svg-atxd0pgbk3u9mhJN{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-atxd0pgbk3u9mhJN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-atxd0pgbk3u9mhJN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-atxd0pgbk3u9mhJN .error-icon{fill:#552222;}#mermaid-svg-atxd0pgbk3u9mhJN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-atxd0pgbk3u9mhJN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-atxd0pgbk3u9mhJN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-atxd0pgbk3u9mhJN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-atxd0pgbk3u9mhJN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-atxd0pgbk3u9mhJN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-atxd0pgbk3u9mhJN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-atxd0pgbk3u9mhJN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-atxd0pgbk3u9mhJN .marker.cross{stroke:#333333;}#mermaid-svg-atxd0pgbk3u9mhJN svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-atxd0pgbk3u9mhJN p{margin:0;}#mermaid-svg-atxd0pgbk3u9mhJN .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-atxd0pgbk3u9mhJN .cluster-label text{fill:#333;}#mermaid-svg-atxd0pgbk3u9mhJN .cluster-label span{color:#333;}#mermaid-svg-atxd0pgbk3u9mhJN .cluster-label span p{background-color:transparent;}#mermaid-svg-atxd0pgbk3u9mhJN .label text,#mermaid-svg-atxd0pgbk3u9mhJN span{fill:#333;color:#333;}#mermaid-svg-atxd0pgbk3u9mhJN .node rect,#mermaid-svg-atxd0pgbk3u9mhJN .node circle,#mermaid-svg-atxd0pgbk3u9mhJN .node ellipse,#mermaid-svg-atxd0pgbk3u9mhJN .node polygon,#mermaid-svg-atxd0pgbk3u9mhJN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-atxd0pgbk3u9mhJN .rough-node .label text,#mermaid-svg-atxd0pgbk3u9mhJN .node .label text,#mermaid-svg-atxd0pgbk3u9mhJN .image-shape .label,#mermaid-svg-atxd0pgbk3u9mhJN .icon-shape .label{text-anchor:middle;}#mermaid-svg-atxd0pgbk3u9mhJN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-atxd0pgbk3u9mhJN .rough-node .label,#mermaid-svg-atxd0pgbk3u9mhJN .node .label,#mermaid-svg-atxd0pgbk3u9mhJN .image-shape .label,#mermaid-svg-atxd0pgbk3u9mhJN .icon-shape .label{text-align:center;}#mermaid-svg-atxd0pgbk3u9mhJN .node.clickable{cursor:pointer;}#mermaid-svg-atxd0pgbk3u9mhJN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-atxd0pgbk3u9mhJN .arrowheadPath{fill:#333333;}#mermaid-svg-atxd0pgbk3u9mhJN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-atxd0pgbk3u9mhJN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-atxd0pgbk3u9mhJN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-atxd0pgbk3u9mhJN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-atxd0pgbk3u9mhJN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-atxd0pgbk3u9mhJN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-atxd0pgbk3u9mhJN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-atxd0pgbk3u9mhJN .cluster text{fill:#333;}#mermaid-svg-atxd0pgbk3u9mhJN .cluster span{color:#333;}#mermaid-svg-atxd0pgbk3u9mhJN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-atxd0pgbk3u9mhJN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-atxd0pgbk3u9mhJN rect.text{fill:none;stroke-width:0;}#mermaid-svg-atxd0pgbk3u9mhJN .icon-shape,#mermaid-svg-atxd0pgbk3u9mhJN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-atxd0pgbk3u9mhJN .icon-shape p,#mermaid-svg-atxd0pgbk3u9mhJN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-atxd0pgbk3u9mhJN .icon-shape .label rect,#mermaid-svg-atxd0pgbk3u9mhJN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-atxd0pgbk3u9mhJN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-atxd0pgbk3u9mhJN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-atxd0pgbk3u9mhJN :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

NAND Flash

eMMC

SD卡

大容量

小容量

要只读

无所谓

频繁写

很少写

存储介质是什么?

容量需求

是否要只读根

是否频繁写

UBIFS

YAFFS2

squashfs+overlay+ext4

ext4

ext4+调优

兼容性好


常见问题

Q: 为什么不用JFFS2?
JFFS2太老了。它把整个文件系统的索引存在内存里,512MB的Flash需要128MB RAM来存索引。而且挂载时要扫描整个Flash,大容量下慢得离谱。UBIFS彻底解决了这些问题。

Q: squashfs的压缩级别怎么选?
我一般用zstd或lz4。zstd压缩比高,lz4解压速度快。如果启动速度是关键指标(比如车载设备),选lz4。如果Flash空间紧张,选zstd。

Q: 我的根文件系统需要更新,squashfs只读怎么办?
两种方案:1)双分区A/B升级 — 两个squashfs分区轮流更新,升级失败可以回滚。2)只保留核心文件在squashfs里,需要更新的部分(如Qt库、应用二进制)放在ext4分区。

Q: uboot如何加载squashfs内核?
实际上uboot不需要理解squashfs。kernel和DTB单独放在一个FAT分区(boot分区),uboot从这里加载。squashfs只放根文件系统。

# uboot脚本示例
setenv bootargs 'root=/dev/mmcblk0p2 rootfstype=squashfs ro'
fatload mmc 0:1 ${loadaddr} zImage
fatload mmc 0:1 ${fdtaddr} ${board}.dtb
bootz ${loadaddr}${fdtaddr}


总结

嵌入式Linux文件系统选型没有完美的方案。最快的决策方法就是按上面那张决策树走。

但如果你让我给一个最保险的默认方案:

  • eMMC存储:squashfs(根) + ext4(overlay+数据)
  • NAND Flash:UBIFS(统一分区,需要可写) 或者 UBIFS(只读根) + UBIFS(数据)
  • 一定要做overlayfs — 只读根防止文件系统损坏,需要写的地方用overlay层

这套方案我用在量产项目上没有出过因为文件系统导致的严重故障。

记住:文件系统的可靠性不是选出来的,是设计出来的。 分区方案和异常处理逻辑比文件系统本身更重要。

赞(0)
未经允许不得转载:171主机测评 » 嵌入式Linux文件系统选型实战:squashfs、UBIFS、ext4到底怎么选?
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址