文件系统简介
早期的嵌入式系统由于需要存储的数据比较少,所以一般是直接在存储设备中指定地址写入数据的方法来存储数据。
随着嵌入式设备功能的发展,存储的数据越来越多,此时就需要一种新的数据管理方式——文件系统。
- 文件系统是一套实现了数据的存储、分级组织、访问和获取等操作的抽象数据类型 (Abstract data type),是一种用于向用户提供底层数据访问的机制。
- 文件系统通常存储的基本单位是文件,即数据是按照一个个文件的方式进行组织。
- 当文件比较多时,将导致文件繁多,不易分类、重名的问题。而文件夹作为一个容纳多个文件的容器而存在。
DFS介绍
DFS是 RT-Thread 提供的虚拟文件系统组件,全称为 Device File System,即设备虚拟文件系统,文件系统的名称使用类似 UNIX 文件、文件夹的风格,目录结构如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Irjumohy-1595147628635)(https://www.rt-thread.org/document/site/programming-manual/filesystem/figures/fs-dir.png)]
在RT-Thread DFS中,文件系统有统一的根目录,使用/来表示,目录的分隔符號是/,这与UNIX/Linux完全相同,与windows不同。
DFS架构
RT-Thread DFS组件的主要功能特点有:
- 为应用程序提供统一的 POSIX 文件和目录操作接口:read、write、poll/select 等。
-
支持多种类型的文件系统,如
FatFS、RomFS、DevFS
等,并提供普通文件、设备文件、网络文件描述符的管理。 - 支持多种类型的存储设备,如 SD Card、SPI Flash、Nand Flash 等。
DFS的层次架构如下图所示,主要分为POSIX接口层、虚拟文件系统层和设备抽象层。
POSIX接口层
- POSIX 表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写 POSIX)
- POSIX标准定义了操作系统应该为应用程序提供的接口标准
- 是IEEE为要在各种 UNIX操作系统上运行的软件而定义的一系列API标准的总称。
- 使用poll/select接口可以阻塞地同时探测一组支持非阻塞的I/O设备是否有事情发生,直到某一个设备触发了事件或者超过了指定的等待时间。
虚拟文件系统层
用户可以将具体的文件系统注册到 DFS 中,如 FatFS、RomFS、DevFS 等,下面介绍几种常用的文件系统类型:
- FatFS:专为小型嵌入式设备开发的兼容微软FAT格式的文件系统,具有良好的硬件无关性以及可移植性,是RT-Thread中最常用的文件系统类型;
- RomFS:是一种简单的、紧凑的、只读的文件系统,不支持动态擦写保存,按顺序存放数据,因而支持应用程序以XIP方式运行,在系统运行时节省RAM空间;
- Jffs2:一种日志闪存文件系统。主要用于 NOR 型闪存,基于 MTD 驱动层,可读写的、支持数据压缩的、基于哈希表的日志型文件系统,并提供了崩溃 / 掉电安全保护,提供写平衡支持等。
- DevFS:即设备文件系统,开启该功能后,可以将系统中的设备在 /dev 文件夹下虚拟成文件,使得设备可以按照文件的操作方式使用 read、write 等接口进行操作。
- NFS 网络文件系统:一项在不同机器、不同操作系统之间通过网络共享文件的技术。
- UFFS:Ultra-low-cost Flash File System(超低功耗的闪存文件系统)的简称,国人开发的、专为嵌入式设备等小内存环境中使用 Nand Flash 的开源文件系统。
挂载管理
文件系统的初始化过程一般分为以下几个步骤:
- 初始化DFS组件
- 初始化具体类型的文件系统
- 在存储器上创建块设备
- 格式化块设备
- 挂载块设备到DFS目录中
- 当文件系统不再使用,可以将它卸载
初始化DFS组件
DFS 组件的的初始化是由 dfs_init() 函数完成。
注册文件系统
int dfs_register(const struct dfs_filesystem_ops *ops);
该函数不需要用户调用,他会被不同文件系统的初始化函数调用,如 elm-FAT 文件系统的初始化函数elm_init()。开启对应的文件系统后,如果开启了自动初始化(默认开启),文件系统初始化函数也将被自动调用。注册过程如下图所示:
将存储设备注册为块设备
因为只有块设备才可以挂载到文件系统上,因此需要在存储设备上创建所需的块设备。如果存储设备是 SPI Flash,则可以使用 “串行 Flash 通用驱动库 SFUD” 组件,它提供了各种 SPI Flash 的驱动,并将 SPI Flash 抽象成块设备用于挂载,注册块设备过程如下图所示:
格式化文件系统
注册了块设备之后,还需要在块设备上创建指定类型的文件系统,也就是格式化文件系统。可以使用 dfs_mkfs() 函数对指定的存储设备进行格式化,创建文件系统,格式化文件系统的接口如下所示:
int dfs_mkfs(const char * fs_name, const char * device_name);
以 elm-FAT 文件系统格式化块设备为例,格式化过程如下图所示:
挂载文件系统
在RT-Thread中,挂载是指将一个存储设备挂接到一个已存在的路径上。我们要访问存储设备中的文件,必须将文件所在的分区挂载到一个已存在的路径上,然后通过这个路径来访问存储设备。挂载文件系统的接口如下所示:
int dfs_mount(const char *device_name,
const char *path,
const char *filesystemtype,
unsigned long rwflag,
const void *data);
如果只有一个存储设备,则可以直接挂载到根目录/上。
卸载文件系统
当某个文件系统不需要再使用了,就可以将它卸载掉,接口函数如下所示:
int dfs_unmount(const char *specialfile);
文件管理
对文件的操作一般都要基于文件描述符fd,如下图所示:
打开和关闭文件
打开或创建一个文件可以调用下面的open()函数:
int open(const char *file, int flags, ...);
flags: 指定打开文件的方式,O_RDONLY\O_WRONLY…
int close(int fd);
读写数据
int read(int fd, void *buf, size_t len);
向文件中写入数据可使用write()函数:
int write(int fd, const void *buf, size_t len);
取得状态
获取文件状态可使用下面的stat()函数:
int stat(const char *file, struct stat *buf);
删除文件
删除指定目录下的文件可使用unlink()函数:
int unlink(const char *pathname);
同步文件数据到存储设备
同步内存中所有已修改的文件数据到储存设备可使用fsync()函数:
int fsync(int fildes);
查询文件系统相关信息
int statfs(const char *path, struct statfs *buf);
监视I/O设备状态
int select( int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
目录管理
DFS配置选项
文件系统在 menuconfig 中具体配置路径如下:
RT-Thread Components --->
Device virtual file system --->
elm-FatFs文件配置选项
在menuconfig中开启elem-FatFs文件系统后可对elm-FatFs做进一步配置,配置菜单描述及对应的宏定义可以参考相关源码。
更多配置
FatFs 本身支持非常多的配置选项,配置非常灵活。下面文件为 FatFs 的配置文件,可以修改这个文件来定制 FatFs。
components/dfs/filesystems/elmfat/ffconf.h
参考资料