下冰雹 · 2020年10月30日

FatFs模块系统应用指南

本文使用的FatFs版本为:V0.12b(2016年9月4发布)

1.如何移植
1.1首要考虑

FatFs模块移植基于以下假设条件:

  • ANSI C: 编译器应遵循ANSI C。FatFs模块是以ANSI C(C89标准)语言编写的中间件软件,它不依赖任何硬件平台,只要相应的编译器支持ANSI C。
  • 整形类型大小: FatFs模块假设char/short/long类型变量大小为8/16/32位、假设int类型变量为16或32位。这些内容定义在文件integer.h中,对大多数编译器而言,默认的定义都不会有什么问题。如果与当前的定义有冲突,你必须在这个文件中小心的解决掉。
1.2系统组织结构

图1-1给出了嵌入式系统FatFs模块典型的关系依赖图。图中假设使用SPI接口访问SD卡,蓝色区域表示FatFs模块,注意FatFs模块并不包含也从不关心绿色区域的底层磁盘IO层。对于使用SPI访问SD卡的应用,FatFs官网提供的例程中,有使用硬件SPI和模拟SPI的例程。
image.png
图1-1:FatFs关系依赖图

如果只有一个磁盘设备,并且使用FatFs提供的磁盘接口,则不需要其它模块,只管编写应用程序即可,如图1-2(a)所示。如果使用多种不同接口,则需要编写另外一层软件,用来在驱动器和FatFs之间翻译命令和数据流,如图1-2(b)所示。
注意图1-2中灰色部分属于底层磁盘I/O层,FatFs从来不关心这一层是如何实现的!
image.png
图1-2:单个设备和多个设备系统

1.3移植必须实现的函数

仅需要提供FatFs模块需要的底层磁盘I/O函数,其它的均不是必须的。大部分函数不是必须的,例如,磁盘写函数在配置为只读模式时就不是必须的。表1-1给出函数是否需要与配置选项的关系。
表1-1:移植时需要的函数列表
image.png

2.限制
  • 支持的文件系统类型:FAT12、FAT16、FAT32(r0.0)和exFAT(r1.0,从版本R0.12开始支持)
  • 打开的文件数目:无限制(取决于可用内存)
  • 卷数量:最大为10
  • 卷大小:最大为2TB(512字节/扇区)
  • 单个文件大小:FAT卷最大为4GB,exFAT卷几乎无限制(理论1610241024TB)
  • 簇大小:FAT卷最大为128扇区,exFAT卷最大为16MB
  • 扇区大小:512、1024、2048、4096字节

什么是exFAT?

exFAT(Extended File Allocation Table File System,扩展FAT)是Microsoft在Windows Embeded 5.0以及以上版本中引入的一种适合于闪存的文件系统,为了解决FAT32文件系统不支持大于等于4GB的单个文件而推出。
卷、簇和扇区?

卷(逻辑驱动器)是文件系统为管理物理磁盘而提出的。一个物理磁盘可以有多个卷,多个物理磁盘也可以映射到1个卷上。我们常说将硬盘分为4个区,对于文件系统而言,实际上它就要管理4个卷。

簇可以简单的理解为是一组扇区,是文件系统管理磁盘数据区的最小单位,一个文件即便是0字节,也一定会占用一个簇。当格式化磁盘时,会有“分配单元大小”选项,默认选择为4096字节,这个大小就是簇大小。

扇区是物理磁盘的最小单位,在制造时大小已经确定,对于U盘、SD卡,一般为512字节。

3.内存使用量

内存使用量大小取决于配置选项,表3-1显示了在特定配置下的ROM和RAM使用情况(FatFs官方网站上有更多的平台供参考,这里截取了其中的常用平台)。在此之前,先来了解一下表3-1会遇到的配置选项和缩写。
在表3-1中:

  • V表示宏_VOLUMES定义的值(卷的个数);
  • F表示打开的文件数。
  • 宏_FS_READONLY表示读写限制,0表示读/写(R/W),1表示只读(R/O);
  • 宏_FS_MINIMIZE用于函数裁剪,0表示全功能版(Full),3为最小功能(Min);
  • 宏_FS_TINY用于模式配置,0表示正常模式,1表示微型模式。

表3-1:不同平台、不同配置下,FatFs模块ROM和RAM消耗参考
image.png

4.功能裁剪

表4-1列出了FatFs的所有API函数以及函数在不同配置下的裁剪情况。
表4-1:API函数和配置宏关系
image.png

5.长文件名

FatFs模块从版本0.07开始支持长文件名。除了函数f_readdir函数外,FatFs模块的其它函数对短文件名(SFN)和长文件名(LFN)是透明的。默认情况下,长文件名被禁止。要使能长文件名,需要设置宏_USE_LFN为1、2或3,并且需要向项目中增加文件option/unicode.c。LFN需要一些额外的缓存,缓存的大小通过宏_MAX_LFN来设定。长文件名最多可能需要255个字符,所以要完全符合长文件名的特性,则至少需要分配255字节的缓存区。如果分配的缓存区不足以放下文件名,则文件函数会返回一个FR_INVALID_NAME的错误值。当使能了长文件名特性并且使能了可重入特性(宏_FS_REENTRANT==1),_USE_LFN必须设置为2或3。在这种情况下,文件函数访问的文件名工作区缓存位域栈或堆中。工作缓存区占用(_MAX_LFN+1)*2字节,如果使能exFAT文件系统,需要再增加608字节缓存。
表5-1列出了在Cortex-M3硬件平台、GCC编译器环境,使用长文件名时不同内码表增加的ROM大小。
image.png

