文件系统支持
本页介绍 fw-AD16N GP-MCU SDK 中的文件系统支持层:从 VFS(虚拟文件系统)抽象、struct vfs_operations 操作表注册机制,到 FAT、nor_fs、sydf 等具体文件系统实现,以及挂载、打开、读写、扫描、格式化等完整 API 的使用方式与底层控制流。
Purpose and Scope
本页覆盖以下内容:
- VFS 虚拟文件系统层的设计与实现(
vfs.c/vfs.h),包括fs_*宏对vfs_*接口的映射。 - 文件系统操作表
struct vfs_operations的注册机制(REGISTER_VFS_OPERATIONS宏与.vfs_operations段)。 - 内置文件系统实现:FAT(
vfs_fat.c)、nor_fs(NOR Flash 文件系统)、sydf 及其资源注册文件。 - 挂载(mount)、打开(openbypath / openbyindex / openbyfile / openbyclust)、读写、seek、扫描(fscan / fselect)、目录与格式化(vfs_format)等核心接口。
- 启动阶段与音频解码器中的实际使用流程。
以下主题属于其他目录页,不在本页展开:具体存储设备驱动(Flash / SD 卡底层读写)、VM 掉电保存机制、录音业务逻辑。本页聚焦"文件系统"这一抽象层本身。
概述
在 MCU 嵌入式场景中,不同存储介质(内部 NOR Flash、外部 SD 卡等)需要统一的文件读写能力:音乐播放器要枚举并打开歌曲文件,录音功能要按索引创建/追加文件,系统参数区要按路径读取 VM/EEPROM 数据。为此 SDK 实现了一层 VFS(Virtual File System) 抽象:上层代码只与 fs_* / vfs_* 接口打交道,由 VFS 依据"文件系统类型名"(如 "fat"、"norfs")在注册表中找到对应的操作实现并转发调用。
关键设计点:
- 注册表驱动:每个文件系统通过
REGISTER_VFS_OPERATIONS宏把自己的操作函数表放到链接脚本指定的.vfs_operations段中,vfs_mount通过list_for_each_vfs_operation遍历该段逐个尝试挂载。新增文件系统无需改动 VFS 核心代码。 - 句柄分层:
struct imount同时承担"文件系统句柄"(pfs)与"文件句柄"(pfile)两种角色,内部保存ops指针,实现多态分发。 - 双模式编译:
HAS_VFS_EN开启时使用 VFS 完整实现;关闭时fs_*宏直接映射到fat_*API,减小代码体积。 - IOCTL 扩展:目录浏览、文件过滤、长文件名缓存、卷标、空间查询等高级能力统一通过
vfs_ioctl的命令码扩展,避免操作表无限膨胀。
架构
flowchart TD
subgraph sg_App["应用层 (Application)"]
Startup["flash_init 启动流程"]
Decoder["decoder_api / mp_io"]
Player["播放器 / 录音 / 文件浏览"]
end
subgraph sg_API["VFS API 层 (fs_* 宏)"]
VFS["vfs.c 核心分发"]
IOCTL["vfs_ioctl 命令分发"]
end
subgraph sg_Ops["文件系统实现层 (.vfs_operations 段)"]
FAT["fat (vfs_fat / ff_opr)"]
NORFS["norfs (nor_fs)"]
SYDF["sydf"]
end
subgraph sg_Dev["存储设备层"]
Flash["NOR Flash 设备"]
SD["SD 卡 / 外部存储"]
end
Startup --> VFS
Decoder --> VFS
Player --> VFS
VFS --> IOCTL
VFS --> FAT
VFS --> NORFS
VFS --> SYDF
FAT --> SD
NORFS --> Flash
SYDF --> Flash
VFS 是唯一入口:应用层不直接调用 FAT 或 nor_fs 的实现函数,而是调用 fs_mount / fs_openbypath 等宏,由 vfs.c 遍历 .vfs_operations 段中的操作表完成分发。vfs_ioctl 则把目录、过滤、缓存等命令转发给具体文件系统的 ioctl 回调。
主要模块与实现
VFS 核心(vfs.c)
vfs.c 位于 sdk/apps/app/bsp/common/fs/vfs.c,是整个文件系统层的调度中枢。它通过 #pragma code_seg(".vfs.text") 等指令把代码与数据放入独立段,便于链接器布局与裁剪。
初始化:vfs_init() 遍历 .vfs_operations 段,逐个调用已注册文件系统的 init 回调:
void vfs_init(void)
{
struct vfs_operations *ops;
list_for_each_vfs_operation(ops) {
if (NULL != ops->init) {
log_info("%s ops_init!!!\n", ops->fs_type);
ops->init();
}
}
}
Source: vfs.c
挂载分发:vfs_mount 先为句柄分配内存,然后遍历操作表:若调用方指定了 type(如 "fat"),只匹配同名字符串的文件系统;若 type 为 NULL,则跳过 "norfs"(避免误挂载内部录音文件系统)。某文件系统挂载成功后,其 ops 被记录到句柄中并返回 0;若挂载失败则调用其 close_fs 清理并继续尝试下一个:
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;
}
}
u32 err = ops->mount(&(pvfs->pfs), device);
if (0 == err) {
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
打开文件分发:vfs_openbypath 同样先分配文件句柄,把文件系统句柄中的 ops 复制给文件句柄,再调用 ops->openbypath 完成实际打开;句柄非法时返回 E_VFS_HDL,缺少回调时返回 E_VFS_OPS:
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;
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) {
// ... 错误处理
}
return err;
}
Source: vfs.c
文件系统操作表(struct vfs_operations)
vfs.h 定义了所有文件系统必须实现的操作函数指针表。该表是 VFS 多态分发的核心契约:
struct vfs_operations {
const char *fs_type;
void (*init)(void);
u32(*mount)(void **ppfs, void *p_device);
u32(*openbypath)(void *pfs, void **ppfile, const char *path);
u32(*openbyindex)(void *pfs, void **ppfile, u32 index, void *parm);
u32(*openbyfile)(void *pcvfile, void **ppfile, void *ext_name);
u32(*openbyclust)(void *pfs, void **ppfile, u32 clust, void *parm);
u32(*createfile)(void *pfs, void **ppfile, u32 *pindex);
u32(*read)(void *pfile, void *buff, u32 len);
u32(*write)(void *pfile, void *buff, u32 len);
u32(*seek)(void *pfile, u32 offset, u32 mode);
u32(*close_fs)(void **ppfs);
u32(*close_file)(void **ppfile);
u32(*fdelete)(void *);
int (*fget_attr)(void *, void *attr);
int (*flen)(void *, u32 *parm);
int (*ftell)(void *, u32 *parm);
u32(*name)(void *, void *name, u32 len);
int (*ioctl)(void *, int cmd, int arg);
int (*fscan_interrupt)(void *, void *, const char *path, u8 max_deepth, int (*callback)(void));
void (*fscan_release)(void *);
int (*fsel)(void *, void *, int sel_mode, void **, int);
int (*file_crc)(void *pfile);
int (*format)(void **p_fs_hdl, void *device, u32 clust_size, u8 create_new);
};
Source: vfs.h
注意:fs_type 字符串(如 "fat"、"norfs")既是文件系统标识,也是 vfs_mount 中 type 参数匹配的依据。fscan_interrupt / fsel / format 等为可选能力,VFS 分发层不强制要求全部实现。
注册机制(REGISTER_VFS_OPERATIONS)
文件系统通过如下宏把自己的操作表放入 .vfs_operations 链接段:
#define REGISTER_VFS_OPERATIONS(ops) \
const struct vfs_operations ops SEC(.vfs_operations)
Source: vfs.h
段首尾符号 vfs_ops_begin[] / vfs_ops_end[] 由链接脚本提供,list_for_each_vfs_operation 宏据此遍历所有已注册文件系统:
#define list_for_each_vfs_operation(ops) \
for (ops=vfs_ops_begin; ops<vfs_ops_end; ops++)
Source: vfs.h
在 sdk/apps/app/bsp/common/fs/ 目录下,fat_resource.c、nor_fs_resource.c、sydf_resource.c 分别定义并注册对应文件系统的操作表;vfs_resource.c 提供 vfs_resource_init() 完成资源级初始化。这种"段注册"方式使编译期即可静态确定系统支持哪些文件系统,运行时无需动态注册,符合 MCU 裸机/轻量 RTOS 环境的约束。
FAT 文件系统(vfs_fat)
FAT 实现位于 sdk/apps/app/bsp/common/fs/vfs_fat.c,底层基于 fat/ff_opr.h(Petit-FatFs 风格的 FAT 操作)与 fat/tff.h。vfs_fat.h 暴露了 VFS 层的扩展接口,其中最重要的是格式化与目录扫描:
int vfs_format(void **ppvfs, const char *dev_name, const char *type, u32 clust_size, u8 create_new);
int vfs_fget_path(void *pvfile, struct vfscan *fscan, u8 *name, int len, u8 is_relative_path);
Source: vfs_fat.h
vfs_format 的语义值得注意:格式化之后 vfs 指针会被释放,需要重新 mount 才能获得新句柄;clust_size 填 0 表示沿用原簇大小,但 create_new == 1(新建文件系统)时簇大小不能为 0,且必须是 4KB/8KB/16KB/32KB/64KB 之类的对齐值。dev_name 形如 "sd0",type 一般为 "fat"。
目录扫描通过 struct vfscan 完成,配合 VFSCAN_RESET_INFO 缓存扫描结果,避免重复扫描同一设备:
typedef struct vfscan_reset_info {
u16 file_total; // 当前设备文件总数
u16 dir_total; // 当前设备文件夹总数
u8 active; // 当前设备是否有效,无效需要扫描。
u8 scan_over; // 当前设备之前是否扫描过,没有需要扫描。
} VFSCAN_RESET_INFO;
Source: vfs_fat.h
nor_fs(NOR Flash 文件系统)
nor_fs 是为内部 NOR Flash 上"录音文件按索引存取"场景设计的轻量文件系统,定义于 sdk/apps/include_lib/fs/nor_fs/nor_fs.h。它以固定 magic 字标识文件系统,按扇区组织文件:
#define NORFS_MAGIC_NUM 0X11223344
#define REC_FILE_END 0xFE
//文件索引
typedef struct __RECF_INDEX_INFO {
u32 index; //文件索引号
u16 sector; //文件所在扇区
} RECF_INDEX_INFO ;
#define FLASH_PAGE_SIZE 256
#define NORF_SECTOR_SIZE FLASH_PAGE_SIZE
//文件系统句柄
typedef struct __RECFILESYSTEM {
RECF_INDEX_INFO index;
u16 total_file;
u16 first_sector;
u16 last_sector;
u8 sector_bits;
void *hdev;
} NORFS, RECFILESYSTEM, *PRECFILESYSTEM ;
//文件句柄
typedef struct __REC_FILE {
RECF_INDEX_INFO index;
RECFILESYSTEM *pfs;
u32 len;
u32 w_len;
u32 rw_p;
u16 sr;
char name[16];
} NORFILE, REC_FILE;
Source: nor_fs.h
nor_fs 的 API 面向"按文件索引号"操作:norfs_mount 挂载、norfs_createfile 创建新文件(返回索引)、norfs_openbyindex 按索引打开、norfs_read / norfs_write / norfs_seek 读写定位、norfs_save_sr 保存扇区记录。文件名为固定 16 字节缓冲区。这与 FAT 的"路径 + 长文件名"模型完全不同,反映了两种文件系统服务于不同业务:nor_fs 用于录音分段存储,FAT 用于 SD 卡多媒体文件。
sydf 文件系统
syfdf_resource.c 与 sydfile.h 表明 SDK 还支持 sydf 文件系统。sydf 同样面向 Flash 存储场景,注册为 VFS 的一种 fs_type,通过同一套 struct vfs_operations 接入 VFS 层。由于本页源材料有限,其内部实现细节不做展开;从架构上看,它与 FAT、nor_fs 并列,通过段注册机制参与 vfs_mount 的类型匹配与挂载尝试。
fs_* API 宏映射
为了让应用代码不感知 VFS 是否存在,vfs.h 在 HAS_VFS_EN 开启时把 fs_* 全部映射为 vfs_*:
#define fs_resource_init()
#define fs_init vfs_init
#define fs_type_name vfs_type_name
#define fs_mount vfs_mount
#define fs_openbypath vfs_openbypath
#define fs_openbyindex vfs_openbyindex
#define fs_openbyfile vfs_openbyfile
#define fs_openbyclust vfs_openbyclust
#define fs_createfile vfs_createfile
#define fs_read vfs_read
#define fs_write vfs_write
#define fs_file_close vfs_file_close
#define fs_fs_close vfs_fs_close
#define fs_file_name vfs_file_name
#define fs_get_attrs vfs_get_attrs
#define fs_get_fsize vfs_get_fsize
#define fs_ftell vfs_ftell
#define fs_file_delete vfs_file_delete
#define fs_fscan_new vfs_fscan_new
#define fs_fscan vfs_fscan
#define fs_fscan_release vfs_fscan_release
#define fs_select vfs_select
#define fs_ioctl vfs_ioctl
#define fs_mk_dir vfs_mk_dir
#define fs_get_encfolder_info vfs_get_encfolder_info
#define fs_seek vfs_seek
Source: vfs.h
当 HAS_VFS_EN 未定义(裁剪场景)时,fs_* 宏直接映射到 fat_* 函数(如 fs_mount → fat_monut_api、fs_type_name 固定返回 "fat"),从而在关闭 VFS 层的情况下依然可以使用 FAT 文件系统,见 vfs.h。
核心流程
启动阶段:挂载与参数区读取(flash_init.c)
系统启动时,sdk/apps/app/bsp/start/flash_init.c 通过 VFS 读取系统参数区。流程是"挂载 → 打开 → 读取 → 关闭"的典型四步,且会执行两次(一次访问 VM 区,一次访问 EEPROM 区):
err = vfs_mount(&pvfs, (void *)NULL, (void *) NULL);
ASSERT(!err, "fii vfs mount : 0x%x\n", err)
err = vfs_openbypath(pvfs, &pvfile, "/app_area_head/VM");
ASSERT(!err, "fii vfs openbypath : 0x%x\n", err)
// ... 读取 VM 数据 ...
vfs_file_close(&pvfile);
vfs_fs_close(&pvfs);
Source: flash_init.c
sequenceDiagram
participant Boot as 系统启动
participant VFS as vfs.c 分发层
participant Ops as vfs_operations 表
participant FAT as fat 实现
participant Flash as Flash/SD 设备
Boot->>VFS: vfs_mount(&pvfs, NULL, NULL)
VFS->>Ops: 遍历 .vfs_operations 段,匹配 fs_type
VFS->>FAT: ops->mount(&pfs, device)
FAT->>Flash: 读取设备/分区信息
FAT-->>VFS: 返回 0(挂载成功)
VFS-->>Boot: 记录 ops 到 imount,返回 0
Boot->>VFS: vfs_openbypath(pvfs, &pvfile, "/app_area_head/VM")
VFS->>FAT: ops->openbypath(pfs, &pfile, path)
FAT-->>VFS: 返回 0(打开成功)
VFS-->>Boot: 返回文件句柄 pvfile
Boot->>VFS: vfs_read / vfs_ioctl 读取参数
Boot->>VFS: vfs_file_close(&pvfile)
Boot->>VFS: vfs_fs_close(&pvfs)
挂载参数 type = NULL 时,vfs_mount 会自动跳过 "norfs",确保默认挂载到 FAT(或 sydf)文件系统;随后用虚拟路径 /app_area_head/VM、/app_area_head/EEPROM 打开分区文件,说明 FAT 实现支持把 Flash 分区映射为路径。第二次挂载流程(第 107-115 行)与第一次完全相同,只是目标路径换为 /app_area_head/EEPROM,见 flash_init.c。
播放流程:解码器中的文件读写(decoder / mp_io)
音频解码器通过 fs_* 宏访问文件。decoder_api.c 使用 fs_openbyfile 依据已打开的媒体文件句柄打开同名 "mio" 扩展文件(媒体信息文件):
if (pfile) {
mio_res = fs_openbyfile(pfile, &mio_pfile, "mio");
}
Source: decoder_api.c
底层流式读取在 mp_io.c 中实现:先 fs_seek 定位,再 fs_read 读取并返回实际读到的字节数。注释明确 addr 是相对文件起始位置的偏移:
fs_seek(obj->p_file, addr, SEEK_SET); //addr为相对文件起始位置的偏移,len为多少个byte
rlen = fs_read(obj->p_file, buf, len);
return rlen;
Source: mp_io.c
flowchart LR
A["解码器请求数据"] --> B["fs_seek(pfile, addr, SEEK_SET)"]
B --> C["fs_read(pfile, buf, len)"]
C --> D{"VFS 分发到 fat 实现"}
D --> E["返回实际读取字节数 rlen"]
E --> F["解码器继续解码"]
该流程说明:VFS 把"路径/索引打开"与"流式读写"解耦——打开阶段只发生一次(按路径或索引),而播放过程中的大量小粒度读操作直接走 read/seek 回调,减少路径解析开销,这是音频实时播放场景的关键性能设计。
使用示例
示例 1:挂载文件系统并打开分区文件(启动代码)
err = vfs_mount(&pvfs, (void *)NULL, (void *) NULL);
ASSERT(!err, "fii vfs mount : 0x%x\n", err)
err = vfs_openbypath(pvfs, &pvfile, "/app_area_head/VM");
ASSERT(!err, "fii vfs openbypath : 0x%x\n", err)
挂载时 type 传 NULL 表示"自动选择",vfs_mount 会跳过 norfs 依次尝试其余文件系统;打开路径采用 /分区名/文件 的虚拟路径语法。使用完毕后必须配对调用 vfs_file_close 与 vfs_fs_close 释放句柄。
Source: flash_init.c
示例 2:按已打开文件派生同名扩展文件(解码器)
if (pfile) {
mio_res = fs_openbyfile(pfile, &mio_pfile, "mio");
}
fs_openbyfile 接收一个已打开的文件句柄和扩展名,用于打开与媒体文件同名的辅助文件(如歌词、MIO 信息文件),避免再次按路径解析。
Source: decoder_api.c
示例 3:流式读取(解码器底层 IO)
fs_seek(obj->p_file, addr, SEEK_SET); //addr为相对文件起始位置的偏移,len为多少个byte
rlen = fs_read(obj->p_file, buf, len);
return rlen;
SEEK_SET 从文件头开始定位,fs_read 返回实际读取长度,调用方(解码器)据此判断文件是否结束。
Source: mp_io.c
示例 4:注册一个新的文件系统操作表
#define REGISTER_VFS_OPERATIONS(ops) \
const struct vfs_operations ops SEC(.vfs_operations)
自定义文件系统只需定义 struct vfs_operations 实例(fs_type 填写唯一名字,如 "myfs"),并用此宏放入 .vfs_operations 段,即可被 vfs_init / vfs_mount 自动发现。
Source: vfs.h
配置选项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
HAS_VFS_EN | 编译宏 | 0/1(由工程配置) | 是否启用 VFS 层;关闭时 fs_* 宏直接映射到 fat_* API |
vfs_mount 的 type 参数 | void * | NULL | 指定文件系统类型名("fat"/"norfs"/"sydf");NULL 时跳过 norfs 自动选择 |
vfs_format 的 clust_size | u32 | 0(沿用原簇) | 新建文件系统时须为非 0 的对齐值(4K/8K/16K/32K/64K) |
vfs_format 的 create_new | u8 | 0 | 0=维持原文件系统类型;1=新建文件系统(须配合非 0 簇大小) |
FS_IOCTL_SET_LFN_BUF | ioctl 命令 | — | 设置长文件名缓冲(512 字节) |
FS_IOCTL_SET_LDN_BUF | ioctl 命令 | — | 设置长目录名缓冲(512 字节) |
VFS_FILE_NAME_LEN | 常量 | 16 | 短文件名缓冲区长度,见 vfs.h |
FLASH_PAGE_SIZE | 常量 | 256 | nor_fs 的扇区/页大小,见 nor_fs.h |
文件属性与定位模式常量
| 常量 | 值 | 含义 |
|---|---|---|
SEEK_SET / SEEK_CUR / SEEK_END | 0 / 1 / 2 | seek 定位基准:文件头 / 当前位置 / 文件尾 |
F_ATTR_RO / F_ATTR_ARC / F_ATTR_DIR / F_ATTR_VOL | 0x01 / 0x02 / 0x04 / 0x08 | 文件属性:只读 / 归档 / 目录 / 卷标 |
Source: vfs.h
vfs_ioctl 命令码(FS_IOCTL_*)
| 命令 | 用途 |
|---|---|
FS_IOCTL_GET_FILE_NUM | 获取文件数量 |
FS_IOCTL_FILE_CHECK | 文件存在性检查 |
FS_IOCTL_FREE_CACHE | 释放文件系统缓存 |
FS_IOCTL_SET_NAME_FILTER | 设置文件过滤规则 |
FS_IOCTL_GET_FOLDER_INFO_3 | 获取文件夹序号与文件数 |
FS_IOCTL_SET_LFN_BUF / FS_IOCTL_SET_LDN_BUF | 设置长文件名/目录名缓冲(512B) |
FS_IOCTL_SET_EXT_TYPE | 设置后缀类型过滤 |
FS_IOCTL_OPEN_DIR / ENTER_DIR / EXIT_DIR / GET_DIR_INFO | 目录浏览 |
FS_IOCTL_GETFILE_BYNAME_INDIR | 由歌曲名获取歌词文件 |
FS_IOCTL_GET_DISP_INFO | 长文件名显示信息获取 |
FS_IOCTL_MK_DIR | 创建文件夹 |
FS_IOCTL_GET_ENCFOLDER_INFO | 获取录音文件夹信息 |
FS_IOCTL_SET_VOL | 设置卷标 |
FS_IOCTL_DIR_FILE_TOTAL / FILE_TOTAL / FS_TOTAL | 目录/文件/文件系统总数统计 |
FS_IOCTL_FILE_ATTR / FILE_SYNC / FILE_INDEX / FS_INDEX | 属性/同步/索引查询 |
FS_IOCTL_RESET_VFSCAN | 重置扫描缓存 |
FS_IOCTL_GET_PARTITION_INFO / GET_FREE_SPACE | 分区信息(簇大小/容量)与剩余空间 |
FS_IOCTL_GET_PATH | 获取相对/绝对路径 |
Source: vfs.h
API 参考
VFS 核心接口(vfs.h)
| 函数 | 作用 |
|---|---|
void vfs_init(void) | 遍历操作表,调用各文件系统 init 回调 |
u32 vfs_mount(void **ppvfs, void *device, void *type) | 挂载文件系统;成功返回 0,失败返回 E_NO_VFS / E_NO_FS |
u32 vfs_openbypath(void *pvfs, void **ppvfile, const char *path) | 按路径打开文件(如 /app_area_head/VM) |
u32 vfs_openbyindex(void *pvfs, void **ppvfile, u32 index, void *parm) | 按索引打开文件(录音/文件列表场景) |
u32 vfs_openbyfile(void *pcvfile, void **ppvfile, void *ext_name) | 由已打开文件派生同名扩展文件 |
u32 vfs_openbyclust(void *pvfs, void **ppvfile, u32 clust, void *parm) | 按簇号打开文件 |
u32 vfs_createfile(void *pvfs, void **ppvfile, u32 *pindex) | 创建文件并返回索引 |
u32 vfs_read(void *pvfile, void *buf, u32 len) / vfs_write | 读写文件,返回实际字节数 |
u32 vfs_seek(void *pvfile, u32 offset, u32 mode) | 定位(SEEK_SET/CUR/END) |
u32 vfs_file_close(void **ppvfile) / vfs_fs_close(void **ppvfs) | 关闭文件/文件系统句柄 |
u32 vfs_file_name(void *pvfile, void *name, u32 len) | 获取文件名 |
int vfs_get_attrs(void *pvfile, void *pvfs_attr) | 获取 struct vfs_attr(属性/大小/簇地址) |
int vfs_get_fsize(void *pvfile, void *parm) / vfs_ftell | 获取文件大小 / 当前偏移 |
u32 vfs_file_delete(void *pvfile) | 删除文件 |
int vfs_ioctl(void *pvfile, int cmd, int arg) | 扩展命令分发(见上方命令码表) |
int vfs_file_crc(void *pfile) | 计算文件 CRC |
扫描与格式化扩展接口(vfs_fat.h)
struct vfscan *vfs_fscan(void *pvfs, const char *path, const char *arg, u8 max_deepth, int (*callback)(void))— 扫描目录,arg为扩展名过滤,max_deepth限制递归深度。struct vfscan *vfs_fscan_new(..., struct vfscan *fsn, struct vfscan_reset_info *info)— 可复用扫描句柄并携带复位缓存信息。int vfs_select(void *pvfs, void **ppvfile, struct vfscan *fs, int sel_mode, int arg)— 在扫描结果中选择文件并打开。int vfs_mk_dir(void *pvfs, char *folder, u8 mode)— 创建目录。int vfs_format(void **ppvfs, const char *dev_name, const char *type, u32 clust_size, u8 create_new)— 格式化设备;成功后 vfs 句柄被释放,须重新 mount。
Source: vfs_fat.h
nor_fs 接口(nor_fs.h)
void norfs_init(u32 sector_start, u32 sector_end, u32 sector_bit)— 初始化文件系统在 Flash 中的扇区范围。u32 norfs_mount(RECFILESYSTEM **ppfs, void *p_device)— 挂载(校验NORFS_MAGIC_NUM)。u32 norfs_createfile(RECFILESYSTEM *pfs, REC_FILE **ppfile, u32 *pindex)— 创建录音文件,返回索引号。u32 norfs_openbyindex(RECFILESYSTEM *pfs, REC_FILE **ppfile, u32 index)— 按索引打开。u16 norfs_read(REC_FILE *pfile, u8 *buff, u16 btr)/u16 norfs_write(REC_FILE *pfile, u8 *buff, u16 btw)— 读写,单位字节。u32 norfs_seek(REC_FILE *pfile, u32 offsize, u32 type)— 定位。u32 norfs_filelen(REC_FILE *pfile, u32 *plen)— 获取文件长度。int norfs_ioctl(REC_FILE *pfile, int cmd, int arg)/u32 norfs_name(REC_FILE *pfile, char *name, u32 len)— 控制命令与文件名。
Source: nor_fs.h
故障模式、边界情况与并发
错误码与句柄生命周期
VFS 层定义了一组错误码,vfs_mount 与 vfs_openbypath 均通过返回值上报:
| 错误码 | 触发场景 |
|---|---|
E_NO_VFS | 句柄内存分配失败(vfs_hdl_malloc 返回 NULL) |
E_NO_FS | 遍历所有已注册文件系统均挂载失败(此时句柄已被 vfs_fhdl_free 释放) |
E_VFS_HDL | 传入的文件系统句柄为 NULL |
E_VFS_OPS | 文件系统操作表中缺少对应回调(如未实现 openbypath) |
关键行为:vfs_mount 中某个文件系统挂载失败时,会先调用其 close_fs 做资源清理,再尝试下一个;全部失败后释放并置空句柄。因此调用方不应在失败后继续使用 pvfs。vfs_format 成功返回后 vfs 指针也会被释放,必须重新 mount——这是文档注释中明确强调的约束(见 vfs_fat.h)。
边界情况
- type 为 NULL 时跳过 norfs:
vfs_mount默认挂载行为刻意排除"norfs",避免把内部录音文件系统误当主文件系统挂载。若业务需要显式挂载 norfs,必须传入"norfs"类型名。 - 簇大小约束:
vfs_format在create_new == 1时若clust_size == 0会失败;格式化为既有文件系统(create_new == 0)时clust_size可为 0 沿用原值。 - 长文件名缓冲:LFN/LDN 缓冲通过
FS_IOCTL_SET_LFN_BUF/FS_IOCTL_SET_LDN_BUF显式设置(各 512 字节),未设置时长文件名/长目录名显示(FS_IOCTL_GET_DISP_INFO)可能截断。 - 扫描缓存失效:
VFSCAN_RESET_INFO.active / scan_over标志用于避免重复扫描;设备插拔或文件变更后须通过FS_IOCTL_RESET_VFSCAN或重新构造vfscan_reset_info使缓存失效,否则vfs_select可能返回陈旧结果。 - nor_fs 容量上限:
RECFILESYSTEM中total_file、first_sector、last_sector均为 u16,文件总数与扇区寻址受 16 位范围限制;REC_FILE.name[16]固定短文件名。
并发与可重入性
SDK 为 MCU 裸机/轻量 RTOS 环境设计,VFS 层不提供内部互斥锁:vfs_mount / vfs_openbypath 中的 vfs_hdl_malloc 属于共享资源分配,若多个任务并发挂载或打开文件,需要业务层自行加锁保护。文件句柄 struct imount 保存各自的 ops 与 pfile,同一文件系统句柄可派生出多个文件句柄,但同一文件句柄的 read/write/seek 操作不可并发(内部游标 rw_p、w_len 等状态共享)。音频解码链路(mp_io.c)是单任务顺序读,天然满足该约束。
性能与运维
- 打开一次、流式读取:播放场景只按路径/索引打开一次文件,随后反复
fs_seek+fs_read小粒度读取;VFS 分发只做一次 ops 解析,热路径开销可控。 - 扫描缓存:
vfscan+VFSCAN_RESET_INFO缓存设备文件/目录总数,避免每次浏览都全盘扫描;配合FS_IOCTL_FREE_CACHE可在内存紧张时释放缓存。 - 段布局优化:
vfs.c通过#pragma code_seg(".vfs.text")等指令将 VFS 代码/数据集中到专用段,便于链接器按需裁剪(不启用 VFS 时整段剔除),同时改善指令缓存局部性。 - 双模式裁剪:
HAS_VFS_EN = 0时直接使用fat_*API,省去vfs_hdl_malloc的句柄开销与 ops 遍历,适合对 RAM/Flash 极其敏感的配置。
扩展点
- 新增文件系统:定义
struct vfs_operations实例,fs_type使用唯一字符串,用REGISTER_VFS_OPERATIONS放入.vfs_operations段;vfs_init自动调用其init,vfs_mount按其mount尝试挂载。无需修改 VFS 核心代码。 - 类型化挂载选择:调用
vfs_mount(pvfs, device, "myfs")即可精确指定文件系统,多个文件系统(FAT + nor_fs + sydf)可共存于同一镜像。 - ioctl 命令扩展:文件过滤(
FS_IOCTL_SET_NAME_FILTER)、后缀过滤(FS_IOCTL_SET_EXT_TYPE)、录音文件夹信息(FS_IOCTL_GET_ENCFOLDER_INFO)等业务能力通过命令码接入,新增命令只影响具体文件系统实现,不破坏 VFS 契约。 - 格式化策略:
vfs_format的create_new参数允许在"保留原文件系统"与"强制新建"之间选择,可在此基础上封装上层"出厂格式化/用户格式化"策略。
相关链接
- vfs.h(VFS API 与 fs_* 宏定义)
- vfs.c(VFS 核心分发实现)
- vfs_fat.h(FAT 扫描/格式化扩展接口)
- vfs_fat.c(FAT 文件系统实现)
- nor_fs.h(NOR Flash 录音文件系统)
- flash_init.c(启动挂载流程)
- decoder_api.c(解码器文件打开)
- mp_io.c(解码器底层流式读写)
- FAT 底层协议:见
sdk/apps/include_lib/fs/fat/下的tff.h、ff_opr.h、fat_resource.h - 存储设备驱动与 VM 掉电保护:参见同目录下"存储设备"相关页面