在初期的嵌入式系统中,须要储存的数据比较少,数据类型也比较单一,常常使用直接在储存设备中的指定地址写入数据的方式来储存数据。但是随着嵌入式设备功能的发展,须要储存的数据越来越多,也越来越复杂,这时仍使用旧方式来储存并管理数据就显得十分繁杂困难。为此我们需要新的数据管理方法来简化储存数据的组织方式,这些方法就是我们接出来要介绍的文件系统。
文件系统是一套实现了数据的储存、分级组织、访问和获取等操作的具象数据类型(Abstractdatatype),是一种用于向用户提供底层数据访问的机制。文件系统一般储存的基本单位是文件,即数据是根据一个个文件的形式进行组织。
2.常用文件系统
●FatFS:是专为大型嵌入式设备开发的一个兼容谷歌FAT格式的文件系统,采用ANSIC编撰,具有良好的硬件无关性以及可移植性,是RT-Thread中最常用的文件系统类型,例如U盘的读写。
●DevFS:即设备文件系统,在RT-Thread操作系统中开启该功能后,可以将系统中的设备在/dev文件夹下虚拟成文件,致使设备可以根据文件的操作方法使用read、write等插口进行操作。
●LittleFs:一个为微控制器设计的小故障安全文件系统。特征是:低资源(ROM、RAM)消耗,掉电保护(适宜随机掉电),擦写均衡(提升寿命)。
●Jffs2:文件系统是一种日志闪存文件系统。主要用于NOR型闪存,基于MTD驱动层,特征是:可读写的、支持数据压缩的、基于哈希表的日志型文件系统,并提供了崩溃/掉电安全保护linux内存管理,提供写平衡支持等
●NFS:网路文件系统(NetworkFileSystem)是一项在不同机器、不同操作系统之间通过网路共享文件的技术。在操作系统的开发调试阶段,可以借助该技术在主机上构建基于NFS的根文件系统,挂载到嵌入式设备上,可以很便捷地更改根文件系统的内容。
3.虚拟文件系统引入
为统一诸多不同类型的文件系统,虚拟文件系统对实际文件系统进行具象,采用统一的文件系统向用户提供统一的标准的文件操作插口:read、write、poll/select等
RT-ThreadDFS1.DFS简介
DFS是RT-Thread提供的虚拟文件系统组件,文件系统的名称使用类似UNIX文件、文件夹的风格,目录结构如右图所示:
在RT-ThreadDFS中,文件系统有统一的根目录,使用/来表示。而在根目录下的f1.bin文件则使用/f1.bin来表示,2018目录下的f1.bin目录则使用/data/2018/f1.bin来表示。即目录的分割符号是/,这与UNIX/Linux完全相同,与Windows则不相同(Windows操作系统上使用来作为目录的分割符)。
2.DFS构架
DFS的层次构架如右图所示,主要分为POSIX插口层、虚拟文件系统层和设备具象层。
POSIX插口层
POSIX表示可移植操作系统插口,是IEEE为要在各类UNIX操作系统上运行的软件而定义的一系列API标准的合称。在类Unix系统中,普通文件、设备文件、网络文件描述符是同一种文件描述符,RT-Thread使用DFS来实现这些统一性(可用通用插口read、write、poll/select等访问),一个POSIX兼容的操作系统编撰的程序,可以在任何其它POSIX操作系统,因而可以很便捷的将Linux/Unix的程序移植到RT-Thread操作系统上。
虚拟文件系统层
各类文件系统的具体实现,用户可以将具体的文件系统注册到DFS中,如FatFS、RomFS、DevFS等。
设备具象层
不同文件系统类型是独立于储存设备驱动而实现的,因而把底层储存设备的驱动插口和文件系统对接上去以后,才可以正确地使用文件系统功能。设备具象层将化学设备如SDCard、SPIFlash、NandFlash,具象成符合文件系统才能访问的设备,比如FAT文件系统要求储存设备必须是块设备类型,再如通过闪存转换层(FTL-地址映射,锈蚀均衡,垃圾回收管理),致使NandFlash才能支持Flash文件系统。
3.DFS数据结构
DFS内部主要包含三个表:filesystem_operation_table、filesystem_table、fd_table,以及一个文件系统互斥锁fslock用于解决系统冲突。
filesystem_operation_table(文件系统操作表)
这个表每一个表项表示一个文件系统对应的一套操作函数及相关属性。不管是哪些文件系统,其操作函数的方式都是一致的,通过dfs_register注册的文件系统操作会被加入这个表中。各个表项的结构定义如右图所示。
filesystem_table(文件系统表)
记录的是当前挂载的文件系统,其每一个表项表示的就是一个文件系统。通过dfs_mount挂载的文件系统操作会被加入这个表中,其中包含了文件系统被挂载到的设备dev_id,所以通过这个表就把文件系统操作与设备绑定了上去。各个表项的结构定义如右图所示。
fd_table(文件描述符表)
记录当前打开的文件集合,每一个表项表示一个打开的文件句柄。各个表项的结构定义如右图所示。
小结:
dfs框架主要围绕这三张表:添加、删除、查找、获取等操作,而真正的文件操作并不是在dfs框架内实现,而是由中间层的具体文件系统来实现,中间层的文件系统,例如elmfat文件系统通过向filesystem_operation_table注册其操作集,向filesystem_table挂载其文件系统,这样一来,系统就可以通过这两张表找到对应的具体操作函数了。
4.RT-Thread虚拟文件系统使用步骤
虚拟文件系统的初始化过程通常按以下流程来进行。
●dfs框架初始化(dfs_init,最顶楼初始化)
●中间层具体文件系统初始化(例如elm_init,中间层具体文件系统初始化)
●文件系统对应的具体设备驱动初始化(最底层设备驱动初始化,例如文件系统在SPIFLASH,须要初始化对应SPI驱动和FLASH驱动)
●挂载文件系统(dfs_mount,将各层具体关联上去)
●当文件系统不使用时可以将它卸载(dfs_unmount)
**4.1DFS框架初始化(最顶楼)
**
由上源码可知,dfs框架初始化会对内部三张表以及资源(文件系统互斥量、工作路径、设备文件系统)进行初始化。
4.2中间层具体文件系统初始化(中间层)
由上述代码可知,以RTT的elmfat文件系统为例,此步骤主要是向DFS框架注册elmfat文件系统的操作函数集,即向filesystem_operation_table注册elmfat文件系统的相关操作,还没具体操作。
4.3文件系统对应的具体设备初始化(底层驱动)
接出来就是对文件系统对应具体设备驱动初始化了,例如spiflash,sd等,这笔分依据实际情况编撰驱动,经过初始化后文件系统对应的具体设备就可以操作了(下一章实际事例说明)。
4.4挂载文件系统
挂载文件系统在文件系统初始化步骤中是最后的一步,也是最重要的一步。挂载成功后用户就可以通过dfs_posix.c文件提供的文件插口函数对文件系统进行任意操作了。
由上可知,挂载的过程可以看成是将某甲名的文件系统和某甲名的设备驱动相关上去unix的文件系统,并挂载到某个挂载点,这个挂载点即为这个文件系统的根目录。
如上图,dfs_mount通过操作之前那三张表致使设备驱动、文件系统和文件操作集合关联在一起。
4.5DFS总结
由以上内容可知,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源码:
●ff.h/ff.c:FatFs核心源代码,无需更改。
●diskio:FatFs的IO层实现,官方提供了框架,须要实现几个函数。
●dfs_elm.c/dfs_elm.h:RTT对应FatFs注册到DFS中,FatFs的IO层实现和操作系统相关函数在该文件中。
●option:该目录下储存了提供了Unicode相关的编码转换函数。
●ffconf.h:该文件是FatFs的配置文件。用户须要依照自己的需求redhat linux下载,来更改该文件中的各配置项。
rtconfig.h配置项说明汇总(详见头文件说明)
ffconf.h配置项说明汇总(详见头文件说明)
(1)初始化DFS框架
dfs.c中INIT_PREV_EXPORT(dfs_init);实现,无需带参,直接手动初始化即可。
(2)注册具体文件系统
dfs_elm.c中INIT_COMPONENT_EXPORT(elm_init);实现。须要设置以下结构体(主要是一些elm-FAT文件系统具体的文件操作和目录操作函数,具体说明可查看前面说到的文件系统操作表),通过dfs_register函数注册到DFS框架中。
(3)文件系统对应驱动实现elmFat(如用HC3232FF460460官方已提供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)文件系统挂载
函数原型:
intdfs_mount(constchar*device_name,//早已低格的块设备名称
constchar*path,//挂载路径,即挂载点
constchar*filesystemtype,//挂载的文件系统类型
unsignedlongrwflag,//读写标志位
constvoid*data)//特定文件系统的私有数据
函数调用:
if (dfs_mount(0, "/", "elm", 0, 0) == 0)
{
LOG_I("Filesystem initialized!");
}
else
{
LOG_E("Failed to initialize filesystem!");
}
挂载成功前面可直接进行文件和目录操作(API函数和Finsh命令)。
(5)文件管理通用函数
intopen(constchar*file,intflags,…);//打开文件
intclose(intfd);//关掉文件
intread(intfd,void*buf,size_tlen);//读文件内容
intwrite(intfd,constvoid*buf,size_tlen);//向文件中写数据
intrename(constchar*old,constchar*new);//重命名文件
intstat(constchar*file,structstat*buf);//获取文件状态
intunlink(constchar*pathname);//删掉指定目录下文件
intfsync(intfildes);//同步显存中所有早已更改的文件数据到储存设备
intstatfs(constchar*path,structstatfs*buf);//查询文件系统相关信息
intselect(intnfds,fd_set*readfd,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);//检测I/O设备是否有风波发生。
(6)目录管理通用函数
//创建目录
intmkdir(constcharpath,mode_tmode);
//删掉目录
intrmdir(constcharpathname);
//打开目录
DIRopendir(constcharname);
//关掉目录
intclosedir(DIR*d);
//读取目录
structdirent*readdir(DIR*d);
//获取目录流的读取位置
longtelldir(DIR*d);
//设置上次读取目录的位置
voidseekdir(DIR*d,off_toffset);
//重设目录流的读取位置
voidrewinddir(DIR*d);
(7)Finsh命令
RT-Thread文件系统常见问题
●文件名或则文件夹名称显示不正常
检测是否开启了长文件名支持,DFS功能配置小节。
●文件系统初始化失败
检测文件系统配置项目中的容许挂载的文件系统类型和数目是否充足。
●创建文件系统mkfs命令失败
检测储存设备是否存在,检测驱动错误。
●文件系统挂载失败
检测指定的挂载路径是否存在。文件系统可以直接挂载到根目录(“/”),而且假如想要挂载到其他路径上,如(“/sdcard”)。须要确保(“/sdcard”)路径是存在的,否则须要先在根目录创建sdcard文件夹能够挂载成功。
检测是否在储存设备上创建了文件系统,假若储存设备上没有文件系统,须要使用mkfs命令在储存器上创建文件系统。
●一步步复查文件系统出现的问题
可以采用从底层到下层的方式来逐渐排查问题。
首先检测储存设备是否注册成功,功能是否正常。
检测储存设备中是否创建了文件系统。
检测指定文件系统类型是否注册到DFS框架,常常要检测容许的文件系统类型和数目是否足够。
检测DFS是否初始化成功,这一步的初始化操作是纯软件的unix的文件系统,因而出错的可能性不高。须要注意的是假如开启了组件手动初始化,就无需再度自动初始化