当LFN特性被使能,ROM大小的增长取决于所用的内码表。上表显示了使用LFN特性后几种内码表对应的ROM增长大小。当使用日文、中文和汉语时,会有成千上万的字符。不幸的是,它们需要一个巨大的OEM-Unicode双向转换表,这会使得模块的大小大幅度的增加,就像表中所给出的数据所示。因此,FatFs的双字节字符集的长文件名特性对于大多数的8位微控制器是不现实的。(这也是为什么作者在很长的时间内都没有兴趣实现LFN特性的原因)。
注:FAT文件系统中的长文件名特性微软的专利。当在一个商业应用中使能LFN时,可能需要向微软申请许可。

6.Unicode API

默认情况下,FatFs的API函数使用ANSI/OEM代码集,即使在使能长文件名的情况下也是如此。但是FatFs也可以切换到Unicode代码集,只需要_LFN_UNICODE==1。使用ANSI/OEM代码集时字符串数据类型使用char,使用Unicode代码集时,字符串数据类型使用WCHAR(UTF-16)。《FatFs路径名称规则》一文提供了关于Unicode更详细的信息。

7.exFAT文件系统

exFAT(Microsoft's Extended File Allocation Table)文件系统是微软在广泛使用的嵌入式操作系统中为替换FAT文件系统而开发的。它已经被SDA(SD卡协会)所采纳并推荐用户在高容量SD卡(>32G)中使用。因此exFAT即将成为可移动存储器的标准文件系统。
对于FAT32系统来说,单个文件被限制到最大4GB,但exFAT文件系统单个文件大小可以超过256TB!此外相对于FAT文件系统,exFAT文件系统在文件系统开销、特别是文件分配延时上都有降低,能够改善文件写效率。但是,当前FatFs版本软件也存在一个问题,就是在文件写入扇区非对齐时不如FAT文件系统吞吐量大。可以使用f_expand函数预先分配一个连续块来避免这个问题。
注意exFAT属于微软专利!FatFs模块的exFAT功能是根据US. Pat. App. Pub. No. 2009/0164440 A1来实现的。可以通过配置宏使能或禁用exFAT功能。当在商业产品上使能exFAT功能后,需要向微软申请许可。
特别注意,使能exFAT后,不再兼容C89,因为需要64位整形类型。

8.可重入性

不同卷上的文件操作总是可重入的(使能长文件名并且使用静态工作缓冲区除外),并且可以同时工作。 相同卷上的文件操作是不可重入的,但是也可以通过配置_FS_REENTRANT选项使用线程保护。在这种情况下,需要OS的同步对象控制函数,必须增加函数ff_cre_syncobj、ff_del_syncobj、ff_req_grant和ff_rel_grant到工程。
当卷被某个任务使用时,这时如果另外一个任务调用文件函数,那么访问会被阻塞等待,直到任务离开文件函数。如果等待超过由宏_TIMEOUT定义的时间,文件函数调用会终止,并返回FR_TIMEOUT。超时特性在某些RTOS下可能不被支持。
函数f_mount、f_mkfs和f_fdisk是一个例外。这些卷管理函数在同一个卷或物理磁盘上始终是不可重入的。当使用这些函数,其它任务要避免访问此卷。
注:这个段落只是描述FatFs模块本身的可重入性,不假定底层磁盘I/O模块具有可重入性。

9.重复的访问文件(Duplicated File Access)

默认情况下,FatFs不支持以读/写方式重复打开文件,允许以只读方式重复打开文件。在写模式下禁止重复打开文件,禁止重命名、删除已经打开的文件,否则该卷上的FAT结构体会崩溃。还有,当前目录也不能被删除。
文件锁控制可以通过宏_FS_LOCK > 1使能。这个宏定义的值表示在文件锁控制下,多少文件、子目录可以同时打开。在这种情况下,任何对打开对象的非法操作(比如移除、重命名)会被拒绝并返回FR_LOCKED错误码,函数f_open和f_opendir是一个特例,它们执行失败返回FR_TOO_MANY_OPEN_FILES错误码。

10. 高性能文件访问

在小型嵌入式系统中,为了获得更好的读写文件性能,应用程序编程应该考虑如何处理以合乎FatFs模块的特性。f_read函数读取磁盘上的文件数据按照下图中的序列:
image.png
图10-1:读数据,扇区未对齐(小块数据)
image.png
图10-2:读数据,扇区未对齐(大块数据)
image.png
图10-3:读数据,扇区对齐

文件I/O缓冲区用来读写部分扇区数据。文件I/O缓冲区可以是每个文件对象都有的私有扇区缓存,也可以是共享整个文件系统对象下的扇区缓存。宏_FS_TINY用来确定文件I/O缓冲区是使用文件对象缓存还是使用文件系统对象缓存。当_FS_TINY==1时,表示使用微型模式,文件I/O缓冲区共享文件系统对象缓存,文件数据传输和FAT/目录访问都使用文件系统缓存。这样每个文件对象可以减少_MAX_SS个字节数据内存。这种小内存配置的缺点是:缓存在文件I/O缓冲区的FAT数据在文件数据传输时将会丢失,并且在每一个簇的边界都要重新加载(意味着更长的执行时间)。 无论如何,这适合于大多数的应用场合,只需少量内存消耗就能获得不错的性能。

图10-1描述了文件当中未对齐扇中的部分扇区数据通过文件I/O缓存进行传送过程。大块数据传送见图10-2,图10-2中的中间部分的一个或多个扇区数据传送直接使用了应用程序缓存。图10-3描述了按扇区对齐的数据传送。在这种情况下,文件I/O缓存不使用。直接传送,通过disk_read函数一次最大程度的读取尽量多的扇区范围,但是多扇区数据传输从不越过簇边界,即使它们是连续的。

因此,尽量在读写时保证扇区对齐,避免传输缓存数据,这样可以改善读写性能。除了这个外,使用扇区对齐读写,在配置成小内存模式下,在文件传送时,不会冲刷掉缓存的FAT数据,这样就可以获得和正常模式下同样的性能同时又具有小的内存消耗。

11. 使用Flash存储器的注意事项

使用Flash存储器时,例如SDC和CFC,为了获得最大写性能,必须考虑它们的特性。

11.1 使用多扇区写

image.png
图11-1:多扇区/单扇区对比
使用Flash存储器时,一个扇区一个扇区的写入最影响写入数据的吞吐率。如果每次能写入多个扇区,会大大提高写入数据的吞吐率,如图11-1所示。存储器接口时钟越快,影响越大,并且通常吞吐率会增大10倍以上。图11-2显示这种关系,在存储器接口速率相同的情况下,比如使用8GB SDHC类型的SD卡,每次写16K(32扇区)比每次写100字节(1扇区)吞吐率高了38.8倍。写事件的次数会影响存储器的寿命,当使用小数据块时,可能会多次访问同一扇区,比如每次写入100字节,会写5~6次才能将一个扇区写满,因此从保护存储器使用寿命上考虑,也要尽可能的使用大块数据。
image.png
图11-2:读写吞吐率与写入块大小、存储器接口速率关系
因此应用程序应该尽可能将数据组成一个大数据块再进行写入操作。理想的写入方式是每次写入是以扇区对齐的,并且写入数据大小等于扇区的倍数,最好是等于簇大小。当然,应用层到介质层必须要支持多扇区写特性,可是多数开源磁盘驱动缺少这块。
注:Fatfs模块和它的例程磁盘驱动支持多扇区读写。

12.强制擦除存储器

当通过f_unlink函数移除一个文件时,FatFs会在FAT表中把文件占用的簇数据空间标记为"空",但是并不会对包含文件数据的扇区做任何擦除操作,所以文件数据仍然存在着。如果要在移除文件时将文件数据强制删除,存储器上的空闲块会增加。这样,在下次向这个数据块写数据时,就可以跳过内部擦除操作。这样可能会提高写入性能。要使能这个特性,设置_USE_TRIM为1。注:这个特性是Flash存储器内部处理程序所期望的。大多数应用并不需要这个特性,并且当移除大文件时,f_unlink函数可能会花费很长时间。

13.临界区

写FAT卷时,如果因为意外故障导致写操作被中断,比如突然断电、错误的移除磁盘以及不可恢复的磁盘错误,可能会导致FAT结构体崩溃。图13-1和图13-2说明FatFs应用程序的临界区。
image.png
图13-1:长临界区
image.png
图13-2::最小化的临界区
上述红色区域代码执行时被意外终止,正在执行的对象更改可能会丢失。当上述黄色区域代码执行时被意外终止,下面列出的一个或多个可能会发生:

  • 正在改写的文件数据被损坏
  • 添加的文件恢复到初始状态
  • 丢失新建的文件
  • 新创建的文件或者覆写的内容丢失
  • 由于丢失簇链,磁盘性能下降

如果以只读模式打开文件,是不会出现上述情况的。为了使数据丢失风险最小化,临界区应尽可能的短,适当的使用f_sync函数可以缩短临界区,如图13-2所示的那样。

作者:朱工
首发博客:https://freertos.blog.csdn.net/article/details/52893131
关注FreeRTOS从基础到高级专栏,即时收取FreeRTOS系列文章。
推荐阅读
关注数
3246
内容数
54
介绍FreeRTOS的基本功能,移植与使用。主要介绍FreeRTOS的裁剪、任务、内存管理、队列、信号量、任务通知等基本组成,看完可以会用FreeRTOS,高级篇会深入介绍FreeRTOS的实现细节、方法、技巧。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息