文件系统实现
本文档介绍杰理 AD15N SDK 中虚拟文件系统(VFS)与各具体文件系统适配层的实现机制,涵盖 VFS 操作表分发、挂载流程、文件句柄生命周期、文件扫描(fscan)与上层封装 API。
Purpose and Scope
本页聚焦 SDK 的文件系统实现:以 sdk/app/bsp/common/fs/vfs.c 为核心的虚拟文件系统(VFS)层,以及 sdk/app/bsp/common/fs/vfs_fat.c 提供的通用文件操作封装与扫描参数解析,并梳理挂载在 VFS 之下的具体文件系统(FAT、nor_fs、simple_fat、sydf)适配结构。
以下相关主题不在此页展开,而是属于各自的 Wiki 页面:
- 设备层(device):
vfs_mount()中传入的p_device指向底层存储设备(SPI NOR Flash、SD 卡等),其实现见设备驱动相关页面。 - 录音/编码流程:
sdk/app/src/voice_toy/toy_record/enc_in_norfs.c展示了 nor_fs 在录音场景中的实际调用,属于"录音功能"页面范畴。 - 具体文件系统内部算法:FAT 的簇链表、目录项解析(
fat_io_new.h、tff.h)、sydf 的日志结构等细节由各自适配层页面承载;本页重点说明它们如何通过struct vfs_operations接入 VFS。
Overview
在资源受限的 MCU SDK(AD15N 系列)中,文件系统需要同时满足以下约束:
- 多文件系统共存:同一套 SDK 可能同时使用 FAT(U 盘/SD 卡)、nor_fs(片内 NOR Flash 的简易文件系统)、simple_fat、sydf(杰理自研日志文件系统)。
- 统一上层接口:应用层(音频播放、录音、资源读取)不应关心底层是哪种文件系统,需要一套与平台无关的抽象 API。
- 极小的代码与内存开销:不能引入重量级抽象(如 POSIX 层、对象模型),必须用 C 语言、函数指针、链接器段收集等方式实现"轻量多态"。
因此,SDK 采用操作表(operations table)分发模式:核心是 struct vfs_operations(定义于 vfs.h),每个具体文件系统提供一份该结构的实例,VFS 层通过统一的 vfs_* 系列函数把调用分发给当前挂载文件系统的操作表。struct imount 同时充当"文件系统句柄"与"文件句柄",通过内部的 ops 指针与 pfs/pfile 指针实现两种角色的复用,从而避免维护两套句柄类型。
关键概念:
struct vfs_operations:文件系统能力接口,包含 mount、openbypath、read、write、seek、fscan_interrupt 等 24 个函数指针。struct imount:通用句柄,ops指向操作表,pfs指向文件系统私有数据,pfile指向文件私有数据。list_for_each_vfs_operation:链接器段收集宏,用于遍历所有注册的文件系统操作表,是"注册-发现"机制的核心。- 错误码:
E_NO_VFS、E_NO_FS、E_VFS_HDL、E_VFS_OPS、E_FS_PFILE等(定义于errno-base.h)。
Architecture
下图展示文件系统实现的整体分层结构与调用关系:
flowchart TD
subgraph sg_App["应用层"]
App["应用代码<br/>(播放/录音/资源读取)"]
end
subgraph sg_VFS["VFS 层 (vfs.c)"]
VFSInit["vfs_init()"]
VFSMount["vfs_mount()"]
VFSOpen["vfs_openbypath / openbyindex /<br/>openbyfile / openbyclust"]
VFSIO["vfs_read / vfs_write / vfs_seek /<br/>vfs_file_close 等"]
VFSHDL["vfs_hdl_malloc / vfs_fhdl_free<br/>句柄管理"]
end
subgraph sg_Common["通用封装层 (vfs_fat.c)"]
FSIZE["vfs_get_fsize()"]
FTELL["vfs_ftell()"]
FDEL["vfs_file_delete()"]
FSCAN["__fscan_arg_handler()<br/>fscan 参数解析"]
end
subgraph sg_Adapters["文件系统适配器"]
FAT["FAT (fat_resource.c)<br/>fs_type: fat"]
NORFS["nor_fs (nor_fs_resource.c)<br/>fs_type: norfs"]
SMPLFAT["simple_fat (smpl_fat_resource.c)"]
SYDF["sydf (sydf_resource.c)"]
end
subgraph sg_Device["设备层"]
DEV["存储设备 device<br/>(SPI NOR / SD / U 盘)"]
end
App --> VFSInit
App --> VFSMount
App --> VFSOpen
App --> VFSIO
App --> FSIZE
App --> FTELL
App --> FDEL
App --> FSCAN
VFSInit -->|"list_for_each_vfs_operation"| FAT
VFSInit -->|"list_for_each_vfs_operation"| NORFS
VFSInit -->|"list_for_each_vfs_operation"| SMPLFAT
VFSInit -->|"list_for_each_vfs_operation"| SYDF
VFSMount -->|"匹配 fs_type 并调用 ops->mount"| FAT
VFSMount -->|"匹配 fs_type 并调用 ops->mount"| NORFS
VFSMount -->|"匹配 fs_type 并调用 ops->mount"| SMPLFAT
VFSMount -->|"匹配 fs_type 并调用 ops->mount"| SYDF
VFSOpen -->|"通过 p_vfs->ops 分发"| FAT
VFSIO -->|"通过 p_vfile->ops 分发"| FAT
FAT --> DEV
NORFS --> DEV
SMPLFAT --> DEV
SYDF --> DEV
架构说明:
- VFS 层(vfs.c) 是唯一的系统入口:
vfs_init()遍历链接器段中注册的所有struct vfs_operations并逐个调用其init;vfs_mount()按type字符串匹配(例如"norfs")找到目标文件系统并调用其mount;打开类 API(vfs_openbypath等)在分配文件句柄后,把文件系统句柄的ops拷贝给文件句柄,后续read/write/seek直接经由该ops分发。 - 通用封装层(vfs_fat.c) 虽然文件名带 "fat",实际提供的是与具体文件系统无关的上层便利函数:
vfs_get_fsize、vfs_ftell、vfs_file_delete,以及 fscan 扫描参数解析器__fscan_arg_handler(支持-t类型、-r递归、-d目录、-s排序、-a属性、-m目录过滤)。 - 适配器层 是各文件系统的具体实现,每个适配器必须提供一份完整的
struct vfs_operations实例。从仓库可见四类资源文件:fat_resource.c、nor_fs_resource.c、smpl_fat_resource.c、sydf_resource.c,分别对应 FAT、nor_fs、simple_fat、sydf。 - 设备层 是文件系统的底层存储载体,通过
vfs_mount(void **ppvfs, void *device, void *type)的device参数传入,适配器内部自行完成设备抽象到自身格式的绑定。
这种"操作表 + 链接器段注册 + 双角色句柄"的设计,把多态开销压缩到一次间接调用,同时让新增文件系统只需"写一份操作表 + 注册到链接器段",符合嵌入式 SDK 对可裁剪、可扩展性的要求。
核心实现分析
VFS 操作表:struct vfs_operations
struct vfs_operations(定义于 vfs.h)是文件系统实现与 VFS 层之间的唯一契约。它定义了 24 个函数指针,覆盖文件系统的完整生命周期:
| 函数指针 | 职责 |
|---|---|
fs_type | 文件系统名字符串(如 "norfs"),用于 vfs_mount 的类型匹配 |
init | 文件系统初始化,由 vfs_init() 在系统启动时批量调用 |
mount | 将设备挂载为文件系统,输出 pfs 句柄 |
openbypath | 按路径打开文件(openbypath(pfs, &pfile, path)) |
openbyindex | 按索引打开文件(配合文件扫描结果) |
openbyfile | 由已有文件打开同目录下的关联文件(如由歌曲名找歌词) |
openbyclust | 按簇号打开文件 |
createfile | 创建新文件,可回传文件索引 |
read / write | 文件读写 |
seek | 文件定位(模式为 SEEK_SET/CUR/END) |
close_fs / close_file | 关闭文件系统/文件 |
fdelete | 删除文件 |
fget_attr / flen / ftell | 属性、长度、当前位置查询 |
name | 获取文件名 |
ioctl | 扩展控制命令(见下文 FS_IOCTL_* 枚举) |
fscan_interrupt / fscan_release / fsel | 可中断的文件扫描、扫描资源释放、扫描结果选择 |
file_crc | 文件 CRC 校验 |
format | 格式化文件系统 |
设计意图:把文件系统的全部能力收敛到一张函数指针表,VFS 层只依赖这一抽象,不关心具体实现;同时允许各适配器把不需要的能力置为 NULL,VFS 层在调用前会做空指针检查并返回 E_VFS_OPS。
VFS 初始化与挂载流程
vfs_init() 与 vfs_mount() 是实现于 vfs.c 的两个核心入口:
void vfs_init(void)
{
struct vfs_operations *ops;
list_for_each_vfs_operation(ops) {
if (NULL != ops->init) {
log_info("ops_init!!!\n");
ops->init();
}
}
}
u32 vfs_mount(void **ppvfs, void *device, void *type)
{
if ((void *)NULL == *ppvfs) {
*ppvfs = vfs_hdl_malloc();
if ((void *)NULL == *ppvfs) {
return E_NO_VFS;
}
}
struct imount *pvfs = *ppvfs;
struct vfs_operations *ops;
list_for_each_vfs_operation(ops) {
if (NULL != ops->mount) {
if (NULL != type) {
if (0 != strcmp(ops->fs_type, type)) {
continue;
}
} else {
if (0 == strcmp(ops->fs_type, "norfs")) {
continue;
}
}
if (0 == ops->mount(&(pvfs->pfs), device)) {
pvfs->ops = ops;
return 0;
} else {
if (NULL != ops->close_fs) {
ops->close_fs(&(pvfs->pfs));
}
}
}
}
*ppvfs = vfs_fhdl_free(*ppvfs);
return E_NO_FS;
}
Source: vfs.c
关键行为与设计意图:
- 链接器段遍历:
list_for_each_vfs_operation(ops)通过链接器段收集所有编译进固件的文件系统操作表。这意味着只要链接进工程,文件系统就会自动被 VFS 发现,无需手工维护注册数组——这是嵌入式 SDK 常见的"零注册表"扩展方式。 - 挂载句柄惰性分配:若调用方传入的
*ppvfs为空,vfs_mount先调用vfs_hdl_malloc()分配句柄;分配失败返回E_NO_VFS。句柄被复用而非每次新建,减少内存碎片。 - 类型匹配策略:
type非空时按strcmp(ops->fs_type, type)精确匹配;type为空时显式跳过 norfs,其含义是"未指定类型时优先挂载可移动介质文件系统(如 FAT)",避免默认挂载到片内 NOR Flash。这是一个值得注意的业务决策:nor_fs 通常作为系统保留文件系统,只有显式指定"norfs"才会被挂载。 - 失败回滚:
ops->mount失败时先调用close_fs清理半初始化状态,继续尝试下一个文件系统;全部失败则释放句柄并返回E_NO_FS。这保证了挂载操作要么完整成功,要么不留残留状态。
文件句柄生命周期与 ops 拷贝
vfs_openbypath()(vfs.c)展示了文件句柄的创建模式:
u32 vfs_openbypath(void *pvfs, void **ppvfile, const char *path)
{
if ((void *)NULL == *ppvfile) {
*ppvfile = vfs_hdl_malloc();
if ((void *)NULL == *ppvfile) {
return E_NO_VFS;
}
}
u32 err;
struct vfs_operations *ops;
struct imount *p_vfs = pvfs;
struct imount *p_vfile = *ppvfile;
if ((void *)NULL == p_vfs) {
err = E_VFS_HDL;
goto __vfs_openbypath;
}
p_vfile->ops = p_vfs->ops; // 关键:把 fs 的操作表拷贝给文件句柄
ops = p_vfs->ops;
if (NULL != ops->openbypath) {
err = ops->openbypath(p_vfs->pfs, &p_vfile->pfile, path);
} else {
err = E_VFS_OPS;
}
__vfs_openbypath:
if (0 != err) {
vfs_file_close(ppvfile);
}
return err;
}
Source: vfs.c
核心设计点:
p_vfile->ops = p_vfs->ops:文件句柄从文件系统句柄继承操作表。此后vfs_read/vfs_write/vfs_seek/vfs_file_close等操作只需要p_vfile->ops即可分发,无需再回溯文件系统句柄。这是struct imount双角色复用的关键。- 失败即清理:打开失败时调用
vfs_file_close(ppvfile)释放句柄,避免句柄泄漏。注意openbyindex/openbyfile/openbyclust/createfile采用了完全相同的模式(分配句柄 → 拷贝 ops → 分发 → 失败回滚),只是分发目标不同,分别对应按索引、按关联文件、按簇、按新建文件打开。 vfs_type_name():通过pvfs->ops->fs_type反查当前文件系统类型名,用于调试日志(log_info("p_vfs 0x%x", ...))。
通用文件操作封装(vfs_fat.c)
vfs_fat.c 提供与具体文件系统无关的上层封装(vfs_fat.c):
vfs_get_fsize(pvfile, parm):经ops->flen(p_vfile->pfile, (u32 *)parm)获取文件大小;句柄或操作表为空时返回 0。vfs_ftell(pvfile, parm):经ops->ftell获取当前读写位置,空指针安全。vfs_file_delete(pvfile):经ops->fdelete删除文件。注意注释说明:"文件关闭在外面应用,删除接口里面不处理"——即删除只负责移除目录项与释放簇,调用方需自行关闭文件。删除失败返回E_FS_PFILE。
此外,__fscan_arg_handler()(vfs_fat.c)解析 fscan 扫描选项字符串,默认属性为 F_ATTR_ARC | F_ATTR_RO(扫描所有普通文件),支持:
| 选项 | 含义 | 副作用 |
|---|---|---|
-t | 指定文件类型 | 设置 fs->ftype,scan_file = 1 |
-r | 递归包含子目录 | subpath = 1 |
-d | 扫描文件夹 | `attr |
-s | 排序方式(时间/文件号) | 进入排序解析状态 |
-a | 文件属性过滤 | 进入属性解析状态 |
-m | 目录过滤 | 写入 fs->filt_dir |
设计意图:扫描参数用命令行风格的紧凑字符串表达("*.mp3 -r -d" 之类),在 MCU 上比结构体配置更省代码、更易由上层配置传入,同时解析逻辑集中一处、各文件系统复用。
Core Flow
一次完整的文件读取流程
从应用调用 vfs_openbypath 到读取数据,VFS 层与适配器层的完整交互如下:
sequenceDiagram
participant App as 应用层
participant VFS as VFS 层 (vfs.c)
participant FAT as FAT/nor_fs 适配器
participant DEV as 存储设备
App->>VFS: vfs_init() 系统启动
VFS->>FAT: list_for_each_vfs_operation → ops->init()
FAT-->>VFS: 初始化完成
App->>VFS: vfs_mount(&pvfs, device, type)
VFS->>VFS: vfs_hdl_malloc() 分配 fs 句柄
VFS->>FAT: 匹配 fs_type → ops->mount(&pfs, device)
FAT-->>VFS: mount 成功
VFS-->>App: 返回 fs 句柄 (ops 已绑定)
App->>VFS: vfs_openbypath(pvfs, &pvfile, "/music/a.mp3")
VFS->>VFS: vfs_hdl_malloc() 分配 file 句柄
VFS->>VFS: p_vfile->ops = p_vfs->ops (继承操作表)
VFS->>FAT: ops->openbypath(pfs, &pfile, path)
FAT->>DEV: 读取目录项定位簇
FAT-->>VFS: 打开成功
VFS-->>App: 返回 file 句柄
App->>VFS: vfs_read(pvfile, buf, len)
VFS->>FAT: ops->read(pfile, buf, len)
FAT->>DEV: 按簇读取数据
DEV-->>FAT: 数据块
FAT-->>VFS: 返回实际读取字节数
App->>VFS: vfs_file_close(&pvfile)
VFS->>FAT: ops->close_file(&pfile)
VFS->>VFS: vfs_fhdl_free() 释放句柄
挂载失败与类型回退流程
flowchart TD
Start([vfs_mount 调用]) --> Alloc{"*ppvfs 为空?"}
Alloc -->|"是"| Malloc["vfs_hdl_malloc()"]
Malloc -->|"失败"| E1["返回 E_NO_VFS"]
Alloc -->|"否"| Loop["遍历 list_for_each_vfs_operation"]
Malloc -->|"成功"| Loop
Loop --> Type{"type 非空?"}
Type -->|"是"| Match{"strcmp(fs_type, type) == 0?"}
Type -->|"否"| SkipNor{"fs_type 为 norfs?"}
Match -->|"不匹配"| Next["继续下一个 ops"]
SkipNor -->|"是 norfs"| Next
Match -->|"匹配"| DoMount["ops->mount(&pfs, device)"]
SkipNor -->|"非 norfs"| DoMount
DoMount -->|"成功"| Bind["pvfs->ops = ops; 返回 0"]
DoMount -->|"失败"| Cleanup["ops->close_fs(&pfs) 清理"]
Cleanup --> Next
Next -->|"遍历完毕"| Free["vfs_fhdl_free(*ppvfs)"]
Free --> E2["返回 E_NO_FS"]
Bind --> Done([结束])
E1 --> Done
E2 --> Done
API Reference
VFS 层核心 API(vfs.c)
| API | 签名 | 说明 |
|---|---|---|
vfs_init | void vfs_init(void) | 遍历链接器段,调用所有注册文件系统的 init |
vfs_type_name | void *vfs_type_name(void *p_vfs) | 返回当前文件系统的 fs_type 字符串 |
vfs_mount | u32 vfs_mount(void **ppvfs, void *device, void *type) | 按类型挂载文件系统;成功返回 0,失败返回 E_NO_VFS/E_NO_FS |
vfs_openbypath | u32 vfs_openbypath(void *pvfs, void **ppvfile, const char *path) | 按路径打开文件 |
vfs_openbyindex | u32 vfs_openbyindex(void *pvfs, void **ppvfile, u32 index) | 按扫描索引打开文件 |
vfs_openbyfile | u32 vfs_openbyfile(void *pcvfile, void **ppvfile, void *ext_name) | 由已打开文件打开关联文件(如歌词) |
vfs_openbyclust | u32 vfs_openbyclust(void *pvfs, void **ppvfile, u32 clust) | 按簇号打开文件 |
vfs_createfile | u32 vfs_createfile(void *pvfs, void **ppvfile, u32 *pindex) | 创建文件,可选回传文件索引 |
所有打开类 API 的通用行为:句柄为空时先 vfs_hdl_malloc() 分配;把文件系统句柄的 ops 拷贝到文件句柄;分发到对应 ops 回调;失败时统一 vfs_file_close() 回滚。
通用封装 API(vfs_fat.c)
| API | 签名 | 说明 |
|---|---|---|
vfs_get_fsize | int vfs_get_fsize(void *pvfile, void *parm) | 获取文件大小(写入 parm),返回 0 表示成功 |
vfs_ftell | int vfs_ftell(void *pvfile, void *parm) | 获取当前读写位置 |
vfs_file_delete | u32 vfs_file_delete(void *pvfile) | 删除文件;失败返回 E_FS_PFILE 或 E_VFS_OPS |
ioctl 命令集(FS_IOCTL_*)
定义于 vfs.h,通过各适配器的 ops->ioctl(pfs/pfile, cmd, arg) 实现,覆盖:
- 文件计数与校验:
FS_IOCTL_GET_FILE_NUM、FS_IOCTL_FILE_CHECK、FS_IOCTL_FILE_TOTAL、FS_IOCTL_FILE_ATTR、FS_IOCTL_FILE_INDEX - 缓存与缓冲控制:
FS_IOCTL_FREE_CACHE、FS_IOCTL_SET_LFN_BUF(长文件名缓冲)、FS_IOCTL_SET_LDN_BUF - 文件名过滤:
FS_IOCTL_SET_NAME_FILTER、FS_IOCTL_SET_EXT_TYPE(后缀类型) - 目录操作:
FS_IOCTL_OPEN_DIR、FS_IOCTL_ENTER_DIR、FS_IOCTL_EXIT_DIR、FS_IOCTL_GET_DIR_INFO、FS_IOCTL_MK_DIR、FS_IOCTL_DIR_FILE_TOTAL - 空间与分区:
FS_IOCTL_GET_FOLDER_INFO、FS_IOCTL_FS_TOTAL、FS_IOCTL_GET_PARTITION_INFO、FS_IOCTL_GET_FREE_SPACE - 扫描控制:
FS_IOCTL_RESET_VFSCAN - 其他:
FS_IOCTL_GETFILE_BYNAME_INDIR(按名称找关联文件)、FS_IOCTL_GET_DISP_INFO(长文件名显示)、FS_IOCTL_GET_ENCFOLDER_INFO(录音文件夹)、FS_IOCTL_SET_VOL(卷标)、FS_IOCTL_FILE_SYNC、FS_IOCTL_FS_INDEX
文件属性与定位常量
#define SEEK_SET 0 /* 从文件头定位 */
#define SEEK_CUR 1 /* 从当前位置定位 */
#define SEEK_END 2 /* 从文件尾定位 */
#define F_ATTR_RO 0x01 /* 只读 */
#define F_ATTR_ARC 0x02 /* 归档(普通文件) */
#define F_ATTR_DIR 0x04 /* 目录 */
#define F_ATTR_VOL 0x08 /* 卷标 */
struct vfs_attr {
u8 attr; // 属性
u32 fsize; // 文件大小
u32 sclust; // 起始簇/地址
};
Source: vfs.h
Usage Examples
示例 1:挂载并读取文件(模式参考)
以下片段展示 vfs_mount 的调用形态——type 为 NULL 时自动跳过 norfs、优先挂载可移动介质文件系统;应用层随后通过返回的句柄打开文件:
u32 vfs_mount(void **ppvfs, void *device, void *type)
{
...
list_for_each_vfs_operation(ops) {
if (NULL != ops->mount) {
if (NULL != type) {
if (0 != strcmp(ops->fs_type, type)) {
continue;
}
} else {
if (0 == strcmp(ops->fs_type, "norfs")) {
continue;
}
}
if (0 == ops->mount(&(pvfs->pfs), device)) {
pvfs->ops = ops;
return 0;
}
...
}
}
...
}
Source: vfs.c
示例 2:读取文件大小与删除文件
上层封装展示了空指针保护与 ops 空检查的范式,删除接口约定"关闭由外部负责":
int vfs_get_fsize(void *pvfile, void *parm)
{
struct imount *p_vfile = pvfile;
struct vfs_operations *ops;
if ((void *)NULL == p_vfile) {
return 0;
}
ops = p_vfile->ops;
if (((void *)NULL != ops) && ((void *)NULL != ops->flen)) {
u32 res;
return ops->flen(p_vfile->pfile, (u32 *)parm);
}
return 0;
}
u32 vfs_file_delete(void *pvfile)
{
struct imount *p_vfile = pvfile;
if ((void *)NULL == p_vfile) {
return E_FS_PFILE;
}
struct vfs_operations *ops;
ops = p_vfile->ops;
if (((void *)NULL != ops) && ((void *)NULL != ops->fdelete)) {
u32 res = E_VFS_OPS;
if (NULL != (p_vfile->pfile)) {
res = ops->fdelete(p_vfile->pfile);
if (0 != res) {
return E_FS_PFILE;
}
}
return res;
}
return E_VFS_OPS;
}
Sources:
Failure Modes, Edge Cases & Concurrency
错误码体系
VFS 层在不同失败场景返回的错误码(源自 errno-base.h,在 vfs.c 中实际使用):
| 错误码 | 触发场景 |
|---|---|
E_NO_VFS | 句柄分配失败(vfs_hdl_malloc() 返回 NULL) |
E_NO_FS | 遍历所有文件系统均挂载失败 |
E_VFS_HDL | 传入的文件系统/文件句柄为 NULL |
E_VFS_OPS | 操作表缺失对应回调(ops->xxx == NULL) |
E_FS_PFILE | 文件私有句柄 pfile 为空或删除失败 |
边界情况
- 句柄惰性分配:所有
vfs_open*/vfs_mount在调用方传入 NULL 句柄时会自动分配,但调用方必须负责最终释放;打开失败路径统一走vfs_file_close回滚,避免泄漏。 - 操作表缺失回调:VFS 层对每个回调都做
NULL检查,未实现的能力返回E_VFS_OPS而非崩溃。新增文件系统时可以只实现必要回调。 type == NULL的默认挂载策略:未指定类型时跳过"norfs",保证默认挂载不会占用片内 Flash 文件系统。若业务需要挂载 nor_fs,必须显式传入"norfs"类型。- 空指针安全:
vfs_get_fsize/vfs_ftell/vfs_file_delete均先校验p_vfile与ops,返回 0 或错误码而非解引用空指针。 - 删除与关闭分离:
vfs_file_delete不关闭文件(注释明确"文件关闭在外面应用"),调用顺序错误(先删后关)可能导致句柄指向已失效的目录项,上层需自行保证顺序。
并发与一致性
- 无锁设计:从源码可见 VFS 层未引入互斥锁。该 SDK 面向单核 MCU,通常由单一任务(如音频任务)串行访问文件系统;若多个任务并发操作同一文件系统,需要上层自行加临界区保护。这是嵌入式文件系统的常见取舍——用协议约束代替锁开销。
- 扫描中断机制:
fscan_interrupt支持可中断的文件扫描(回调callback用于让出 CPU),配合FS_IOCTL_RESET_VFSCAN可重置扫描状态。长时间扫描(如遍历整个 U 盘)不应阻塞系统,这是该接口存在的设计意图。 - 缓存控制:
FS_IOCTL_FREE_CACHE提供主动释放文件系统缓存的能力,在内存紧张或切换介质时可调用。
Performance & Operational Considerations
- 单次间接调用开销:每次文件操作经过
vfs_*→ops->xxx一层函数指针间接调用,在 MCU 上代价可忽略,换来的却是整套文件系统的可插拔性。 - 句柄复用:
vfs_hdl_malloc/vfs_fhdl_free采用句柄池/复用策略(而非每次 malloc),降低频繁开关文件时的堆碎片风险。 - 批量扫描:
fscan_interrupt配合回调允许分片扫描,避免一次性扫描大容量介质造成长时间阻塞;FS_IOCTL_SET_LFN_BUF/SET_LDN_BUF需要上层预分配 512 字节缓冲(见 vfs.h 注释),用于长文件名/长目录名解析。 - 分区与空间查询:
FS_IOCTL_GET_PARTITION_INFO(簇大小、容量)与FS_IOCTL_GET_FREE_SPACE(剩余空间)是上层做空间管理(如录音剩余时长提示)的标准途径。
Extension Points
新增一个文件系统
接入 VFS 只需三步:
- 实现
struct vfs_operations实例:参考fat_resource.c、nor_fs_resource.c、smpl_fat_resource.c、sydf_resource.c(位于sdk/app/bsp/common/fs/下),填充fs_type与所需回调。 - 注册到链接器段:使用
list_for_each_vfs_operation对应的段注册宏(与遍历宏配套,通常通过__attribute__((section))放入指定段),使vfs_init/vfs_mount能发现该操作表。 - 实现挂载与文件私有结构:
mount回调负责把device抽象为pfs私有句柄;openbypath等回调产出pfile私有句柄;struct imount的pfs/pfile字段即存放这些私有指针。
通过 ioctl 扩展能力
对既有文件系统增加非标准能力(如特定介质的状态查询)时,优先在 ops->ioctl 中扩展 FS_IOCTL_* 枚举(vfs.h),VFS 层与上层封装无需改动即可透传命令。
上层便利函数的扩展位置
若需要新的通用文件操作(如 rename、fcopy),当前 VFS 操作表未定义对应回调(旧版接口以注释形式保留在 vfs.h 中,如 frename、fmove、fset_attr),可参考这些注释恢复回调并补充到操作表与封装层。
相关文件索引
本页涉及的实现文件(完整列表由框架自动跟踪):
sdk/app/bsp/common/fs/vfs.c— VFS 核心(init/mount/open 系列/句柄管理)sdk/app/bsp/common/fs/vfs_fat.c— 通用封装(fsize/ftell/delete/fscan 参数解析)sdk/include_lib/fs/vfs.h— VFS 契约(操作表、ioctl 枚举、属性常量)sdk/app/bsp/common/fs/fat/fat_resource.c— FAT 文件系统适配器sdk/app/bsp/common/fs/nor_fs/nor_fs_resource.c— nor_fs 文件系统适配器sdk/app/bsp/common/fs/smpl_fat/smpl_fat_resource.c— simple_fat 适配器sdk/app/bsp/common/fs/sydf/sydf_resource.c— sydf 适配器