虚拟文件系统
在早期的嵌入式系统中,需要存储的数据比较少,数据类型也比较单一,往往使用直接在存储设备中的指定地址写入数据的方法来存储数据。然而随着嵌入式设备功能的发展,需要存储的数据越来越多,也越来越复杂,这时仍使用旧方法来存储并管理数据就变得非常繁琐困难。因此我们需要新的数据管理方式来简化存储数据的组织形式,这种方式就是我们接下来要介绍的文件系统。
文件系统是一套实现了数据的存储、分级组织、访问和获取等操作的抽象数据类型 (Abstract data type),是一种用于向用户提供底层数据访问的机制。文件系统通常存储的基本单位是文件,即数据是按照一个个文件的方式进行组织。当文件比较多时,将导致文件繁多,不易分类、重名的问题。而文件夹作为一个容纳多个文件的容器而存在。
本章讲解 RT-Thread 文件系统相关内容,带你了解 RT-Thread 虚拟文件系统的架构、功能特点和使用方式。
DFS 简介
DFS 是 RT-Thread 提供的虚拟文件系统组件,全称为 Device File System,即设备虚拟文件系统,文件系统的名称使用类似 UNIX 文件、文件夹的风格,目录结构如下图所示:
在 RT-Thread DFS 中,文件系统有统一的根目录,使用 /
来表示。而在根目录下的 f1.bin 文件则使用 /f1.bin
来表示,2018 目录下的 f1.bin
文件则使用 /data/2018/f1.bin
来表示。即目录的分割符号是 /
,这与 UNIX/Linux 完全相同,与 Windows 则不相同(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 标准的总称。
POSIX 标准意在期望获得源代码级别的软件可移植性。换句话说,为一个 POSIX 兼容的操作系统编写的程序,应该可以在任何其它 POSIX 操作系统(即使是来自另一个厂商)上编译执行。RT-Thread 支持 POSIX 标准接口,因此可以很方便的将 Linux/Unix 的程序移植到 RT-Thread 操作系统上。
在类 Unix 系统中,普通文件、设备文件、网络文件描述符是同一种文件描述符。而在 RT-Thread 操作系统中,使用 DFS 来实现这种统一性。有了这种文件描述符的统一性,我们就可以使用 poll/select 接口来对这几种描述符进行统一轮询,为实现程序功能带来方便。
使用 poll/select 接口可以阻塞地同时探测一组支持非阻塞的 I/O 设备是否有事件发生(如可读,可写,有高优先级的错误输出,出现错误等等),直至某一个设备触发了事件或者超过了指定的等待时间。这种机制可以帮助调用者寻找当前就绪的设备,降低编程的复杂度。
虚拟文件系统层
用户可以将具体的文件系统注册到 DFS 中,如 FatFS、RomFS、DevFS 等,下面介绍几种常用的文件系统类型:
-
FatFS 是专为小型嵌入式设备开发的一个兼容微软 FAT 格式的文件系统,采用 ANSI C 编写,具有良好的硬件无关性以及可移植性,是 RT-Thread 中最常用的文件系统类型。
-
传统型的 RomFS 文件系统是一种简单的、紧凑的、只读的文件系统,不支持动态擦写保存,按顺序存放数据,因而支持应用程序以 XIP(execute In Place,片内运行) 方式运行,在系统运行时, 节省 RAM 空间。
-
Jffs2 文件系统是一种日志闪存文件系统。主要用于 NOR 型闪存,基于 MTD 驱动层,特点是:可读写的、支持数据压缩的、基于哈希表的日志型文件系统,并提供了崩溃 / 掉电安全保护,提供写平衡支持等。
-
DevFS 即设备文件系统,在 RT-Thread 操作系统中开启该功能后,可以将系统中的设备在 /dev 文件夹下虚拟成文件,使得设备可以按照文件的操作方式使用 read、write 等接口进行操作。
-
NFS 网络文件系统(Network File System)是一项在不同机器、不同操作系统之间通过网络共享文件的技术。在操作系统的开发调试阶段,可以利用该技术在主机上建立基于 NFS 的根文件系统,挂载到嵌入式设备上,可以很方便地修改根文件系统的内容。
-
UFFS 是 Ultra-low-cost Flash File System(超低功耗的闪存文件系统)的简称。它是国人开发的、专为嵌入式设备等小内存环境中使用 Nand Flash 的开源文件系统。与嵌入式中常使用的 Yaffs 文件系统相比具有资源占用少、启动速度快、免费等优势。
设备抽象层
设备抽象层将物理设备如 SD Card、SPI Flash、Nand Flash,抽象成符合文件系统能够访问的设备,例如 FAT 文件系统要求存储设备必须是块设备类型。
不同文件系统类型是独立于存储设备驱动而实现的,因此把底层存储设备的驱动接口和文件系统对接起来之后,才可以正确地使用文件系统功能。
挂载管理
文件系统的初始化过程一般分为以下几个步骤:
- 初始化 DFS 组件。
- 初始化具体类型的文件系统。
- 在存储器上创建块设备。
- 格式化块设备。
- 挂载块设备到 DFS 目录中。
- 当文件系统不再使用,可以将它卸载。
初始化 DFS 组件
DFS 组件的的初始化是由 dfs_init() 函数完成。dfs_init() 函数会初始化 DFS 所需的相关资源,创建一些关键的数据结构, 有了这些数据结构,DFS 便能在系统中找到特定的文件系统,并获得对特定存储设备内文件的操作方法。如果开启了自动初始化(默认开启),该函数将被自动调用。
注册文件系统
在 DFS 组件初始化之后,还需要初始化使用的具体类型的文件系统,也就是将具体类型的文件系统注册到 DFS 中。注册文件系统的接口如下所示:
int dfs_register(const struct dfs_filesystem_ops *ops);
参数 | 描述 |
---|---|
ops | 文件系统的操作函数的集合 |
返回 | —— |
0 | 文件注册成功 |
-1 | 文件注册失败 |
该函数不需要用户调用,他会被不同文件系统的初始化函数调用,如 elm-FAT 文件系统的初始化函数elm_init()
。开启对应的文件系统后,如果开启了自动初始化(默认开启),文件系统初始化函数也将被自动调用。
elm_init()
函数会初始化 elm-FAT 文件系统,此函数会调用 dfs_register(
) 函数将 elm-FAT 文件系统注册到 DFS 中,文件系统注册过程如下图所示:
将存储设备注册为块设备
因为只有块设备才可以挂载到文件系统上,因此需要在存储设备上创建所需的块设备。如果存储设备是 SPI Flash,则可以使用 “串行 Flash 通用驱动库 SFUD” 组件,它提供了各种 SPI Flash 的驱动,并将 SPI Flash 抽象成块设备用于挂载,注册块设备过程如下图所示:
格式化文件系统
注册了块设备之后,还需要在块设备上创建指定类型的文件系统,也就是格式化文件系统。可以使用 dfs_mkfs()
函数对指定的存储设备进行格式化,创建文件系统,格式化文件系统的接口如下所示:
int dfs_mkfs(const char * fs_name, const char * device_name);
参数 | 描述 |
---|---|
fs_name | 文件系统类型 |
device_name | 块设备名称 |
返回 | —— |
0 | 文件系统格式化成功 |
-1 | 文件系统格式化失败 |
文件系统类型(fs_name)可取值及对应的文件系统如下表所示:
取值 | 文件系统类型 |
---|---|
elm | elm-FAT 文件系统 |
jffs2 | jffs2 日志闪存文件系统 |
nfs | NFS 网络文件系统 |
ram | RamFS 文件系统 |
rom | RomFS 只读文件系统 |
uffs | uffs 文件系统 |
lfs | littlefs 文件系统 |
以 elm-FAT 文件系统格式化块设备为例,格式化过程如下图所示:
还可以使用 mkfs
命令格式化文件系统,格式化块设备 sd0 的运行结果如下所示:
msh />mkfs sd0 # sd0 为块设备名称,该命令会默认格式化 sd0 为 elm-FAT 文件系统
msh />
msh />mkfs -t elm sd0 # 使用 -t 参数指定文件系统类型为 elm-FAT 文件系统
挂载文件系统
在 RT-Thread 中,挂载是指将一个存储设备挂接到一个已存在的路径上。我们要访问存储设备中的文件,必须将文件所在的分区挂载到一个已存在的路径上,然后通过这个路径来访问存储设备。挂载文件系统的接口如下所示:
int dfs_mount(const char *device_name,
const char *path,
const char *filesystemtype,
unsigned long rwflag,
const void *data);
参数 | 描述 |
---|---|
device_name | 已经格式化的块设备名称 |
path | 挂载路径,即挂载点 |
filesystemtype | 挂载的文件系统类型,可取值见 dfs_mkfs() 函数描述 |
rwflag | 读写标志位 |
data | 特定文件系统的私有数据 |
返回 | —— |
0 | 文件系统挂载成功 |