it编程 > 硬件开发 > 嵌入式

[嵌入式系统-67]:RT-Thread-组件:虚拟-设备文件系统DFS,以目录结构和文件的方式存储和管理各种各样的数据

83人参与 2024-08-01 嵌入式

目录

虚拟文件系统

1. dfs 简介

dfs 架构

posix 接口层

虚拟文件系统层

设备抽象层

2. 挂载管理:构建统一的文件系统目录

初始化 dfs 组件

注册文件系统

将存储设备注册为块设备

格式化文件系统

挂载文件系统

卸载文件系统

3. 文件管理

打开和关闭文件

读写数据

重命名

取得状态

删除文件

同步文件数据到存储设备

查询文件系统相关信息

监视 i/o 设备状态

4. 目录管理

创建和删除目录

打开和关闭目录

读取目录

取得目录流的读取位置

设置下次读取目录的位置

重设读取目录的位置为开头位置

5. dfs 文件系统配置选项

elm-fatfs 文件系统配置选项

长文件名

编码方式

文件系统扇区大小

可重入性

更多配置

dfs 应用示例

finsh 命令

读写文件示例

更改文件名称示例

获取文件状态示例

创建目录示例

读取目录示例

设置读取目录位置示例

6. 常见问题

q: 发现文件名或者文件夹名称显示不正常怎么办?

q: 文件系统初始化失败怎么办?

q: 创建文件系统 mkfs 命令失败怎么办?

q: 文件系统挂载失败怎么办?

q: sfud 探测不到 flash 所使用的具体型号怎么办?

q: 存储设备的 benchmark 测试耗时过长是怎么回事?

q: spi flash 实现 elmfat 文件系统,如何保留部分扇区不被文件系统使用?

q: 测试文件系统过程中程序卡住了怎么办?

q: 如何一步步检查文件系统出现的问题?


虚拟文件系统

在早期的嵌入式系统中,需要存储的数据比较少,数据类型也比较单一,往往使用直接在存储设备中的指定地址,按照特定的数据结构,写入数据的方法来存储数据

然而随着嵌入式设备功能的发展,需要存储的数据种类和数量越来越多,也越来越复杂,这时仍使用旧方法来存储并管理数据就变得非常繁琐困难。因此我们需要新的数据管理方式来简化存储数据的组织形式,这种方式就是我们接下来要介绍的文件系统。

文件系统是一套实现了数据的存储、分级组织、访问和获取等操作的抽象数据类型 (abstract data type),是一种用于向用户提供底层数据访问的机制

文件系统通常存储的基本单位是文件,即数据是按照一个个文件的方式进行组织和管理。当文件比较多时,将导致文件繁多,不易分类、重名的问题。而文件夹作为一个容纳多个文件的容器而存在。

本章讲解 rt-thread 文件系统相关内容,带你了解 rt-thread 虚拟文件系统的架构、功能特点和使用方式。

1. 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 组件的主要功能特点有:

dfs 的层次架构如下图所示,主要分为 posix 接口层、虚拟文件系统层和设备抽象层。

dfs 层次架构图

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 等,下面介绍几种常用的文件系统类型:

设备抽象层

设备抽象层将物理设备如 sd card、spi flash、nand flash,抽象成符合文件系统能够访问的设备,例如 fat 文件系统要求存储设备必须是块设备类型。

不同文件系统类型独立于存储设备驱动而实现的,因此把底层存储设备的驱动接口和文件系统对接起来之后,才可以正确地使用文件系统功能。

2. 挂载管理:构建统一的文件系统目录

文件系统的初始化过程一般分为以下几个步骤:

  1. 初始化 dfs 组件。
  2. 初始化具体类型的文件系统。
  3. 在存储器上创建块设备。
  4. 格式化块设备。
  5. 挂载块设备到 dfs 目录中。
  6. 当文件系统不再使用,可以将它卸载。

初始化 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)可取值及对应的文件系统如下表所示:

