单片机|RT-Thread文件系统详细说明(FatFs+DFS)

单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

文件系统引入 1.文件系统引入 在早期的嵌入式系统中,需要存储的数据比较少,数据类型也比较单一,往往使用直接在存储设备中的指定地址写入数据的方法来存储数据。然而随着嵌入式设备功能的发展,需要存储的数据越来越多,也越来越复杂,这时仍使用旧方法来存储并管理数据就变得非常繁琐困难。因此我们需要新的数据管理方式来简化存储数据的组织形式,这种方式就是我们接下来要介绍的文件系统。
文件系统是一套实现了数据的存储、分级组织、访问和获取等操作的抽象数据类型 (Abstract data type),是一种用于向用户提供底层数据访问的机制。文件系统通常存储的基本单位是文件,即数据是按照一个个文件的方式进行组织。
2.常用文件系统 ●FatFS :是专为小型嵌入式设备开发的一个兼容微软 FAT 格式的文件系统,采用 ANSI C 编写,具有良好的硬件无关性以及可移植性,是 RT-Thread 中最常用的文件系统类型,比如U盘的读写。
●DevFS :即设备文件系统,在 RT-Thread 操作系统中开启该功能后,可以将系统中的设备在 /dev 文件夹下虚拟成文件,使得设备可以按照文件的操作方式使用 read、write 等接口进行操作。
●LittleFs:一个为微控制器设计的小故障安全文件系统。特点是:低资源(ROM、RAM)消耗,掉电保护(适合随机掉电),擦写均衡(提高寿命)。
●Jffs2 :文件系统是一种日志闪存文件系统。主要用于 NOR 型闪存,基于 MTD 驱动层,特点是:可读写的、支持数据压缩的、基于哈希表的日志型文件系统,并提供了崩溃 / 掉电安全保护,提供写平衡支持等
●NFS :网络文件系统(Network File System)是一项在不同机器、不同操作系统之间通过网络共享文件的技术。在操作系统的开发调试阶段,可以利用该技术在主机上建立基于 NFS 的根文件系统,挂载到嵌入式设备上,可以很方便地修改根文件系统的内容。
3.虚拟文件系统引入 为 统一众多不同类型的文件系统,虚拟文件系统对实际文件系统进行抽象,采用统一的文件系统向用户提供统一的标准的文件操作接口:read、write、poll/select 等
RT-Thread DFS 1.DFS简介 DFS 是 RT-Thread 提供的虚拟文件系统组件,文件系统的名称使用类似 UNIX 文件、文件夹的风格,目录结构如下图所示:
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

在 RT-Thread DFS 中,文件系统有统一的根目录,使用 / 来表示。而在根目录下的 f1.bin 文件则使用 /f1.bin 来表示,2018 目录下的 f1.bin 目录则使用 /data/2018/f1.bin 来表示。即目录的分割符号是 /,这与 UNIX/Linux 完全相同,与 Windows 则不相同(Windows 操作系统上使用 \ 来作为目录的分割符)。
2.DFS架构 DFS 的层次架构如下图所示,主要分为 POSIX 接口层、虚拟文件系统层和设备抽象层。
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

POSIX接口层
POSIX 表示可移植操作系统接口,是 IEEE 为要在各种 UNIX 操作系统上运行的软件而定义的一系列 API 标准的总称。在类 Unix 系统中,普通文件、设备文件、网络文件描述符是同一种文件描述符,RT-Thread使用 DFS 来实现这种统一性(可用通用接口read、write、poll/select 等访问),一个 POSIX 兼容的操作系统编写的程序,可以在任何其它 POSIX 操作系统,因此可以很方便的将 Linux/Unix 的程序移植到 RT-Thread 操作系统上。
虚拟文件系统层
各种文件系统的具体实现,用户可以将具体的文件系统注册到 DFS 中,如 FatFS、RomFS、DevFS 等。
设备抽象层
不同文件系统类型是独立于存储设备驱动而实现的,因此把底层存储设备的驱动接口和文件系统对接起来之后,才可以正确地使用文件系统功能。设备抽象层将物理设备如 SD Card、SPI Flash、Nand Flash,抽象成符合文件系统能够访问的设备,例如 FAT 文件系统要求存储设备必须是块设备类型,再如通过闪存转换层(FTL-地址映射,磨损均衡,垃圾回收管理),使得NandFlash能够支持Flash 文件系统。
3.DFS 数据结构 DFS内部主要包含三个表:filesystem_operation_table、filesystem_table、fd_table,以及一个文件系统互斥锁fslock用于解决系统冲突。
filesystem_operation_table(文件系统操作表)
这个表每一个表项表示一个文件系统对应的一套操作函数及相关属性。不管是什么文件系统,其操作函数的形式都是一致的,通过dfs_register注册的文件系统操作会被加入这个表中。各个表项的结构定义如下图所示。
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

