Zephyr文件系统
本文说明Zephyr虚拟文件系统的架构,原理,和驱动的关联操作。不涉及具体文件系统的实现方式。
简介 链接到标题
Zephyr提供的文件系统功能允许在不同的挂载点挂载多个文件系统,每个挂载点都维护有关实例化,mount,操作当前文件系统的必要信息。通过文件系统注册机制,将应用和直接访问文件系统特定API解耦合。 Zephyr的文件系统目前支援fatfs和nffs,且这两种fs都最后都访问的是flash,fatfs还可以访问ram和sd卡。暂时还没看到对真实disk的支援。本文省略了ram和sd卡的情况,以flash为例进行说明。
架构 链接到标题
下图说明了Zephyr文件系统的架构

VFS 链接到标题
VFS可以看做由两层组成:
- VFS接口层:fs.c 对上提供FS统一接口,对下呼叫fs 操作函数,达到解耦合的目的。在其内部对挂载点进行管理
- VFS实现层:使用fs的实际API实现统一的fs callback,提供VFS功能。
VFS接口层 链接到标题
subsys/fs/fs.c
初始化 链接到标题
初始化使用fs_init,该函数被注册为系统函数,无需再app中使用,在系统初始化时被调用
SYS_INIT(fs_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
fs_init完成对挂载点链表的初始化
static int fs_init(struct device *dev)
{
k_mutex_init(&mutex);
sys_dlist_init(&fs_mnt_list);
return 0;
}
文件系统注册 链接到标题
VFS提供两个API用于注册VFS实现层提供的操作函数struct fs_file_system_t函数如下
int fs_register(enum fs_type type, struct fs_file_system_t *fs);
int fs_unregister(enum fs_type type, struct fs_file_system_t *fs);
前zephyr VFS只支援fatfs和nffs两种类型
enum fs_type {
FS_FATFS = 0,
FS_NFFS,
FS_TYPE_END,
};
不同的文件系统类型对用不同的操作函数,为了达到解耦合的目的,都被抽象为统一的函数结构struct fs_file_system_t
struct fs_file_system_t {
/* File operations */
int (*open)(struct fs_file_t *filp, const char *fs_path);
ssize_t (*read)(struct fs_file_t *filp, void *dest, size_t nbytes);
ssize_t (*write)(struct fs_file_t *filp,
const void *src, size_t nbytes);
int (*lseek)(struct fs_file_t *filp, off_t off, int whence);
off_t (*tell)(struct fs_file_t *filp);
int (*truncate)(struct fs_file_t *filp, off_t length);
int (*sync)(struct fs_file_t *filp);
int (*close)(struct fs_file_t *filp);
/* Directory operations */
int (*opendir)(struct fs_dir_t *dirp, const char *fs_path);
int (*readdir)(struct fs_dir_t *dirp, struct fs_dirent *entry);
int (*closedir)(struct fs_dir_t *dirp);
/* File system level operations */
int (*mount)(struct fs_mount_t *mountp);
int (*unmount)(struct fs_mount_t *mountp);
int (*unlink)(struct fs_mount_t *mountp, const char *name);
int (*rename)(struct fs_mount_t *mountp, const char *from,
const char *to);
int (*mkdir)(struct fs_mount_t *mountp, const char *name);
int (*stat)(struct fs_mount_t *mountp, const char *path,
struct fs_dirent *entry);
int (*statvfs)(struct fs_mount_t *mountp, const char *path,
struct fs_statvfs *stat);
};
文件系统挂载 链接到标题
文件系统挂载函数,将指定的device以指定的文件系统挂载到指定的路径下,这里简化一下实现代码,看一下主要是做了什么事情
int fs_mount(struct fs_mount_t *mp)
{
//根据指定的文件系统类型取得操作函数
fs = fs_map[mp->type];
//在mount点链表中检查mount点十分已存在
mp->mountp_len = strlen(mp->mnt_point);
SYS_DLIST_FOR_EACH_NODE(&fs_mnt_list, node) {
itr = CONTAINER_OF(node, struct fs_mount_t, node);
/* continue if length does not match */
if (mp->mountp_len != itr->mountp_len) {
continue;
}
if (strncmp(mp->mnt_point, itr->mnt_point,
mp->mountp_len) == 0) {
LOG_ERR("mount Point already exists!!");
rc = -EBUSY;
goto mount_err;
}
}
//执行实际的mount
rc = fs->mount(mp);
//设置mount点操作函数
mp->fs = fs;
//将新的mount点加入到mount点链表中
sys_dlist_append(&fs_mnt_list, &mp->node);
}
int fs_unmount(struct fs_mount_t *mp)
{
//执行实际的unmount
rc = mp->fs->unmount(mp);
//清除mount点操作函数
mp->fs = NULL;
//将mount点从mount点list中移除
sys_dlist_remove(&mp->node);
}
从上面分析可以看到mount点管理的mount信息并不会再存一份,而是直接将mp加入链表,因此fs_mount函数的使用者要保存struct fs_mount_t。fs_mount的使用者必须要传入device,mount路径和mount的文件类型
struct fs_mount_t {
sys_dnode_t node;
enum fs_type type; //必要输入,mount的文件系统类型
const char *mnt_point; //必要输入mount点
void *fs_data;
void *storage_dev; //必要输入device
size_t mountp_len;
const struct fs_file_system_t *fs;
};
文件操作 链接到标题
vfs的文件操作函数基本是直接呼叫注册进来的VFS实现层struct fs_file_system_t,这里不用多做分析,罗列一下函数,具体使用可以参考官方文档
- 文件 int fs_open(struct fs_file_t *zfp, const char *file_name) int fs_close(struct fs_file_t *zfp) ssize_t fs_read(struct fs_file_t *zfp, void *ptr, size_t size) ssize_t fs_write(struct fs_file_t *zfp, const void *ptr, size_t size) int fs_seek(struct fs_file_t *zfp, off_t offset, int whence) off_t fs_tell(struct fs_file_t *zfp) int fs_truncate(struct fs_file_t *zfp, off_t length) int fs_sync(struct fs_file_t *zfp) 文件夹 int fs_opendir(struct fs_dir_t *zdp, const char *abs_path) int fs_readdir(struct fs_dir_t *zdp, struct fs_dirent *entry) int fs_closedir(struct fs_dir_t *zdp) 文件系统 int fs_mkdir(const char *abs_path) int fs_unlink(const char *abs_path) int fs_rename(const char *from, const char *to) int fs_stat(const char *abs_path, struct fs_dirent *entry) int fs_statvfs(const char *abs_path, struct fs_statvfs *stat)
VFS实现层 链接到标题
subsys/fs/fat_fs.c, subsys/fs/nffs_fs.c VFS实现层本身并不涉及文件系统的实现,而是通过调用组织实际文件系统函数的实现文件系统的统一接口,并注册到VFS接口层中。本文无意说明FS如何实现,因此只看一下如何注册。 以nffs_fs.c为例:
//使用nffs_fs VFS实现层API初始化fs_file_system_t
static struct fs_file_system_t nffs_fs = {
.open = nffs_open,
.close = nffs_close,
.read = nffs_read,
.write = nffs_write,
.lseek = nffs_seek,
.tell = nffs_tell,
.truncate = nffs_truncate,
.sync = nffs_sync,
.opendir = nffs_opendir,
.readdir = nffs_readdir,
.closedir = nffs_closedir,
.mount = nffs_mount,
.unlink = nffs_unlink,
.rename = nffs_rename,
.mkdir = nffs_mkdir,
.stat = nffs_stat,
.statvfs = nffs_statvfs,
};
//在nffs_init初始化时将nffs_fs的fs_file_system_t注册到VFS接口层内
static int nffs_init(struct device *dev)
{
ARG_UNUSED(dev);
k_mutex_init(&nffs_lock);
return fs_register(FS_NFFS, &nffs_fs);
}
//nffs_init该函数被注册为系统函数,无需再app中使用,在系统初始化时被调用
SYS_INIT(nffs_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
VFS如何使用Flash驱动 链接到标题
目前zephyr实现了fatfs和nffs,代码放在ext/fs下,本文无意说明FS如何实现,这里以文件的write为例说明如何call到驱动层
nffs 链接到标题
nffs_write->nffs_write_to_file->nffs_write_chunk->nffs_write_over_block->nffs_flash_write->nffs_os_flash_write->flash_write nffs实现时通过调用nffs VFS实现层的函数nffs_os_flash_write来访问flash驱动。这里看一下这里flash驱动如何知道要操作哪一个device呢: 在vfs实现层的nffs_mount时会将mount的device保存在vfs实现层内
flash_dev = (struct device *)mountp->storage_dev;
在flash_write的时候就是使用的这个device
int nffs_os_flash_write(uint8_t id, uint32_t address, const void *src,
uint32_t num_bytes)
{
int rc;
rc = flash_write_protection_set(flash_dev, false);
if (rc) {
return rc;
}
rc = flash_write(flash_dev, address, src, num_bytes);
/* Ignore errors here - this does not affect write operation */
(void) flash_write_protection_set(flash_dev, true);
return rc;
}
因此对于nffs来说,fs使用的device就是mount的时候指定的storage_dev
fatfs 链接到标题
fatfs_write->f_write->disk_write->disk_access_write->disk->ops->write(disk_flash_access_write)->update_flash_block->flash_write fatfs通过disk_access对flash进行访问,disk_access要访问flash,最终是通过disk_flash_access_write,具体访问哪个device,不是由mount时指定的storage_dev,而是disk_flash_access_init时自己指定的
static int disk_flash_access_init(struct disk_info *disk)
{
if (flash_dev) {
return 0;
}
//使用CONFIG_DISK_FLASH_DEV_NAME做为device
flash_dev = device_get_binding(CONFIG_DISK_FLASH_DEV_NAME);
if (!flash_dev) {
return -ENODEV;
}
return 0;
}
然后通过这个device进行写
static int update_flash_block(off_t start_addr, u32_t size, const void *buff)
{
off_t fl_addr;
u8_t *src = (u8_t *)buff;
u32_t num_write;
/* if size is a partial block, perform read-copy with user data */
if (size < CONFIG_DISK_ERASE_BLOCK_SIZE) {
int rc;
rc = read_copy_flash_block(start_addr, size, buff, fs_buff);
if (rc != 0) {
return -EIO;
}
/* now use the local buffer as the source */
src = (u8_t *)fs_buff;
}
/* always align starting address for flash write operation */
fl_addr = ROUND_DOWN(start_addr, CONFIG_DISK_FLASH_ERASE_ALIGNMENT);
/* disable write-protection first before erase */
flash_write_protection_set(flash_dev, false);
if (flash_erase(flash_dev, fl_addr, CONFIG_DISK_ERASE_BLOCK_SIZE)
!= 0) {
return -EIO;
}
/* write data to flash */
num_write = GET_NUM_BLOCK(CONFIG_DISK_ERASE_BLOCK_SIZE,
CONFIG_DISK_FLASH_MAX_RW_SIZE);
for (u32_t i = 0; i < num_write; i++) {
/* flash_write reenabled write-protection so disable it again */
flash_write_protection_set(flash_dev, false);
if (flash_write(flash_dev, fl_addr, src,
CONFIG_DISK_FLASH_MAX_RW_SIZE) != 0) {
return -EIO;
}
fl_addr += CONFIG_DISK_FLASH_MAX_RW_SIZE;
src += CONFIG_DISK_FLASH_MAX_RW_SIZE;
}
return 0;
}
参考 链接到标题
https://docs.zephyrproject.org/latest/reference/file_system/index.html