取值文件系统类型
elmelm-fat 文件系统
jffs2jffs2 日志闪存文件系统
nfsnfs 网络文件系统
ramramfs 文件系统
romromfs 只读文件系统
uffsuffs 文件系统
lfslittlefs 文件系统

以 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文件系统挂载成功
-1文件系统挂载失败

如果只有一个存储设备,则可以直接挂载到根目录 / 上。

卸载文件系统

当某个文件系统不需要再使用了,那么可以将它卸载掉。卸载文件系统的接口如下所示:

int dfs_unmount(const char *specialfile);复制错误复制成功
参数描述
specialfile挂载路径
返回——
0卸载文件系统成功
-1卸载文件系统失败

3. 文件管理

本节介绍对文件进行操作的相关函数,对文件的操作一般都要基于文件描述符 fd,如下图所示:

文件管理常用函数

打开和关闭文件

打开或创建一个文件可以调用下面的 open() 函数:

int open(const char *file, int flags, ...);复制错误复制成功
参数描述
file打开或创建的文件名
flags指定打开文件的方式,取值可参考下表
返回——
文件描述符文件打开成功
-1文件打开失败

一个文件可以以多种方式打开,并且可以同时指定多种打开方式。例如,一个文件以 o_rdonly 和 o_creat 的方式打开,那么当指定打开的文件不存在时,就会先创建这个文件,然后再以只读的方式打开。文件打开方式如下表所示:

参数描述
o_rdonly只读方式打开文件
o_wronly只写方式打开文件
o_rdwr以读写方式打开文件
o_creat如果要打开的文件不存在,则建立该文件
o_append当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式添加到文件的尾部
o_trunc如果文件已经存在,则清空文件中的内容

当使用完文件后若不再需要使用则可使用 close() 函数关闭该文件,而 close() 会让数据写回磁盘,并释放该文件所占用的资源。

int close(int fd);复制错误复制成功
参数描述
fd文件描述符
返回——
0文件关闭成功
-1文件关闭失败

读写数据

读取文件内容可使用 read() 函数:

int read(int fd, void *buf, size_t len);复制错误复制成功
参数描述
fd文件描述符
buf缓冲区指针
len读取文件的字节数
返回——
int实际读取到的字节数
0读取数据已到达文件结尾或者无可读取的数据
-1读取出错,错误代码查看当前线程的 errno

该函数会把参数 fd 所指的文件的 len 个字节读取到 buf 指针所指的内存中。此外文件的读写位置指针会随读取到的字节移动。

向文件中写入数据可使用 write() 函数:

int write(int fd, const void *buf, size_t len);复制错误复制成功
参数描述
fd文件描述符
buf缓冲区指针
len写入文件的字节数
返回——
int实际写入的字节数
-1写入出错,错误代码查看当前线程的 errno

该函数会把 buf 指针所指向的内存中 len 个字节写入到参数 fd 所指的文件内。此外文件的读写位置指针会随着写入的字节移动。

重命名

重命名文件可使用 rename() 函数:

int rename(const char *old, const char *new);复制错误复制成功
参数描述
old旧文件名
new新文件名
返回——
0更改名称成功
-1更改名称失败

该函数会将参数 old 所指定的文件名称改为参数 new 所指的文件名称。若 new 所指定的文件已经存在,则该文件将会被覆盖。

取得状态

获取文件状态可使用下面的 stat() 函数:

int stat(const char *file, struct stat *buf);复制错误复制成功
参数描述
file文件名
buf结构指针,指向一个存放文件状态信息的结构体
返回——
0获取状态成功
-1获取状态失败

删除文件

删除指定目录下的文件可使用 unlink() 函数:

int unlink(const char *pathname);复制错误复制成功
参数描述
pathname指定删除文件的绝对路径
返回——
0删除文件成功
-1删除文件失败

同步文件数据到存储设备

同步内存中所有已修改的文件数据到储存设备可使用 fsync() 函数:

int fsync(int fildes);复制错误复制成功
参数描述
fildes文件描述符
返回——
0同步文件成功
-1同步文件失败

查询文件系统相关信息

查询文件系统相关信息可使用 statfs() 函数:

int statfs(const char *path, struct statfs *buf);复制错误复制成功
参数描述
path文件系统的挂载路径
buf用于储存文件系统信息的结构体指针
返回——
0查询文件系统信息成功
-1查询文件系统信息失败

监视 i/o 设备状态

监视 i/o 设备是否有事件发生可使用 select() 函数:

int select( int nfds,
            fd_set *readfds,
            fd_set *writefds,
            fd_set *exceptfds,
            struct timeval *timeout);复制错误复制成功
参数描述
nfds集合中所有文件描述符的范围,即所有文件描述符的最大值加 1
readfds需要监视读变化的文件描述符集合
writefds需要监视写变化的文件描述符集合
exceptfds需要监视出现异常的文件描述符集合
timeoutselect 的超时时间
返回——
正值监视的文件集合出现可读写事件或出错
0等待超时,没有可读写或错误的文件
负值出错

使用 select() 接口可以阻塞地同时探测一组支持非阻塞的 i/o 设备是否有事件发生(如可读,可写,有高优先级的错误输出,出现错误等等),直至某一个设备触发了事件或者超过了指定的等待时间。

4. 目录管理

本节介绍目录管理经常使用的函数,对目录的操作一般都基于目录地址,如下图所示:

目录管理常用函数

创建和删除目录

创建目录可使用 mkdir() 函数:

int mkdir(const char *path, mode_t mode);复制错误复制成功
参数描述
path目录的绝对地址
mode创建模式
返回——
0创建目录成功
-1创建目录失败

该函数用来创建一个目录即文件夹,参数 path 为目录的绝对路径,参数 mode 在当前版本未启用,所以填入默认参数 0x777 即可。

删除目录可使用 rmdir() 函数:

int rmdir(const char *pathname);复制错误复制成功
参数描述
pathname需要删除目录的绝对路径
返回——
0目录删除成功
-1目录删除错误

打开和关闭目录

打开目录可使用 opendir() 函数:

dir* opendir(const char* name);复制错误复制成功
参数描述
name目录的绝对地址
返回——
dir打开目录成功,返回指向目录流的指针
null打开失败

关闭目录可使用 closedir() 函数:

int closedir(dir* d);复制错误复制成功
参数描述
d目录流指针
返回——
0目录关闭成功
-1目录关闭错误

该函数用来关闭一个目录,必须和 opendir() 函数配合使用。

读取目录

读取目录可使用 readdir() 函数:

struct dirent* readdir(dir *d);复制错误复制成功
参数描述
d目录流指针
返回——
dirent读取成功返回指向目录条目的结构体指针
null已读到目录尾

该函数用来读取目录,参数 d 为目录流指针。此外,每读取一次目录,目录流的指针位置将自动往后递推 1 个位置。

取得目录流的读取位置

获取目录流的读取位置可使用 telldir() 函数:

long telldir(dir *d);复制错误复制成功
参数描述
d目录流指针
返回——
long读取位置的偏移量

该函数的返回值记录着一个目录流的当前位置,此返回值代表距离目录文件开头的 偏移量。你可以在随后的 seekdir()函数调用 中利用这个值来重置目录到当前位置。也就是说 telldir() 函数可以和 seekdir() 函数配合使用,重新设置目录流的读取位置到指定的偏移量。

设置下次读取目录的位置

设置下次读取目录的位置可使用 seekdir() 函数:

void seekdir(dir *d, off_t offset);复制错误复制成功
参数描述
d目录流指针
offset偏移值,距离本次目录的位移

该用来设置参数 d 目录流的读取位置,在调用 readdir() 时便从此新位置开始读取。

重设读取目录的位置为开头位置

重设目录流的读取位置为开头可使用 rewinddir() 函数:

void rewinddir(dir *d);复制错误复制成功
参数描述
d目录流指针

该函数可以用来设置 d 目录流目前的读取位置为目录流的初始位置。

5. dfs 文件系统配置选项

文件系统在 menuconfig 中具体配置路径如下:

rt-thread components  --->
    device virtual file system  --->复制错误复制成功

配置菜单描述及对应的宏定义如下表所示:

配置选项对应宏定义描述
[*] using device virtual file systemrt_using_dfs开启dfs虚拟/抽象文件系统
[*] using working directorydfs_using_workdir开启相对路径
(2) the maximal number of mounted file systemdfs_filesystems_max最大挂载文件系统的数量
(2) the maximal number of file system typedfs_filesystem_types_max最大支持文件系统类型的数量
(4) the maximal number of opened filesdfs_fd_max打开文件的最大数量
[ ] using auto mount table for file systemrt_using_dfs_mnttable开启自动挂载表
[*] enable elm-chan fatfsrt_using_dfs_elmfat开启 elm-fatfs 文件系统
[*] using devfs for device objectsrt_using_dfs_devfs开启 devfs 设备文件系统
[ ] enable readonly file system on flashrt_using_dfs_romfs开启 romfs 文件系统
[ ] enable ram file systemrt_using_dfs_ramfs开启 ramfs 文件系统
[ ] enable uffs file system: ultra-low-cost flash file systemrt_using_dfs_uffs开启 uffs 文件系统
[ ] enable jffs2 file systemrt_using_dfs_jffs2开启 jffs2 文件系统
[ ] using nfs v3 client file systemrt_using_dfs_nfs开启 nfs 文件系统

默认情况下,rt-thread 操作系统为了获得较小的内存占用,并不会开启相对路径功能。当支持相对路径选项没有打开时,在使用文件、目录接口进行操作时应该使用绝对目录进行(因为此时系统中不存在当前工作的目录)。如果需要使用当前工作目录以及相对目录,可在文件系统的配置项中开启相对路径功能。

选项 [*] using mount table for file system 被选中之后,会使能相应的宏 rt_using_dfs_mnttable,开启自动挂载表功能。自动挂载表 mount_table[] 由用户在应用代码中提供,用户需在表中指定设备名称、挂载路径、文件系统类型、读写标志及私有数据等,之后系统便会遍历该挂载表执行挂载,需要注意的是挂载表必须以 {0} 结尾,用于判断表格结束。

自动挂载表 mount_table[] 的示例如下所示,其中 mount_table[0] 的 5 个成员即函数 dfs_mount() 的 5 个参数,意思是将 elm 文件系统挂载到 flash0 设备的 / 路径下,rwflag 为 0 ,data 为 0 ; mount_table[1] 为 {0} 作为结尾,用于判断表格结束。

const struct dfs_mount_tbl mount_table[] =
{
    {"flash0", "/", "elm", 0, 0},
    {0}
};复制错误复制成功

elm-fatfs 文件系统配置选项

在 menuconfig 中开启 elm-fatfs 文件系统后可对 elm-fatfs 做进一步配置,配置菜单描述及对应的宏定义如下表所示:

配置选项对应宏定义描述
(437) oem code pagert_dfs_elm_code_page编码方式
[*] using rt_dfs_elm_word_accessrt_dfs_elm_word_access
support long file name (0: lfn disable) --->rt_dfs_elm_use_lfn开启长文件名子菜单
(255) maximal size of file name lengthrt_dfs_elm_max_lfn文件名最大长度
(2) number of volumes (logical drives) to be used.rt_dfs_elm_drives挂载 fatfs 的设备数量
(4096) maximum sector size to be handled.rt_dfs_elm_max_sector_size文件系统扇区大小
[ ] enable sector erase featurert_dfs_elm_use_erase
[*] enable the reentrancy (thread safe) of the fatfs modulert_dfs_elm_reentrant开启可重入性
长文件名