filesystem_table(文件系统表)
记录的是当前挂载的文件系统,其每一个表项表示的就是一个文件系统。通过dfs_mount挂载的文件系统操作会被加入这个表中,其中包含了文件系统被挂载到的设备dev_id,所以通过这个表就把文件系统操作与设备绑定了起来。各个表项的结构定义如下图所示。
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

fd_table(文件描述符表)
记录当前打开的文件集合,每一个表项表示一个打开的文件句柄。各个表项的结构定义如下图所示。
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

小结:
dfs框架主要围绕这三张表:添加、删除、查找、获取等操作,而真正的文件操作并不是在dfs框架内实现,而是由中间层的具体文件系统来实现,中间层的文件系统,比如elmfat文件系统通过向filesystem_operation_table注册其操作集,向filesystem_table挂载其文件系统,这样一来,系统就可以通过这两张表找到对应的具体操作函数了。
4.RT-Thread 虚拟文件系统使用步骤 【单片机|RT-Thread文件系统详细说明(FatFs+DFS)】虚拟文件系统的初始化过程一般按以下流程来进行。
● dfs框架初始化(dfs_init,最顶层初始化)
● 中间层具体文件系统初始化(比如elm_init,中间层具体文件系统初始化)
● 文件系统对应的具体设备驱动初始化(最底层设备驱动初始化,比如文 件系统在SPI FLASH,需要初始化对应SPI驱动和FLASH驱动)
● 挂载文件系统(dfs_mount,将各层具体关联起来)
● 当文件系统不使用时可以将它卸载(dfs_unmount)
**4.1 DFS框架初始化(最顶层)
**
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

由上源码可知,dfs框架初始化会对内部三张表以及资源(文件系统互斥量、工作路径、设备文件系统)进行初始化。
4.2 中间层具体文件系统初始化(中间层)
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

由上述代码可知,以RTT的elmfat文件系统为例,此步骤主要是向DFS框架注册elmfat文件系统的操作函数集,即向filesystem_operation_table注册elmfat文件系统的相关操作,还没具体操作。
4.3 文件系统对应的具体设备初始化(底层驱动)
接下来就是对文件系统对应具体设备驱动初始化了,比如spi flash,sd等,这笔分根据实际情况编写驱动,经过初始化后文件系统对应的具体设备就可以操作了(下一章实际例子说明)。
4.4 挂载文件系统
挂载文件系统在文件系统初始化步骤中是最后的一步,也是最重要的一步。挂载成功后用户就可以通过dfs_posix.c文件提供的文件接口函数对文件系统进行任意操作了。
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

由上可知,挂载的过程可以看成是将某某名的文件系统和某某名的设备驱动相关起来,并挂载到某个挂载点,这个挂载点即为这个文件系统的根目录。
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

如上图,dfs_mount通过操作之前那三张表使得设备驱动、文件系统和文件操作集合关联在一起。
4.5 DFS 总结
由以上内容可知,dfs并未提供具体的文件操作实现,而只是提供了一个框架,可以让各种各样的文件系统适配到这个框架上来,而保持顶层的dfs_posix接口不变,对于用户来说,只要知道posix文件接口就可以了,而不用关心内部实现细节。文件操作的真正实现细节是在各种文件系统中,RTT的elmfat就是其中一种,其相关操作由filesystem_operation_table来管理。RTT通过向filesysytem_operation_table注册文件操作集和向filesystem_table挂载文件系统来实现上层与中间层具体文件系统的脱离,从而实现dfs模块化,便于移植多种类型的文件系统
RT-Thread 文件系统使用 elm-FAT文件系统使用 (以此为例,此版本RTT已移植R0.12b)
使用前简单了解下RTT组件elmfat源码:
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

●ff.h/ff.c:FatFs核心源代码,无需修改。
●diskio:FatFs的IO层实现,官方提供了框架,需要实现几个函数。
●dfs_elm.c/dfs_elm.h:RTT对应FatFs注册到DFS中,FatFs的IO层实现和操作系统相关函数在该文件中。
●option:该目录下存放了提供了 Unicode 相关的编码转换函数。
●ffconf.h:该文件是 FatFs 的配置文件。用户需要根据自己的需求,来修改该文件中的各配置项。
rtconfig.h配置项说明汇总(详见头文件说明)
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

ffconf.h配置项说明汇总(详见头文件说明)
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

