1. 文件系统基础:嵌入式Linux文件系统概述、VFS架构与常见文件系统对比
嵌入式Linux开发中,文件系统是个绕不开的话题。我见过不少工程师,内核驱动写得飞起,一碰到文件系统就头疼——选型不知道怎么选,参数不知道怎么调,出了问题也不知道从哪查。其实文件系统没那么玄乎,咱们从最基础的东西开始捋一遍。
1.1 嵌入式Linux文件系统概述
文件系统说白了,就是管理存储介质上数据的一套规则。它负责三件事:数据怎么存、怎么读、怎么管理空间。在嵌入式场景下,这套规则要比PC上苛刻得多。
嵌入式设备的特点很明显:
- 存储介质多样:NAND Flash、NOR Flash、eMMC、SD卡,甚至还有SPI Flash。每种介质的物理特性都不一样。
- 资源受限:CPU主频低、内存小、存储空间有限。你不可能在只有64MB内存的设备上跑一个复杂的日志型文件系统。
- 掉电风险高:嵌入式设备经常被直接拔电。文件系统必须能扛住意外掉电,不能丢数据、不能损坏。
- 寿命问题:Flash有擦写次数限制,文件系统得做磨损均衡,不然某些块先挂了,整个设备就废了。
我在一个工业网关项目里就吃过亏。当时图省事,直接用了PC上的ext4,结果设备运行了三个月,频繁掉电后文件系统就挂了,数据全丢。后来才老老实实换成ubifs,加了掉电保护机制。嗯,这个教训挺深刻的。
核心观点:嵌入式文件系统的选型,不是看哪个功能多,而是看哪个最适合你的硬件平台和应用场景。没有银弹。
1.2 VFS(虚拟文件系统)架构
VFS是Linux内核里一个非常巧妙的设计。它定义了一套统一的接口标准,让上层应用不用关心底层文件系统到底是什么。你写一个open()、read()、write(),VFS帮你把请求路由到对应的文件系统驱动上。
VFS的核心组件有四个:
- 超级块(super_block):每个挂载的文件系统都有一个超级块,记录整个文件系统的元信息,比如块大小、inode总数、挂载状态等。
- inode:每个文件或目录对应一个inode,记录文件的元数据(权限、大小、时间戳)和数据块指针。注意,文件名不在这里,文件名在目录项里。
- 目录项(dentry):负责维护文件名到inode的映射关系。VFS会缓存目录项,加速路径解析。
- 文件对象(file):进程打开一个文件时,内核创建一个文件对象,记录当前读写位置、访问模式等。文件对象是进程级的,多个进程可以打开同一个文件,各自有独立的文件对象。
我画了一张VFS架构图,你看一眼就明白了:
你看,VFS在中间做了一层抽象。上层应用只管调用标准接口,下层具体文件系统只管实现自己的逻辑。这种分层设计的好处是:你可以随时换文件系统,应用代码一行都不用改。
调试技巧:想查看当前系统挂载了哪些文件系统?用 cat /proc/filesystems。想看每个挂载点的详细信息?mount 命令或者 cat /proc/mounts。我在排查文件系统问题时,第一步永远是看这两个文件。
1.3 常见文件系统类型对比
嵌入式领域常用的文件系统就那么几种,各有各的脾气。我挑四个最典型的给你讲讲:ext4、jffs2、ubifs、squashfs。
1.3.1 ext4
ext4是Linux桌面和服务器上的标配,功能全面、性能稳定。但它最初是为硬盘设计的,用在Flash上有些水土不服。ext4没有原生的磨损均衡,也没有针对Flash的掉电保护。如果你用eMMC,eMMC控制器自己会做磨损均衡和坏块管理,ext4勉强能用。但如果是裸NAND,我建议你别用ext4。
1.3.2 jffs2
jffs2是专门为NOR Flash设计的日志型文件系统。它把整个Flash当成一个环形缓冲区,数据追加写入,旧数据被标记为脏块,垃圾回收时再清理。jffs2的优点是掉电安全、不需要扫描整个介质就能挂载。但缺点也很明显:挂载时间随容量线性增长,内存占用高。我记得一个项目里用了64MB的NOR Flash,jffs2挂载花了将近30秒,客户直接投诉了。
1.3.3 ubifs
ubifs是jffs2的接班人,专门为NAND Flash设计。它引入了UBI层来做坏块管理和磨损均衡,性能比jffs2好很多。ubifs支持写缓存、压缩、快速挂载,适合大容量NAND Flash。我现在做新项目,只要是用NAND Flash,首选就是ubifs。
1.3.4 squashfs
squashfs是只读压缩文件系统。它把整个文件系统压缩成一个镜像,挂载时解压读取。squashfs非常适合存放固件、内核模块、只读数据。它不支持写操作,所以不用担心掉电损坏。很多嵌入式设备的根文件系统就是squashfs,配合一个可写的overlay层使用。
我把它们的核心差异整理成了一张表:
| 特性 | ext4 | jffs2 | ubifs | squashfs |
|---|---|---|---|---|
| 适用介质 | 硬盘、eMMC | NOR Flash | NAND Flash | 任何只读介质 |
| 读写支持 | 读写 | 读写 | 读写 | 只读 |
| 压缩 | 支持(需开启) | 支持 | 支持 | 支持(默认) |
| 磨损均衡 | 无 | 有 | 有(UBI层) | 不适用 |
| 掉电保护 | 日志(有限) | 日志型 | 日志型 | 天然安全 |
| 挂载速度 | 快 | 慢(随容量线性增长) | 快 | 快 |
| 内存占用 | 中等 | 高 | 中等 | 低 |
| 典型场景 | eMMC系统盘 | 小容量NOR Flash | 大容量NAND Flash | 固件、只读根文件系统 |
避坑指南:我曾经在一个项目里把jffs2用在了128MB的NAND Flash上,结果挂载时间超过2分钟,内存占用飙到30MB。后来换成ubifs,挂载时间降到2秒,内存占用不到5MB。选型时一定要搞清楚你的存储介质类型和容量,别拿jffs2去喂大容量NAND。
选文件系统其实没那么复杂。你想想看:
- 如果是NOR Flash,容量小(通常32MB以下),用jffs2。
- 如果是NAND Flash,容量大(128MB以上),用ubifs。
- 如果是eMMC,控制器帮你做了脏活,ext4就够用。
- 如果是只读数据,比如固件、内核、rootfs,用squashfs。