默认情况下,fatfs 的文件命名有如下缺点:

如果需要支持长文件名,则需要打开支持长文件名选项。长文件名子菜单描述如下所示:

配置选项对应宏定义描述
( ) 0: lfn disablert_dfs_elm_use_lfn_0关闭长文件名
( ) 1: lfn with static lfn working bufferrt_dfs_elm_use_lfn_1采用静态缓冲区支持长文件名,多线程操作文件名时将会带来重入问题
( ) 2: lfn with dynamic lfn working buffer on the stackrt_dfs_elm_use_lfn_2采用栈内临时缓冲区支持长文件名。对栈空间需求较大
(x) 3: lfn with dynamic lfn working buffer on the heaprt_dfs_elm_use_lfn_3使用heap(malloc申请)缓冲区存放长文件名,最安全(默认方式)
编码方式

当打开长文件名支持时,可以设置文件名的编码方式,rt-thread/fatfs 默认使用 437 编码(美国英语)。如果需要存储中文文件名,可以使用 936 编码(gbk编码)。936 编码需要一个大约 180kb 的字库。如果仅使用英文字符作为文件,建议使用 437 编码(美国 英语),这样就可以节省这 180kb 的 flash 空间。

fatfs所支持的文件编码如下所示:

/* this option specifies the oem code page to be used on the target system.
/  incorrect setting of the code page can cause a file open failure.
/
/   1   - ascii (no extended character. non-lfn cfg. only)
/   437 - u.s.
/   720 - arabic
/   737 - greek
/   771 - kbl
/   775 - baltic
/   850 - latin 1
/   852 - latin 2
/   855 - cyrillic
/   857 - turkish
/   860 - portuguese
/   861 - icelandic
/   862 - hebrew
/   863 - canadian french
/   864 - arabic
/   865 - nordic
/   866 - russian
/   869 - greek 2
/   932 - japanese (dbcs)
/   936 - simplified chinese (dbcs)
/   949 - korean (dbcs)
/   950 - traditional chinese (dbcs)
*/复制错误复制成功
文件系统扇区大小

指定 fatfs 的内部扇区大小,需要大于等于实际硬件驱动的扇区大小。例如,某 spi flash 芯片扇区为 4096 字节,则上述宏需要修改为 4096,否则 fatfs 从驱动读入数据时就会发生数组越界而导致系统崩溃(新版本在系统执行时给出警告信息)。

一般 flash 设备可以设置为 4096,常见的 tf 卡和 sd 卡的扇区大小设置为 512。

可重入性

fatfs 充分考虑了多线程安全读写安全的情况,当在多线程中读写 fatfs 时,为了避免重入带来的问题,需要打开上述宏。如果系统仅有一个线程操作文件系统,不会出现重入问题,则可以关闭此功能节省资源。

更多配置

fatfs 本身支持非常多的配置选项,配置非常灵活。下面文件为 fatfs 的配置文件,可以修改这个文件来定制 fatfs。

components/dfs/filesystems/elmfat/ffconf.h复制错误复制成功

dfs 应用示例

finsh 命令

文件系统挂载成功后就可以进行文件和目录的操作了,文件系统操作常用的 finsh 命令如下表所示:

finsh 命令描述
ls显示文件和目录的信息
cd进入指定目录
cp复制文件
rm删除文件或目录
mv将文件移动位置或改名
echo将指定内容写入指定文件,当文件存在时,就写入该文件,当文件不存在时就新创建一个文件并写入
cat展示文件的内容
pwd打印出当前目录地址
mkdir创建文件夹
mkfs格式化文件系统

使用 ls 命令查看当前目录信息,运行结果如下所示:

msh />ls                          # 使用 ls 命令查看文件系统目录信息
directory /:                      # 可以看到已经存在根目录 /复制错误复制成功

使用 mkdir 命令来创建文件夹,运行结果如下所示:

msh />mkdir rt-thread             # 创建 rt-thread 文件夹
msh />ls                          # 查看目录信息如下
directory /:
rt-thread           <dir>复制错误复制成功

使用 echo 命令将输入的字符串输出到指定输出位置,运行结果如下所示:

msh />echo "hello rt-thread!!!"                 # 将字符串输出到标准输出
hello rt-thread!!!
msh />echo "hello rt-thread!!!" hello.txt      # 将字符串出输出到 hello.txt 文件
msh />ls
directory /:
rt-thread           <dir>
hello.txt           18
msh />复制错误复制成功

使用 cat 命令查看文件内容,运行结果如下所示:

msh />cat hello.txt                     # 查看 hello.txt 文件的内容并输出
hello rt-thread!!!复制错误复制成功

使用 rm 命令删除文件夹或文件,运行结果如下所示:

msh />ls                                # 查看当前目录信息
directory /:
rt-thread           <dir>
hello.txt           18
msh />rm rt-thread                      # 删除 rt-thread 文件夹
msh />ls
directory /:
hello.txt           18
msh />rm hello.txt                      # 删除 hello.txt 文件
msh />ls
directory /:
msh />复制错误复制成功

读写文件示例

文件系统正常工作后,就可以运行应用示例,在该示例代码中,首先会使用 open() 函数创建一个文件 text.txt,并使用 write() 函数在文件中写入字符串 “rt-thread programmer!\n”,然后关闭文件。再次使用 open() 函数打开 text.txt 文件,读出其中的内容并打印出来,最后关闭该文件。

示例代码如下所示:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

static void readwrite_sample(void)
{
    int fd, size;
    char s[] = "rt-thread programmer!", buffer[80];

    rt_kprintf("write string %s to test.txt.\n", s);

    /* 以创建和读写模式打开 /text.txt 文件,如果该文件不存在则创建该文件 */
    fd = open("/text.txt", o_wronly | o_creat);
    if (fd>= 0)
    {
        write(fd, s, sizeof(s));
        close(fd);
        rt_kprintf("write done.\n");
    }

      /* 以只读模式打开 /text.txt 文件 */
    fd = open("/text.txt", o_rdonly);
    if (fd>= 0)
    {
        size = read(fd, buffer, sizeof(buffer));
        close(fd);
        rt_kprintf("read from file test.txt : %s \n", buffer);
        if (size < 0)
            return ;
    }
  }
/* 导出到 msh 命令列表中 */
msh_cmd_export(readwrite_sample, readwrite sample);
复制错误复制成功

更改文件名称示例

本小节的示例代码展示如何修改文件名称,程序会创建一个操作文件的函数 rename_sample() 并导出到 msh 命令列表。该函数会调用 rename() 函数, 将名为 text.txt 的文件改名为 text1.txt。示例代码如下所示:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

static void rename_sample(void)
{
    rt_kprintf("%s => %s", "/text.txt", "/text1.txt");

    if (rename("/text.txt", "/text1.txt") < 0)
        rt_kprintf("[error!]\n");
    else
        rt_kprintf("[ok!]\n");
}
/* 导出到 msh 命令列表中 */
msh_cmd_export(rename_sample, rename sample);复制错误复制成功

在 finsh 控制台运行该示例,运行结果如下:

msh />echo "hello" text.txt
msh />ls
directory /:
text.txt           5
msh />rename_sample
/text.txt => /text1.txt [ok!]
msh />ls
directory /:
text1.txt           5复制错误复制成功

在示例展示过程中,我们先使用 echo 命令创建一个名为 text.txt 文件,然后运行示例代码将文件 text.txt 的文件名修改为 text1.txt。

获取文件状态示例

本小节的示例代码展示如何获取文件状态,程序会创建一个操作文件的函数 stat_sample() 并导出到 msh 命令列表。该函数会调用 stat() 函数获取 text.txt 文件的文件大小信息。示例代码如下所示:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

