在早期的嵌入式系统中,需要存储的数据比较少,数据类型也比较单一,往往使用直接在存储设备中的指定地址写入数据的方法来存储数据。然而随着嵌入式设备功能的发展,需要存储的数据越来越多,也越来越复杂,这时仍使用旧方法来存储并管理数据就变得非常繁琐困难。因此我们需要新的数据管理方式来简化存储数据的组织形式,这就是文件系统的由来。
为了统一众多不同类型的文件系统,虚拟文件系统对实际文件系统进行抽象,采用统一的文件系统向用户提供相应的一组统一的标准的文件操作接口(open,read,close,select,poll等)。
DFS( Device File System)是一种抽象的文件机制,RT-Thread中对文件系统的相关操作实际上都是通过操作DFS实现,也就是说DFS是对各种文件系统的抽象。DFS使的其他部分无须关心不同文件系统之间的差异,使得RT-Thread可以支持多种类型的文件系统。
RT-Thread DFS 组件的主要功能特点有:
DFS 的层次架构如下图所示,主要分为 POSIX 接口层、虚拟文件系统层和设备抽象层。
POSIX 接口层: POSIX 表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写 POSIX),POSIX 标准定义了操作系统应该为应用程序提供的接口标准,是 IEEE 为要在各种 UNIX 操作系统上运行的软件而定义的一系列 API 标准的总称。 虚拟文件系统层: 用户可以将具体的文件系统注册到 DFS 中,如 FatFS、RomFS、DevFS 等。 设备抽象层: 设备抽象层将物理设备如 SD Card、SPI Flash、Nand Flash,抽象成符合文件系统能够访问的设备,例如 FAT 文件系统要求存储设备必须是块设备类型。 不同文件系统类型是独立于存储设备驱动而实现的,因此把底层存储设备的驱动接口和文件系统对接起来之后,才可以正确地使用文件系统功能。
文件系统操作表:
const struct dfs_filesystem_ops *filesystem_operation_table[DFS_FILESYSTEM_TYPES_MAX];
文件系统表:
struct dfs_filesystem filesystem_table[DFS_FILESYSTEMS_MAX];
文件描述符:
static struct dfs_fdtable _fdtab;
dfs_init() 函数会初始化 DFS 所需的相关资源,创建一些关键的数据结构, 有了这些数据结构,DFS 便能在系统中找到特定的文件系统,并获得对特定存储设备内文件的操作方法。 dfs_init()加入了自动初始化机制,在系统上电后会自动运行dfs_init()。
INIT_PREV_EXPORT(dfs_init);
实例化DFS 组件相关的数据结构
const struct dfs_filesystem_ops *filesystem_operation_table[DFS_FILESYSTEM_TYPES_MAX];
struct dfs_filesystem filesystem_table[DFS_FILESYSTEMS_MAX]
static struct dfs_fdtable _fdtab
创建当前目录表
char working_directory[DFS_PATH_MAX] = {"/"}
初始化 DFS:
在 DFS 组件初始化之后,还需要初始化使用的具体类型的文件系统,也就是将具体类型的文件系统注册到 DFS 中。注册文件系统的接口如下所示:
int dfs_register(const struct dfs_filesystem_ops *ops);
在挂载文件系统之前,如果是用作存储设备,还需要先存储设备注册为块设备,然后格式化成对应的文件系统后,才能挂载。 在 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);
devfs是设备文件系统,设备文件系统是用来把一切设备都抽象为像文件那样操作(如可读,可写)。 devfs默认挂载在"/dev"路径下。但是会发现在根目录下使用shell 的ls命令不能看到“/dev”目录, 但是cd "/dev" 能进入dev 目录 ,并且进入dev目录后也能显示dev下的设备。 因为根目录“/"下并没有创建任何文件夹,所以在根目录下ls命令自然看不到“/dev”目录。由于挂载文件系统需要挂载在一个已存在的路径上,devfs属于特殊文件系统,DFS为devfs设备文件系统注册时设置了专门的“/dev”路径以供设备文件系统挂载。
/* Check if the path exists or not, raw APIs call, fixme */
if ((strcmp(fullpath, "/") != 0) && (strcmp(fullpath, "/dev") != 0))
{
struct dfs_fd fd;
if (dfs_file_open(&fd, fullpath, O_RDONLY | O_DIRECTORY) < 0)
{
rt_free(fullpath);
rt_set_errno(-ENOTDIR);
return -1;
}
dfs_file_close(&fd);
}
如果开启了DFS,devfs设备文件系统会在dfs_init()里自动初始化和挂载。
#ifdef RT_USING_DFS_DEVFS
{
extern int devfs_init(void);
/* if enable devfs, initialize and mount it as soon as possible */
devfs_init();
dfs_mount(NULL, "/dev", "devfs", 0, 0);
}
#endif
设置devfs文件系统的数据结构:_device_fs
static const struct dfs_filesystem_ops _device_fs =
{
"devfs",
DFS_FS_FLAG_DEFAULT,
&_device_fops,
dfs_device_fs_mount,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
dfs_device_fs_stat,
RT_NULL,
};
将devfs文件系统的数据结构挂载到文件系统操作表里
int devfs_init(void)
{
/* register rom file system */
dfs_register(&_device_fs);
return 0;
}
文件系统操作表filesystem_operation_table的第一个目录:
dfs_mount(NULL, "/dev", "devfs", 0, 0)
文件系统表filesystem_table的第一个目录:
在根目录下使用shell 的cd命令切换到/dev目录,然后使用ls命令:
RomFS是在嵌入式设备上常用的一种文件系统,具备体积小,可靠性高,读取速度快等优点,常用来作为系统初始文件系统。但也具有其局限性,RomFS是一种只读文件系统。
把RomFS当作初始文件系统rootfs挂载在根目录,在RomFS里创建几个目录,用于其他文件系统的挂载点。 更改…\romfs.c文件,添加mnt文件夹和user文件夹。
#include <rtthread.h>
#include <dfs_romfs.h>
static const struct romfs_dirent _romfs_root[] = {
{ROMFS_DIRENT_DIR, "mnt", RT_NULL, 0},
{ROMFS_DIRENT_DIR, "user", RT_NULL, 0}
};
const struct romfs_dirent romfs_root = {
ROMFS_DIRENT_DIR, "/", (rt_uint8_t *)_romfs_root, sizeof(_romfs_root)/sizeof(_romfs_root[0])
};
设置romfs文件系统的数据结构:_romfs
static const struct dfs_filesystem_ops _romfs =
{
"rom",
DFS_FS_FLAG_DEFAULT,
&_rom_fops,
dfs_romfs_mount,
dfs_romfs_unmount,
NULL,
NULL,
NULL,
dfs_romfs_stat,
NULL,
};
将romfs文件系统的数据结构挂载到文件系统操作表里
int dfs_romfs_init(void)
{
/* register rom file system */
dfs_register(&_romfs);
return 0;
}
INIT_COMPONENT_EXPORT(dfs_romfs_init);//自动初始化
文件系统操作表filesystem_operation_table的第二个目录:
int mnt_init(void)
{
if(dfs_mount (RT_NULL,"/","rom",0,&(romfs_root)) == 0)
{
rt_kprintf("ROM file system initializated;\n");
}
else
{
rt_kprintf("ROM file system initializate failed;\n");
}
return 0;
}
INIT_ENV_EXPORT(mnt_init);
文件系统表filesystem_table的第二个目录:
在根目录下使用shell 的ls命令:
RamFS是内存文件系统,它不能格式化,可以同时创建多个,在创建时可以指定其最大能使用的内存大小。其优点是读写速度很快,但存在掉电丢失的风险。如果一个进程的性能瓶颈是硬盘的读写,那么可以考虑在RamFS上进行大文件的读写操作。 RT-Thread的RamFS设计之初未考虑支持文件夹,所以不能使用mkdir。
设置ramfs文件系统的数据结构:_ramfs
static const struct dfs_filesystem_ops _ramfs =
{
"ram",
DFS_FS_FLAG_DEFAULT,
&_ram_fops,
dfs_ramfs_mount,
dfs_ramfs_unmount,
NULL, /* mkfs */
dfs_ramfs_statfs,
dfs_ramfs_unlink,
dfs_ramfs_stat,
dfs_ramfs_rename,
};
将ramfs文件系统的数据结构挂载到文件系统操作表里
int dfs_ramfs_init(void)
{
/* register ram file system */
dfs_register(&_ramfs);
return 0;
}
INIT_COMPONENT_EXPORT(dfs_ramfs_init);
文件系统操作表filesystem_operation_table的第三个目录:
int mnt_ram_elminit(void)
{
if(dfs_mount (RT_NULL,"/mnt","ram",0,dfs_ramfs_create(rampool, 1024)) == 0)
{
rt_kprintf("ram file system initializated;\n");
}
else
{
rt_kprintf("ram file system initializate failed;\n");
}
return 0;
}
INIT_ENV_EXPORT(mnt_ram_elminit);
文件系统表filesystem_table的第三个目录:
在根目录下使用shell 的cd命令切换到/mnt目录,然后使用ls命令:
FatFs 是一个通用的文件系统(FAT/exFAT)模块,用于在小型嵌入式系统中实现FAT文件系统。
设置fatfs文件系统的数据结构:dfs_elm
static const struct dfs_filesystem_ops dfs_elm =
{
"elm",
DFS_FS_FLAG_DEFAULT,
&dfs_elm_fops,
dfs_elm_mount,
dfs_elm_unmount,
dfs_elm_mkfs,
dfs_elm_statfs,
dfs_elm_unlink,
dfs_elm_stat,
dfs_elm_rename,
};
将fatfs文件系统的数据结构挂载到文件系统操作表里
int elm_init(void)
{
/* register fatfs file system */
dfs_register(&dfs_elm);
return 0;
}
INIT_COMPONENT_EXPORT(elm_init);
elm-FAT文件系统注册过程如下图所示:
void sd_mount(void *parameter)
{
while (1)
{
rt_thread_mdelay(500);
if(rt_device_find("sd0") != RT_NULL)
{
if (dfs_mount("sd0", "/fatfs", "elm", 0, 0) == RT_EOK)
{
LOG_I("sd card mount to '/fatfs'");
break;
}
else
{
LOG_W("sd card mount to '/fatfs' failed!");
}
}
}
}
int stm32_sdcard_mount(void)
{
rt_thread_t tid;
tid = rt_thread_create("sd_mount", sd_mount, RT_NULL,
1024, RT_THREAD_PRIORITY_MAX - 2, 20);
if (tid != RT_NULL)
{
rt_thread_startup(tid);
}
else
{
LOG_E("create sd_mount thread err!");
}
return RT_EOK;
}
INIT_APP_EXPORT(stm32_sdcard_mount);
在根目录下使用shell 的cd命令切换到/fatfs目录,然后使用ls命令:
littlefs 是 ARM 官方推出的,专为嵌入式系统设计的文件系统,相比传统的文件系统,littlefs 具有以下优点:
littlefs 自带的擦写均衡和掉电保护使开发者可以放心的将文件系统挂载到 nor flash 上。
层级关系 littlefs 在 RT-Thread 上运行的层级关系图如下所示:
设置littlefs文件系统的数据结构:_dfs_lfs_ops
static const struct dfs_filesystem_ops _dfs_lfs_ops = {
"lfs",
DFS_FS_FLAG_DEFAULT,
&_dfs_lfs_fops,
_dfs_lfs_mount,
_dfs_lfs_unmount,
_dfs_lfs_mkfs,
_dfs_lfs_statfs,
_dfs_lfs_unlink,
_dfs_lfs_stat,
_dfs_lfs_rename,
};
将littlefs文件系统的数据结构挂载到文件系统操作表里
int dfs_lfs_init(void)
{
/* init file system lock */
rt_mutex_init(&_lfs_lock, "lfsmtx", RT_IPC_FLAG_FIFO);
/* register ram file system */
return dfs_register(&_dfs_lfs_ops);
}
INIT_COMPONENT_EXPORT(dfs_lfs_init);
...
struct rt_device *mtd_dev = RT_NULL;
...
/* 初始化 fal */
fal_init();
/* 生成 mtd 设备 */
mtd_dev = fal_mtd_nor_device_create(FS_PARTITION_NAME);
if (!mtd_dev)
{
LOG_E("Can't create a mtd device on '%s' partition.", FS_PARTITION_NAME);
}
else
{
/* 挂载 littlefs */
if (dfs_mount(FS_PARTITION_NAME, "/littlefs", "lfs", 0, 0) == 0)
{
LOG_I("Filesystem initialized!");
}
else
{
/* 格式化文件系统 */
dfs_mkfs("lfs", FS_PARTITION_NAME);
/* 挂载 littlefs */
if (dfs_mount("filesystem", "/littlefs", "lfs", 0, 0) == 0)
{
LOG_I("Filesystem initialized!");
}
else
{
LOG_E("Failed to initialize filesystem!");
}
}
}
...
在根目录下使用shell 的cd命令切换到/littlefs目录,然后使用ls命令:
注意:spi_flash.h中缺少一个头文件,需要自行添加
DevFS、RomFS、RamFS、FatFS文件系统配置:
littlefs文件系统配置;
主程序:
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <fal.h>
#include <dfs_fs.h>
#include <dfs_romfs.h>
#include <dfs_ramfs.h>
#include <dfs_posix.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
/* defined the LED0 pin: PH10 */
#define LED0_PIN GET_PIN(H, 10)
int main(void)
{
int count = 1;
rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
while (count++)
{
rt_pin_write(LED0_PIN, PIN_HIGH);
rt_thread_mdelay(1000);
rt_pin_write(LED0_PIN, PIN_LOW);
rt_thread_mdelay(1000);
}
return RT_EOK;
}
rt_uint8_t rampool[128];
void sd_mount(void *parameter)
{
while (1)
{
rt_thread_mdelay(500);
if(rt_device_find("sd0") != RT_NULL)
{
if (dfs_mount("sd0", "/fatfs", "elm", 0, 0) == RT_EOK)
{
LOG_I("sd card mount to '/fatfs'");
break;
}
else
{
LOG_W("sd card mount to '/fatfs' failed!");
}
}
}
}
int fs_init(void)
{
/* partition initialized */
fal_init();
if(dfs_mount (RT_NULL,"/","rom",0,&(romfs_root)) == 0)
{
LOG_I("ROM file system initializated;\n");
}
else
{
LOG_I("ROM file system initializate failed;\n");
}
if(dfs_mount (RT_NULL,"/ram","ram",0,dfs_ramfs_create(rampool, sizeof(rampool))) == 0)
{
LOG_I("ram file system initializated;\n");
}
else
{
LOG_I("ram file system initializate failed;\n");
}
/* Create a block device on the file system partition of spi flash */
struct rt_device *flash_dev = fal_mtd_nor_device_create("filesystem");
if (flash_dev == RT_NULL)
{
LOG_I("Can't create a mtd device on '%s' partition.", "filesystem");
}
else
{
LOG_I("Create a mtd device on the %s partition of flash successful.", "filesystem");
}
/* mount the file system from "filesystem" partition of spi flash. */
if (dfs_mount(flash_dev->parent.name, "/littlefs", "lfs", 0, 0) == 0)
{
LOG_I("littlefs initialized!");
}
else
{
dfs_mkfs("lfs", flash_dev->parent.name);
if (dfs_mount(flash_dev->parent.name, "/", "lfs", 0, 0) == 0)
{
LOG_I("littlefs initialized!");
}
}
rt_thread_t tid;
tid = rt_thread_create("sd_mount", sd_mount, RT_NULL,
1024, RT_THREAD_PRIORITY_MAX - 2, 20);
if (tid != RT_NULL)
{
rt_thread_startup(tid);
}
else
{
LOG_E("create sd_mount thread err!");
}
return 0;
}
INIT_COMPONENT_EXPORT(fs_init);
测试:
工程地址:https://gitee.com/Aladdin-Wang/RT-FOTA-STM32L431/tree/master/stm32f429_app