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 虚拟文件系统架构图 用户空间(User Space) 系统调用:open() / read() / write() / close() VFS 虚拟文件系统层 超级块(super_block) | inode | 目录项(dentry) | 文件对象(file) 统一接口:open / read / write / iterate / ioctl ... 具体文件系统层 ext4 | jffs2 | ubifs | squashfs | fat | ntfs ... 每个文件系统实现 VFS 定义的操作函数指针 块设备层(Block Layer) 存储介质:NAND / NOR / eMMC / SD

你看,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。