static void stat_sample(void)
{
    int ret;
     struct stat buf;
     ret = stat("/text.txt", &buf);
    if(ret == 0)
    rt_kprintf("text.txt file size = %d\n", buf.st_size);
    else
    rt_kprintf("text.txt file not fonud\n");
}
/* 导出到 msh 命令列表中 */
msh_cmd_export(stat_sample, show text.txt stat sample);复制错误复制成功

在 finsh 控制台运行该示例,运行结果如下:

msh />echo "hello" text.txt
msh />stat_sample
text.txt file size = 5复制错误复制成功

在示例运行过程中,首先会使用 echo 命令创建文件 text.txt,然后运行示例代码,将文件 text.txt 的文件大小信息打印出来。

创建目录示例

本小节的示例代码展示如何创建目录,程序会创建一个操作文件的函数 mkdir_sample() 并导出到 msh 命令列表,该函数会调用 mkdir() 函数创建一个名为 dir_test 的文件夹。示例代码如下所示:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

static void mkdir_sample(void)
{
    int ret;

    /* 创建目录 */
    ret = mkdir("/dir_test", 0x777);
    if (ret < 0)
    {
        /* 创建目录失败 */
        rt_kprintf("dir error!\n");
    }
    else
    {
        /* 创建目录成功 */
        rt_kprintf("mkdir ok!\n");
    }
}
/* 导出到 msh 命令列表中 */
msh_cmd_export(mkdir_sample, mkdir sample);复制错误复制成功

在 finsh 控制台运行该示例,运行结果如下:

msh />mkdir_sample
mkdir ok!
msh />ls
directory /:
dir_test                 <dir>    # <dir> 表示该目录的类型是文件夹复制错误复制成功

本例程演示了在根目录下创建名为 dir_test 的文件夹。

读取目录示例

本小节的示例代码展示如何读取目录,程序会创建一个操作文件的函数 readdir_sample() 并导出到 msh 命令列表,该函数会调用 readdir() 函数获取 dir_test 文件夹的内容信息并打印出来。示例代码如下所示:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

static void readdir_sample(void)
{
    dir *dirp;
    struct dirent *d;

    /* 打开 / dir_test 目录 */
    dirp = opendir("/dir_test");
    if (dirp == rt_null)
    {
        rt_kprintf("open directory error!\n");
    }
    else
    {
        /* 读取目录 */
        while ((d = readdir(dirp)) != rt_null)
        {
            rt_kprintf("found %s\n", d->d_name);
        }

        /* 关闭目录 */
        closedir(dirp);
    }
}
/* 导出到 msh 命令列表中 */
msh_cmd_export(readdir_sample, readdir sample);复制错误复制成功

在 finsh 控制台运行该示例,运行结果如下:

msh />ls
directory /:
dir_test                 <dir>
msh />cd dir_test
msh /dir_test>echo "hello" hello.txt       # 创建一个 hello.txt 文件
msh /dir_test>cd ..                        # 切换到上级文件夹
msh />readdir_sample
found hello.txt复制错误复制成功

本示例中,首先进入到 dir_test 文件夹下创建 hello.txt 文件,然后退出 dir_test 文件夹。此时运行示例程序将 dir_test 文件夹中的内容打印出来。

设置读取目录位置示例

本小节的示例代码展示如何设置下次读取目录的位置,程序会创建一个操作文件的函数 telldir_sample() 并导出到 msh 命令列表。该函数会首先打开根目录,然后读取根目录下所有目录信息,并将这些目录信息打印出来。同时使用 telldir() 函数记录第三个目录项的位置信息。在第二次读取根目录下的目录信息前,使用 seekdir() 函数设置读取位置为之前记录的第三个目录项的地址,此时再次读取根目录下的信息,并将目录信息打印出来。示例代码如下所示:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