(1) 初始化DFS框架
dfs.c中INIT_PREV_EXPORT(dfs_init); 实现,无需带参,直接自动初始化即可。
(2) 注册具体文件系统
dfs_elm.c中INIT_COMPONENT_EXPORT(elm_init); 实现。需要设置以下结构体(主要是一些elm-FAT文件系统具体的文件操作和目录操作函数,具体说明可查看上面说到的文件系统操作表),通过dfs_register函数注册到DFS框架中。
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

(3)文件系统对应驱动实现elmFat(如用HC32F460官方已提供U盘驱动并适配disk接口)
由于FatFs模块是独立于平台和存储介质的文件系统层,因此它与物理设备(如存储卡、硬盘和任何类型的存储设备)完全分离。存储设备控制模块不 是FatFs模块的任何部分,需要用户提供。FatFs通过一个简单的媒体访问接口控制存储设备,主要接口如下。
●disk_initialize - 此函数用于初始化存储设备,并将其准备好进行一般读/写操作。函数成功后,返回值中的STA_NOINIT标志被清除。
●disk_status -当前驱动器状态获取。FatFs返回值只有STA_NOINIT(设备未初始化)和STA_PROTECT(设备写保护)。
●disk_read - 从存储设备的扇区读取数据(FatFs支持512到4096字节的扇区大小)
●disk_write - 写存储设备的扇区数据(FatFs支持512到4096字节扇区大小)
●disk_ioctl - 控制配置设备特定的功能和除通用读/写之外的其他功能。
(CTRL_SYNC确保写完成、GET_SECTOR_COUNT获取扇区数、GET_SECTOR_SIZE获取扇区大小、GET_BLOCK_SIZE获取块大小
●get_fattime - 获取当前时间
(4)文件系统挂载
函数原型:
int dfs_mount(const char *device_name,//已经格式化的块设备名称
const char *path,//挂载路径,即挂载点
const char *filesystemtype,//挂载的文件系统类型
unsigned long rwflag,//读写标志位
const void *data)//特定文件系统的私有数据

函数调用: if (dfs_mount(0, "/", "elm", 0, 0) == 0) { LOG_I("Filesystem initialized!"); } else { LOG_E("Failed to initialize filesystem!"); }

挂载成功 后面可直接进行文件和目录操作(API函数和Finsh命令)。
(5)文件管理通用函数
int open(const char *file, int flags, …); //打开文件
int close(int fd); //关闭文件
int read(int fd, void *buf, size_t len); //读文件内容
int write(int fd, const void *buf, size_t len); //向文件中写数据
int rename(const char *old, const char *new); //重命名文件
int stat(const char *file, struct stat *buf); //获取文件状态
int unlink(const char *pathname); //删除指定目录下文件
int fsync(int fildes); //同步内存中所有已经修改的文件数据到存储设备
int statfs(const char *path, struct statfs *buf); //查询文件系统相关信息
int select( int nfds,fd_set *readfd,fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); //监测I/O设备是否有事件发生。
(6)目录管理通用函数
//创建目录
int mkdir(const char path, mode_t mode);
//删除目录
int rmdir(const char pathname);
//打开目录
DIR
opendir(const char
name);
//关闭目录
int closedir(DIR* d);
//读取目录
struct dirent* readdir(DIR *d);
//获取目录流的读取位置
long telldir(DIR *d);
//设置下次读取目录的位置
void seekdir(DIR *d, off_t offset);
//重设目录流的读取位置
void rewinddir(DIR *d);
(7)Finsh命令
单片机|RT-Thread文件系统详细说明(FatFs+DFS)
文章图片

RT-Thread 文件系统常见问题 ●文件名或者文件夹名称显示不正常
检查是否开启了长文件名支持,DFS 功能配置小节。
●文件系统初始化失败
检查文件系统配置项目中的允许挂载的文件系统类型和数量是否充足。
●创建文件系统 mkfs 命令失败
检查存储设备是否存在,检查驱动错误。
●文件系统挂载失败
检查指定的挂载路径是否存在。文件系统可以直接挂载到根目录(“/”),但是如果想要挂载到其他路径上,如 (“/sdcard”)。需要确保(“/sdcard”)路径是存在的,否则需要先在根目录创建 sdcard 文件夹才能挂载成功。
检查是否在存储设备上创建了文件系统,如果存储设备上没有文件系统,需要使用 mkfs 命令在存储器上创建文件系统。
●一步步检查文件系统出现的问题
可以采用从底层到上层的方法来逐步排查问题。
首先检查存储设备是否注册成功,功能是否正常。
检查存储设备中是否创建了文件系统。
检查指定文件系统类型是否注册到 DFS 框架,经常要检查允许的文件系统类型和数量是否足够。
检查 DFS 是否初始化成功,这一步的初始化操作是纯软件的,因此出错的可能性不高。需要注意的是如果开启了组件自动初始化,就无需再次手动初始化

    推荐阅读