本文讲述个人对嵌入式文件系统的学习、理解、移植、应用等相关方面的笔记。
前置思考与解答
个人认为,接触一个事物之前,总要带着一点疑问和思考较为好些,那么在文件系统应用与开发方面,也应该有些前置思考:
文件系统在嵌入式开发中有哪些应用场景?
如何从零移植一个文件系统到嵌入式芯片上?移植步骤?
文件系统的代码原理?占用内存空间如何评估?时间复杂度如何评估?如何从零开始编写一个文件系统?
初始化挂载、目录读写、文件读写实现原理?与Windows的文件目录架构、Linux的文件目录架构有何异同?不同文件系统的差异?
对于非Windows兼容或者非上位机直接交互的存储设备/文件系统,如外部Flash,如何将文件按照指定格式烧录进存储设备?
准备源文件,选择文件系统镜像工具(杰理有提供),根据需求配置文件系统类型、簇大小、镜像大小等参数,将源文件按照指定格式生成文件系统镜像文件。
在嵌入式设备中,直接给存储设备烧录文件通常比较麻烦?有其它烧写文件系统的方法吗?如何调试与开发?
对于板载 SPI-Flash,可以直接通过SPI-Flash编程器把文件系统镜像直接烧录进去
另外,如杰理芯片(内部集成了烧录升级协议栈的),可以配置在给芯片升级时,同时将文件系统镜像烧录进去。(设备通过USB Mass Storage实现读卡器功能,大致是:设备配置为USB从机,电脑主机通过USB将设备的存储介质,如SPI-Flash识别为一个大容量设备,从而对SPI-Flash进行格式化和镜像烧写操作)
使用文件系统会遇到的问题?如何排查和解决?
嵌入式文件系统API对不同文件系统类型的支持度有所不同,需要注意(如:部分设备对FAT12的支持不完善,比如空间占用统计功能不完整)
SD卡某些情况下,会被电脑识别成损坏,但嵌入式设备能读卡,并正常播放部分歌曲(但中途可能会有卡顿、播放异常、跳歌等情况)。原因是:电脑扫描检测更完善,嵌入式设备不会做全盘的完整性检测。比如,FAT表的完整性、坏簇的检查等。
注意文件系统的文件排序与实际存储的排序(创建文件的文件号与预期调用的不符,跟扫盘逻辑有关)
在使用SD卡、MMC、SPI Flash、E2PROM等存储设备时,需要考虑什么问题?
小容量设备,选择文件系统时,要选择较小的簇大小,可以选择空间开销较少的FAT12等文件系统;
容量较大,则选择FAT32或者其它文件系统,选择较大的簇,减少时间开销;
特别小容量的存储介质,不建议上文件系统,可以自行通过底层接口,进行二进制数据读写。不同文件系统的扫盘、文件增删读写等操作有何区别?扫盘到底是什么?文件系统是如何扫盘的?对文件号的排序是怎样的?获取下一个文件,需要扫盘吗?上一个文件呢?
文末解答,此处不赘述
对于有限擦写寿命的Flash是如何实现擦写均衡的?掉电安全是如何实现的?等等原理实现?
以下内容为在前人的基础上作进一步总结,未完待续……,参考内容见文末站点
文件系统概述
文件系统是什么?
文件系统是一个中间件,用于管理和组织存储设备(如硬盘、U盘、SD卡等)的文件和目录。文件系统定义了文件如何被存储、命名、组织以及检索的方式。
文件系统向应用程序或用户提供了一系列的接口,包括对文件/目录的创建、删除、重命名、读取、写入、移动等等操作。
文件系统向上层/用户提供的功能包括有:文件存储、目录管理、文件属性管理、文件访问控制、文件备份、文件恢复、文件压缩、文件加密等等。
文件系统概念
扇区:存储介质的最小分割单位,一次至少读取或写入一个扇区的数据。
文件系统建立在物理扇区之上,通常将单个或多个物理扇区组合成更大的逻辑单元,称为簇(Clust),或者块(BLOCK)。簇(clust):文件系统的最小管理单位。
如果一个文件系统的簇大小为4KB,那么即使一个文件只有1KB大小,它也会占用整个4KB的簇空间。
如果一个文件大小为4.5KB,它将占用两个簇的空间,即8KB。一个文件的最后一簇往往是未存满的。
以32GB-U盘实验为例,检测到簇大小为16KB,创建一个空文件夹或者空文件,都会显示已用空间增加了16KB。
无论是不是在根目录下创建,或者创建的是文件夹还是文件,其实体在数据区都会至少占用一个簇。
PS:在容量较小的存储介质中,尽量少用或者不用文件夹,以减少空间消耗。
文件系统类型概述及适用场景
FAT12 、FAT16、 FAT32 都是属于FAT文件系统
NTFS ext4
exFAT 适用于大于32GB的可移动存储设备
FAT文件系统结构
FAT文件系统由 DBR及其保留扇区、FAT1、FAT2、DATA 四个部分组成。(分区被格式化时即创建该系统结构)
DBR及其保留扇区: DBR的含义是DOS引导记录,也称为操作系统引导记录,在DBR之后会有一些保留扇区。
FAT1: FAT的含义是文件分配表,FAT32一般有两份FAT,FAT1是第一份,也是主FAT。
FAT2: FAT2是FAT32的第二份文件分配表,也是FAT1的备份。
DATA: DATA也就是数据区,是FAT32文件系统的主要区域,由目录区和文件数据。
注意:FAT12/FAT16 通常情况下根目录区位于2号簇,但FAT32文件系统并不一定,根目录区的位置偏移由引导扇区的BPB描述决定。
FAT文件系统重要概念:
FAT表项: 每个 FAT 表项对应于一个簇(Cluster),包含指向下一个簇的指针,形成一个链表,用于表示文件或目录的实际存储位置。
FAT表项的序号对应相应的簇,比如第10个FAT表项,对应10号簇
FAT表项的值:全零,表示该簇为空闲;0FFFFFFF表示当前簇是一个文件的末尾;其它序号值,指向当前文件数据的下一个簇所在。
FAT12、FAT16、FAT32的根本区别就是单个FAT表项的位数,其直接限制了簇号的最大值,所能管理的空间大小 = 簇号Max * 簇Max大小
目录项: 描述文件或目录的元数据信息,包括但不限于文件名、扩展名、属性、创建时间、最后访问时间、最后修改时间、文件大小以及文件的第一个簇号等。
DBR 引导扇区及保留扇区
由五部分组成,分别为跳转指令、OEM代号、BPB、引导程序、结束标志
如上图所示
EB 3C 90
为跳转指令,3C 表示OS引导代码的偏移位置(非OS环境可忽略)OEM代号:占8个字节,由创建该文件系统镜像的厂商具体安排
BPB:FAT32的BPB从DBR的第12个字节开始,占用79字节,记录了有关该文件系统的重要信息。前面53个字节是BPB,后面26个字节是扩展BPB
记录的信息包括如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21BPB
每个扇区的字节数
每簇的扇区数
保留扇区数
FAT数:一般为2
根目录项数:FAT12/16使用,FAT32必须为0
小扇区数:FAT12/16使用,FAT32必须为0
媒体描述符
...
...
总扇区数
每FAT扇区数:只被FAT32使用,协助决定根目录从哪里开始
扩展标志、文件系统版本、根目录簇号、文件系统信息扇区号:只被FAT32使用
备份引导扇区号:只被FAT32使用
保留位
扩展BPB:
物理驱动器号、保留位、扩展引导标签
分区序号
卷标号
系统ID文件系统信息扇区位于保留扇区,记录数据区中空闲簇的个数及下一个空闲簇的簇号。(即紧跟DBR后的第一个扇区:1号扇区)
引导程序代码:FAT32的DBR引导程序占用420字节(非OS环境可忽略)
结束标志:DBR的结束标志与MBR,EBR的结束标志相同,为“55 AA”。
FAT 文件分配表
FAT表是用来记录簇号的,FAT表由FAT表项构成,每个表项对应一个簇号。(根目录、目录、文件都是属于文件数据,都会以表项的形式通过链表方式记录在FAT表中)
分区的数据区中每个簇都会映射到FAT表中的唯一一个FAT项,因为0号FAT和1号FAT被系统占用,用户的数据从2号FAT开始记录。
FAT文件系统之所以有12,16,32不同的版本之分,其根本在于单个FAT表项的二进制位数。
以FAT16为例,每一簇在FAT表中占据2字节(二进制16位)。所以,FAT16最大可以表示的簇号为0xFFFF(十进制的65535),以32K(最多只支持64个扇区)为簇的大小的话,FAT16可以管理的最大磁盘空间为:32KB×65535=2048MB,这就是为什么FAT16不支持超过2GB分区的原因(DOS下)。
下图为FAT1的第一个扇区:
文件系统擦写均衡策略,就是通过FAT表来管理的,每一个表项表示一个簇,擦写时随机选用簇来存放实际数据,而不是递增存放。
第0项和第1项即文件系统的DBR扇区及FAT表等存放的位置,从第2项开始,为0F FF FF FF,表示这是一个小文件,只占用一个簇就结束了,然后第3、4项也是的。
文件分配表,FAT32系统中,每32位就表示一个簇,簇号呈递增关系。 32位数据里的内容,则是簇链,表示文件的下一部分指向哪一个簇。 (刚初始化的文件分配表,通常都是连续的)
1 | 第5簇中存放的数据是6,表示一个文件或文件夹的首簇。其内容为第6簇,表示文件内容接下来的簇位于第6簇; |
重点:FAT表追踪记录 文件内容或者文件夹 在数据区的存放连续性,并不描述文件或文件夹属性。
DATA 数据区
数据区的位置在FAT2的后面,
数据区的内容主要由三部分组成:根目录,子目录和实际的文件数据内容。在数据区中是以“簇”为单位进行存储的,通常2号簇被分配给根目录使用。
重申:目录区和实际文件内容,在存储介质中的地址并不是递增关系,而是由FAT表记录
只有根目录区的位置是由引导扇区的描述数据确定的
其它子目录和文件的起始簇位置,由根目录区下的目录项描述(呈现树状关系)
FAT32文件系统中,分区根目录下的文件和目录的目录项都放在根目录区中,子目录中的文件和目录的目录项都放在子目录区中,每32个字节为一个目录项(FDT)
每个目录项纪录着一个目录或文件的数据描述信息。
也可能是多个目录项记录一个文件或目录
注意长短文件名目录项。
嵌入式文件系统的应用
暂无
移植思路概述
文件系统底层接口函数适配
底层接口,即为操作存储介质的接口。
比如SD卡,有SD卡配置初始化、块读、块写、块擦除、块指针偏移等。
文件系统上层接口应用
暂略
标准C库文件系统的部分接口示意如下:
1 | FILE *fopen(const char *path, const char *mode); |
文件访问权限字符串
1 | r :以只读方式打开已经存在的文件,如果不存在则打开失败 |
文件系统IO接口常见问题
- 当在W+格式时,可能出现有数据却读不到数据的情况,有可能是当前已经在文件末尾了,可以用
int feof(FILE *stream);
判断是否在文件最后。 - 在操作文件前,都首先要fopen文件,获取文件句柄,然后通过句柄操作文件。
嵌入式文件系统实现原理
暂略
思考与拓展
FAT文件系统中 FAT表项和目录项 的区别?
- FAT 表项是 FAT 文件系统中用于跟踪文件存储位置的信息。每个 FAT 表项对应于一个簇(Cluster),包含指向下一个簇的指针,形成一个链表,用于表示文件或目录的实际存储位置,使得系统能够追踪文件数据的连续性。
只要是文件夹或者文件,其数据本身就会至少占一个簇。(FAT12/16中,根目录只能占一个簇)
目录项则是用来描述文件或目录的元数据信息,包括但不限于文件名、扩展名、属性、创建时间、最后访问时间、最后修改时间、文件大小以及文件的第一个簇号等。
目录项存储在目录区(Directory Area),提供了文件或目录的基本信息,并通过指向 FAT 表中的第一个簇号来链接到文件的实际数据存储位置。
通过结合目录项的文件首簇号,和FAT表的文件簇链,从而可以找到文件的所有连续数据。
文件系统扫盘的详细流程?
- FAT文件系统,由上文可知,上层只知道根目录区在数据区的簇号,其它一概不知。所以每初次读取文件,都需要从根目录开始。
从根目录开始,先扫描根目录下的所有目录项(含文件夹和文件的目录项)
然后扫根目录下的第一个子目录下的文件,再递归扫子目录下的目录,直到叶子目录。(直到当前目录下的所有目录或者文件都已经扫完)
文件系统扫盘,通常是递归扫描。如果是层级扫描,那么扫描每一层不同文件夹的文件时,都须从根目录区开始重新定位。(可以是从根目录先递归文件,再递归子目录;也可以先递归终端目录的文件,再折返递归至根目录)
给文件号的排序,不取决于文件的目录项在目录区中的位置。(看文件系统上层程序的实现方式)
PS:建议自行做实验验证
文件系统的文件增加、删减操作在底层是如何体现的?会重新更新一次FAT表吗?
分配簇:创建新文件或目录时,在 FAT 表项,找到一个空闲簇,分配给新文件或目录,并在相应的目录区创建目录项以指向新分配的簇。
释放簇:删除文件或目录时,仅删除目录项,并在 FAT 表中,标记目录项指向的相关簇为空闲。而不会去清零文件数据。
调整簇链:增删改查,更新 FAT 表项以反映新的簇链。不会全局更新FAT表,而是局部更新,如同链表插入和删除节点一样。
另外,一个文件所存放的簇号,不一定就是连续的,其实这就是碎片化?
文件系统中文件检索与读取?文件号排序越靠后的,检索读取时间就越长吗?
某种程序上来说,是的,按文件号读取的话,首次指定读取指定文件号,就需要从根目录区开始扫
每扫到一个文件项,就文件号计数加一,当文件号排序累加到与指定序号一致时,即表示找到了目标文件的目录项,接着从该目录项配置中,去到介质中指定偏移地址,读取实际的文件内容。
- 设备扫盘,实际上是扫描DATA数据区的根目录区、子目录区的所有目录项。而不会扫描文件内容。
增删文件后,需要重新扫盘,给文件号重新排序,避免混乱或者错序
获取下一个文件或者基于当前文件的情况下,读取靠后的文件号文件时,文件系统只需要基于当前的扫盘指针,继续往后扫即可。
如果文件号排序越靠后,那么初次读取花费时间就越长。(断点续传时,会记录相应的偏移,无须重新定位文件,所以不耗时)
在嵌入式中,如果存储了数量非常多的文件,要重视访问文件时所消耗的扫盘时间,尤其是多线程环境,如果扫盘线程优先级靠后,且文件数量多,可能会出现明显的程序运行滞后(可考虑调高优先级)
另外,文件系统内容碎片化程度越高,访问时间也越长。
磁盘碎片化是什么意思?如何理解?
FAT表记录的簇链,在初始化时是沿着簇号递增链接的,随着增删改次数越来越多,簇链就变得杂乱,
导致文件内容在数据区的簇号分布也是散乱的,那么每次在读取一个文件时,都要根据各种偏移或者信息,进行拼凑,导致性能下降。
磁盘碎片整理,有助于提高访问性能
文件系统是如何统计空间占用的?
通过统计FAT表的表项值来判断
- 空闲簇的表项值是0x0000;(FAT12是0x000,FAT16是0x0000,FAT32是0x00000000)
- 占用的簇的表项值是0xFFFF;(FAT12是0xFFF,FAT16是0xFFFF,FAT32是0x0FFFFFFF)
对于FAT16和FAT32,都是整字节占用的,利于统计,在统计FAT表时,每两个字节或者每四个字节,就判断一下当前的簇状态,然后累计计数,占用的簇数乘以簇大小,就是文件占用的空间大小。
而 FAT12,是每12位表示当前的簇状态,稍显麻烦。可能会有些程序,没做好占用空间大小统计的兼容。比如,在695N-3.1.0-SDK
中,如果有部分文件系统统计占用空间为0,但其它的正常。那说明是没有做好对FAT12文件系统的空间占用统计。需要打补丁
FAT16相对于FAT12,仅除了FAT表项占用的位数有差异,其它无论是在分区大小、簇大小都有优势,建议舍弃FAT12,选用FAT16。(FAT32文件系统本身的空间开销会明显偏大一些)
为什么SD卡、U盘等,没有存放文件,也被使用了一部分空间?
文件系统本身即占一部分资源,DBR、FAT表、根目录区;
另外,SD卡本身也有保留空间,
文件系统的擦写均衡是如何实现的?
通过FAT表,FAT表记录了各簇的使用情况,在增删改时,可以调整随机使用不同的簇,而不是从前往后、一直递增地使用簇。