/* 假设文件操作是在一个线程中完成 */
static void telldir_sample(void)
{
    dir *dirp;
    int save3 = 0;
    int cur;
    int i = 0;
    struct dirent *dp;

    /* 打开根目录 */
    rt_kprintf("the directory is:\n");
    dirp = opendir("/");

    for (dp = readdir(dirp); dp != rt_null; dp = readdir(dirp))
    {
        /* 保存第三个目录项的目录指针 */
        i++;
        if (i == 3)
            save3 = telldir(dirp);

        rt_kprintf("%s\n", dp->d_name);
    }

    /* 回到刚才保存的第三个目录项的目录指针 */
    seekdir(dirp, save3);

    /* 检查当前目录指针是否等于保存过的第三个目录项的指针. */
    cur = telldir(dirp);
    if (cur != save3)
    {
        rt_kprintf("seekdir (d, %ld); telldir (d) == %ld\n", save3, cur);
    }

    /* 从第三个目录项开始打印 */
    rt_kprintf("the result of tell_seek_dir is:\n");
    for (dp = readdir(dirp); dp != null; dp = readdir(dirp))
    {
         rt_kprintf("%s\n", dp->d_name);
    }

    /* 关闭目录 */
    closedir(dirp);
}
/* 导出到 msh 命令列表中 */
msh_cmd_export(telldir_sample, telldir sample);复制错误复制成功

本次演示示例中,需要手动在根目录下用 mkdir 命令依次创建从 hello_1 到 hello_5 这五个文件夹,确保根目录下有运行示例所需的文件夹目录。

在 finsh 控制台运行该示例,运行结果如下:

msh />ls
directory /:
hello_1             <dir>
hello_2             <dir>
hello_3             <dir>
hello_4             <dir>
hello_5             <dir>
msh />telldir_sample
the directory is:
hello_1
hello_2
hello_3
hello_4
hello_5
the result of tell_seek_dir is:
hello_3
hello_4
hello_5复制错误复制成功

运行示例程序后,可以看到第一次读取根目录信息时是从第一个文件夹开始读取,打印出了根目录下所有的目录信息。第二次打印目录信息时,由于使用了 seekdir() 函数设置读取的起始位置为第三个文件夹的位置,因此第二次读取根目录时,是从第三个文件夹开始读取直到最后一个文件夹,只打印出了从 hello_3 到 hello_5 的目录信息。

6. 常见问题

q: 发现文件名或者文件夹名称显示不正常怎么办?

a: 检查是否开启了长文件名支持,dfs 功能配置小节。

q: 文件系统初始化失败怎么办?

a: 检查文件系统配置项目中的允许挂载的文件系统类型和数量是否充足。

q: 创建文件系统 mkfs 命令失败怎么办?

a: 检查存储设备是否存在,如果存在检查设备驱动是否可以通过功能测试,如果不能通过,则检查驱动错误。 检查 libc 功能是否开启。

q: 文件系统挂载失败怎么办?

a:

q: sfud 探测不到 flash 所使用的具体型号怎么办?

a:

q: 存储设备的 benchmark 测试耗时过长是怎么回事?

a:

q: spi flash 实现 elmfat 文件系统,如何保留部分扇区不被文件系统使用?

a: 可以使用 rt-thread 提供的 partition 工具软件包为整个存储设备创建多个块设备,为创建的多个块设备分配不同的功能即可。

q: 测试文件系统过程中程序卡住了怎么办?

a: 尝试使用调试器或者打印一些必要的调试信息,确定程序卡住的位置再提出问题。

q: 如何一步步检查文件系统出现的问题?

a:

(0)
打赏 微信扫一扫 微信扫一扫

您想发表意见!!点此发布评论

推荐阅读

【嘉立创EDA】构建自己的元件库,绘制符号、封装的方法

08-02

基于STM32F407实现离散傅里叶变换(FFT、DFT),计算指定频率的幅值

08-02

Stm32-使用TB6612驱动电机及编码器测速

08-01

ST7735S应用笔记

08-01

stm32f103rct6引脚功能表格

08-01

嵌入式软件学习路线(入门)

08-01

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论