文件系统 (FAT / nor_fs / SYDF)
AW30N BLE SDK 通过统一的 VFS(虚拟文件系统)层对底层多种文件系统进行抽象,提供 FAT(TF 卡/U 盘等块设备)、nor_fs(片内 SPI NOR Flash 简易文件系统)以及 SYDF(录音专用文件系统)三类存储后端。本文档从 VFS 挂载框架出发,深入讲解各文件系统的数据结构、API 行为与实际调用链。
Purpose and Scope
本页覆盖以下内容:
- VFS 抽象层(
vfs.h/vfs.c)的挂载、打开、读写分发机制; - FAT 文件系统后端(
vfs_fat.c)及其基于 FatFs 的操作封装; - nor_fs 简易 NOR Flash 文件系统(
nor_fs.h)的扇区式存储结构与全部 API; - SYDF(挂载类型
"sydfile")在录音场景(enc_in_norfs.c/enc_in_fatfs.c)中的使用方式。
与文件系统相关的媒体播放流程、录音编码流程属于各自独立页面,本页只说明它们如何通过 fs_mount / fs_openbypath 与文件系统交互。底层存储设备驱动(SPI NOR、SD/MMC、USB Mass Storage)也不在本页展开。
Overview
在嵌入式 BLE 音频 SoC(如 AW30N)中,数据存储需求多样:音乐文件需要大容量、可跨设备移植的 FAT 卷(TF 卡/U 盘);录音与系统数据需要掉电可靠、无日志型磨损的片内 Flash 存储;录音文件在低端场景下又需要极简的"顺序扇区追加"模型。SDK 没有让上层业务直接依赖某一具体文件系统,而是定义了 struct vfs_operations 函数表作为统一契约:
- 每个文件系统后端注册一个
vfs_operations实例,通过list_for_each_vfs_operation链入系统; - 上层调用
fs_mount(&pfs, device, type)时,VFS 遍历函数表,按fs_type字符串匹配(例如"fat"、"norfs"、"sydfile")找到对应后端并执行其mount; - 打开文件、读写、seek、ioctl 等操作全部经由
pvfs->ops函数指针分发到具体后端。
这种设计让业务代码(音乐、录音、升级)与存储介质解耦:同一套 fs_* API 既可用于 FAT 卷,也可用于 nor_fs 卷,切换存储介质只需改变挂载时的 type 参数。
Architecture
flowchart TD
subgraph sg_App["应用层 (mbox_flash)"]
Music["music_device.c<br/>fs_mount(type=sydfile)"]
Record["record/enc_in_fatfs.c<br/>enc_in_norfs.c"]
PlayFile["simple_play_file.c<br/>fs_mount(type=sydfile)"]
end
subgraph sg_VFS["VFS 抽象层 (vfs.c / vfs.h)"]
VFSInit["vfs_init()<br/>遍历 ops 链调用 init"]
VFSMount["vfs_mount()<br/>按 fs_type 匹配后端"]
VFSOpen["vfs_openbypath()<br/>vfs_openbyindex()"]
Ops["struct vfs_operations 函数表<br/>(read/write/seek/ioctl/format...)"]
VFSInit --> VFSMount
VFSMount --> Ops
VFSOpen --> Ops
end
subgraph sg_Backend["文件系统后端"]
FAT["vfs_fat.c<br/>fs_type='fat'"]
NORFS["nor_fs_resource.c<br/>fs_type='norfs'"]
SYDF["SYDF 录音文件系统<br/>fs_type='sydfile'"]
end
subgraph sg_Dev["存储设备"]
SD[(TF卡 / U盘<br/>块设备)]
NOR[(片内 SPI NOR Flash<br/>页大小 256B)]
end
Music --> VFSMount
Record --> VFSMount
PlayFile --> VFSMount
VFSMount --> FAT
VFSMount --> NORFS
VFSMount --> SYDF
FAT --> SD
NORFS --> NOR
SYDF --> NOR
架构要点:
- VFS 层是无状态分发器:
vfs_mount为每个挂载点分配imount句柄(vfs_hdl_malloc),成功后将pvfs->ops指向匹配后端的vfs_operations;后续所有操作都通过该函数表间接调用(见 vfs.c)。 - norfs 是默认后备后端:
vfs_mount在type == NULL时会显式跳过"norfs"类型,优先尝试 FAT 等可移动卷,避免误挂载片内 Flash(vfs.c)。 - SYDF 复用 nor_fs 物理层:
"sydfile"挂载点用于录音场景,底层仍落在片内 NOR Flash 上,但提供录音专用的文件组织语义(见enc_in_norfs.c的#pragma bss_seg(".enc_in_norfs.data.bss")分区,enc_in_norfs.c)。
VFS 抽象层详解
统一操作契约:struct vfs_operations
所有文件系统后端必须实现 struct vfs_operations 函数表(vfs.h)。它包含 24 个函数指针,覆盖文件系统全生命周期:
| 指针成员 | 职责 |
|---|---|
fs_type | 类型名("fat" / "norfs" / "sydfile"),挂载匹配依据 |
init | 后端初始化,由 vfs_init() 批量调用 |
mount | 挂载设备到 void **ppfs |
openbypath / openbyindex / openbyfile / openbyclust | 四种打开方式:按路径、按索引、按已有文件、按簇号 |
createfile | 创建新文件,返回文件索引 |
read / write / seek | 顺序读写与定位 |
close_fs / close_file | 卸载文件系统 / 关闭文件 |
fdelete / fget_attr / flen / ftell / name | 删除、取属性、取长度、取偏移、取文件名 |
ioctl | 扩展控制命令(见下方 FS_IOCTL 枚举) |
fscan_interrupt / fscan_release / fsel | 可中断扫描、扫描资源释放、文件选择 |
file_crc | 文件 CRC 校验 |
format | 格式化分区(clust_size、create_new) |
挂载分发逻辑(vfs_mount)
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 { // 未指定:跳过 norfs
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
设计意图:挂载失败时逐后端尝试(fallback),未指定类型时自动避开 "norfs",保证默认路径优先挂载可移动媒体;每次失败都会调用 close_fs 清理半初始化状态,最后统一返回 E_NO_FS 并归还句柄,避免句柄泄漏。
FS_IOCTL 扩展命令
vfs_operations.ioctl 通过 FS_IOCTL_* 枚举(vfs.h)向应用暴露后端能力,包括:
- 扫描/目录:
FS_IOCTL_GET_FILE_NUM、FS_IOCTL_FILE_CHECK、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_SET_NAME_FILTER(文件过滤)、FS_IOCTL_SET_LFN_BUF/FS_IOCTL_SET_LDN_BUF(长文件名/长目录名缓冲,512B)、FS_IOCTL_GET_DISP_INFO(长文件名获取)、FS_IOCTL_GETFILE_BYNAME_INDIR - 容量/属性:
FS_IOCTL_FILE_TOTAL、FS_IOCTL_FS_TOTAL、FS_IOCTL_FILE_ATTR、FS_IOCTL_GET_PARTITION_INFO(簇大小/容量)、FS_IOCTL_GET_FREE_SPACE、FS_IOCTL_SET_VOL(卷标) - 同步/索引:
FS_IOCTL_FILE_SYNC、FS_IOCTL_FILE_INDEX、FS_IOCTL_FS_INDEX、FS_IOCTL_RESET_VFSCAN、FS_IOCTL_GET_PATH - 录音:
FS_IOCTL_GET_ENCFOLDER_INFO(获取录音文件信息) - 未实现项:
FS_IOCTL_GET_ERR_CODE(注释标明"暂不支持")
FAT 文件系统后端
FAT 后端实现在 sdk/apps/app/bsp/common/fs/vfs_fat.c,对外头文件为 vfs_fat.h。其内部基于 FatFs(fat/ff_opr.h、fat/fat_resource.h)实现:
- 适用于 TF 卡、U 盘等块设备;支持目录树、长文件名(配合
FS_IOCTL_SET_LFN_BUF提供 512B 缓冲)、多分区(FS_IOCTL_GET_PARTITION_INFO返回簇大小与容量)。 - 提供
fscan_interrupt可中断扫描,配合回调函数避免扫描大容量卡时阻塞系统,这对音频播放器"上电枚举曲目"场景至关重要。 format操作支持指定clust_size与create_new标志,用于录音文件系统的按簇格式化。
vfs_fat.c 与 nor_fs_resource.c 同目录(bsp/common/fs/),说明 FAT 后端与 nor_fs 后端共享同一套 VFS 注册机制(通过 list_for_each_vfs_operation 链入,见 vfs_resource.c 与 nor_fs_resource.c)。
nor_fs:片内 NOR Flash 简易文件系统
设计定位
nor_fs 是杰理自研的轻量文件系统,专门面向片内 SPI NOR Flash 的录音/系统数据存储。它不维护目录树、不做磨损均衡,而是采用固定扇区 + 文件索引表的极简模型:每个文件占据若干 256B 扇区,文件系统头部记录索引号与起始扇区。这种设计换取了极低的 RAM/ROM 占用(句柄仅几十字节)和确定性的写入行为,适合录音这类"顺序追加、少量文件"的场景。
核心数据结构(nor_fs.h)
#define NORFS_MAGIC_NUM 0X11223344 // Flash 头部魔数
#define REC_FILE_END 0xFE // 文件结束标记
// 文件索引:索引号 + 所在扇区
typedef struct __RECF_INDEX_INFO {
u32 index; // 文件索引号
u16 sector; // 文件所在扇区
} RECF_INDEX_INFO;
// 文件系统句柄
typedef struct __RECFILESYSTEM {
RECF_INDEX_INFO index;
u16 total_file; // 文件总数
u16 first_sector; // 起始扇区
u16 last_sector; // 结束扇区
u8 sector_bits; // 扇区大小(以 bit 表达,决定寻址粒度)
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; // 起始扇区(start sector)
char name[16]; // 文件名(固定 16 字节)
} NORFILE, REC_FILE;
Source: nor_fs.h
关键设计点:
FLASH_PAGE_SIZE 256与NORF_SECTOR_SIZE相等,说明 nor_fs 的扇区粒度直接对齐 NOR Flash 编程页,一次写入恰好一页,简化了驱动适配;sector_bits以 bit 表达扇区大小(如 8 表示 256B),内部换算成地址位移,避免乘除法开销;REC_FILE.name[16]使用定长 8.3 风格名称,符合 FAT 兼容的短文件名约定;norfs_flash_info_t(含norfs_magic_num/first_sector/last_sector/sector_size)用于在 Flash 中持久化分区几何信息,挂载时据此校验。
对外 API 一览
nor_fs 暴露过程式 C API(非函数表),供 nor_fs_resource.c 封装成 vfs_operations 后接入 VFS:
| 函数 | 行为 |
|---|---|
norfs_init_api(void) | 初始化 nor_fs 内部状态 |
norfs_init(start, end, sector_bit) | 划定 Flash 扇区区间并设置扇区粒度 |
norfs_mount(RECFILESYSTEM **ppfs, void *p_device) | 挂载设备,读取头部索引信息 |
norfs_createfile(pfs, REC_FILE **ppfile, u32 *pindex) | 创建新文件并分配索引 |
norfs_openbyindex(pfs, REC_FILE **ppfile, u32 index) | 按索引打开既有文件 |
norfs_read(pfile, buff, btr) / norfs_write(pfile, buff, btw) | 顺序读写,返回实际读写字节数 |
norfs_seek(pfile, offsize, type) | 按 SEEK_SET/CUR/END 定位 |
norfs_closefile(REC_FILE **ppfile) / norfs_filelen(pfile, plen) | 关闭文件 / 查询长度 |
norfs_save_sr(pfile, sr) | 保存起始扇区(掉电恢复用) |
norfs_ioctl(pfile, cmd, arg) | 扩展控制 |
norfs_name(pfile, name, len) | 取文件名 |
这些 API 的签名全部来自 nor_fs.h,是上层封装的唯一依据。
SYDF:录音文件系统(挂载类型 "sydfile")
SYDF 不是独立的第三套实现,而是面向录音应用的文件系统视图:上层以 "sydfile" 类型挂载,实际落到 nor_fs(片内 Flash)或 FAT(外部卡)之上,由录音编码层决定物理后端。
挂载入口
音乐与录音业务通过 fs_mount 指定 "sydfile" 类型:
if (INNER_FLASH_RO == dev_index) {
err = fs_mount(&ppctl->pfs, p_device, (void *)"sydfile");
if (0 != err) {
// 挂载失败处理
}
}
...
if (0 == memcmp(fs_name, "sydfile", sizeof("sydfile"))) {
/* 按 sydfile 语义处理目录/文件 */
}
Source: music_device.c
录音文件的实际写入由两个编码入口完成,按存储介质分流:
enc_in_norfs.c:录音写入 nor_fs(片内 Flash),文件头使用#pragma bss_seg(".enc_in_norfs.data.bss")等段属性,将数据固定在独立内存段,避免与播放缓冲竞争;enc_in_fatfs.c:录音写入 FAT 卷(外部存储),同样通过段属性隔离(.enc_in_fatfs.*)。
两者对应 FS_IOCTL_GET_ENCFOLDER_INFO 命令——上层查询录音文件夹信息时,由 VFS 分发到当前挂载的后端,从而统一了"录音文件浏览"逻辑,无论录音落在片内还是卡上。
简单的播放路径示例
u32 err = fs_mount(&ppctl->pfs, ppctl->device, "sydfile");
if (err) {
// 挂载失败处理
}
Source: simple_play_file.c
VFS 还内置了演示函数 vfs_demo_sydfs()(vfs.c),展示 SYDF 挂载-打开-读取的完整调用序列,主程序 main.c 中以注释形式保留了调用示例(main.c)。
Core Flow:文件系统挂载与读写调用链
挂载 → 打开 → 读取(SYDF 场景)
sequenceDiagram
participant App as 应用 (music_device)
participant VFS as VFS (vfs.c)
participant Ops as vfs_operations 表
participant NOR as nor_fs 后端
participant FLASH as SPI NOR Flash
App->>VFS: fs_mount(&pfs, device, "sydfile")
activate VFS
VFS->>VFS: vfs_hdl_malloc() 分配 imount
VFS->>VFS: list_for_each_vfs_operation 匹配 fs_type
VFS->>Ops: ops->mount(&pfs, device)
Ops->>NOR: norfs_mount(&pfs, device)
NOR->>FLASH: 读取头部 (magic/first_sector)
FLASH-->>NOR: norfs_flash_info_t
NOR-->>Ops: 0 (成功)
Ops-->>VFS: 0
VFS->>VFS: pvfs->ops = ops (绑定函数表)
VFS-->>App: 0
deactivate VFS
App->>VFS: fs_openbyindex(pfs, &pfile, index)
activate VFS
VFS->>Ops: ops->openbyindex(pfs, &pfile, index, NULL)
Ops->>NOR: norfs_openbyindex(pfs, &pfile, index)
NOR-->>Ops: 0
Ops-->>VFS: 0
VFS-->>App: pfile 就绪
deactivate VFS
App->>VFS: fs_read(pfile, buf, len)
activate VFS
VFS->>Ops: ops->read(pfile, buf, len)
Ops->>NOR: norfs_read(pfile, buf, btr)
NOR->>FLASH: 读 256B 页
FLASH-->>NOR: 数据
NOR-->>Ops: 实际字节数
Ops-->>VFS: 实际字节数
VFS-->>App: 实际字节数
deactivate VFS
调用链要点:VFS 层自身不做任何介质操作,所有读写最终都落在后端函数上;imount.ops 在 mount 成功那一刻被绑定,之后文件操作无需再查表,避免每次调用都遍历注册链。
录音写入(nor_fs 顺序追加模型)
flowchart TD
Start([录音开始]) --> Create["norfs_createfile<br/>分配索引 index + 起始扇区 sr"]
Create --> Write["norfs_write<br/>按 256B 页顺序写入<br/>更新 w_len / rw_p"]
Write --> Full{"扇区写满?"}
Full -->|"否"| Write
Full -->|"是"| Next["推进到下一扇区<br/>sector + 1"]
Next --> More{"录音继续?"}
More -->|"是"| Write
More -->|"否"| Save["norfs_save_sr(pfile, sr)<br/>持久化起始扇区"]
Save --> Close["norfs_closefile<br/>记录 REC_FILE_END (0xFE)"]
Close --> End([录音结束])
nor_fs 的写入不覆盖既有数据、只做顺序追加,天然适配 NOR Flash "只能 1→0、擦除按块" 的物理特性;norfs_save_sr 在关闭前落盘起始扇区,保证掉电后仍能通过索引恢复文件。
Usage Examples
示例 1:按设备类型选择文件系统挂载
if (INNER_FLASH_RO == dev_index) {
err = fs_mount(&ppctl->pfs, p_device, (void *)"sydfile");
if (0 != err) {
log_info("mount sydfile err 0x%x", err);
}
} else {
err = fs_mount(&ppctl->pfs, p_device, (void *)"fat");
}
Source: music_device.c
内部分区(INNER_FLASH_RO)挂 SYDF/nor_fs,外部可移动设备挂 FAT——这正是 VFS 多后端共存的典型用法。
示例 2:nor_fs 原生 API 调用序列
RECFILESYSTEM *pfs = NULL;
REC_FILE *pfile = NULL;
u32 index = 0, len = 0;
norfs_init(0, 4096, 8); // 扇区 0~4095,每扇区 256B
norfs_mount(&pfs, flash_dev); // 挂载设备
norfs_createfile(pfs, &pfile, &index); // 创建文件,分配索引
norfs_write(pfile, audio_data, 1024); // 顺序写入
norfs_filelen(pfile, &len); // 查询长度
norfs_closefile(&pfile); // 关闭
Source: nor_fs.h(API 原型依据)
注:上例为基于头文件原型的示意调用序列;实际封装见
nor_fs_resource.c对vfs_operations的实现。
示例 3:VFS 全量初始化与挂载失败兜底
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_init 在系统启动早期调用,遍历注册链完成所有后端的初始化;每个后端在 init 中打印 fs_type,便于日志定位。
Configuration Options
| 配置项 | 类型 | 默认值/取值 | 说明 |
|---|---|---|---|
NORFS_MAGIC_NUM | 宏 | 0x11223344 | nor_fs 卷头魔数,挂载时校验介质合法性 |
REC_FILE_END | 宏 | 0xFE | 文件结束标记字节 |
FLASH_PAGE_SIZE / NORF_SECTOR_SIZE | 宏 | 256 | nor_fs 扇区/页大小,对齐 NOR 编程页 |
norfs_init 的 sector_bit | 参数 | 如 8 | 扇区大小以 bit 表达(8 → 256B),决定地址换算 |
FS_IOCTL_SET_LFN_BUF | ioctl | 512B | FAT 长文件名缓冲(FS_IOCTL_SET_LDN_BUF 同规格) |
FS_IOCTL_SET_NAME_FILTER | ioctl | 后端相关 | 设置文件过滤规则,扫描时生效 |
FS_IOCTL_SET_EXT_TYPE | ioctl | 后端相关 | 设置后缀类型过滤 |
FS_IOCTL_SET_VOL | ioctl | 字符串 | 设置 FAT 卷标 |
FS_IOCTL_GET_FREE_SPACE | ioctl | u32 | 查询剩余空间(FAT 后端) |
FS_IOCTL_GET_PARTITION_INFO | ioctl | 结构体 | 获取簇大小、容量等分区信息 |
FS_IOCTL_GET_ENCFOLDER_INFO | ioctl | 结构体 | 获取录音文件夹信息(SYDF 场景) |
FS_IOCTL_GET_ERR_CODE | ioctl | — | 暂不支持(枚举注释明确标注) |
配置方式说明:宏在编译期生效(nor_fs.h);运行时行为通过 vfs_operations.ioctl 的 FS_IOCTL_* 命令按需设置,属于典型的"编译期定结构、运行期定策略"嵌入式设计。
API Reference
VFS 层(vfs.h)
vfs_mount(void **ppvfs, void *device, void *type): u32
- 参数:
ppvfs挂载句柄指针(可传入 NULL 让 VFS 分配);device底层设备句柄;type后端类型字符串(NULL 表示自动探测并跳过"norfs")。 - 返回:
0成功;E_NO_VFS句柄池耗尽;E_NO_FS无后端可挂载。 - 行为:遍历
vfs_operations注册链,匹配fs_type后调用ops->mount;失败自动close_fs清理并释放句柄。
vfs_openbypath(void *pvfs, void **ppvfile, const char *path): u32
- 参数:已挂载的
pvfs;文件句柄指针(NULL 则分配);path文件路径。 - 返回:
0成功;E_NO_VFS句柄分配失败;后端错误码透传。 - 行为:先分配
imount文件句柄,再经pvfs->ops->openbypath分发(vfs.c)。
vfs_type_name(void *p_vfs): void *
- 返回当前挂载点
ops->fs_type字符串(NULL 入参返回 NULL),用于判断已挂载文件系统类型(vfs.c)。
nor_fs 层(nor_fs.h)
norfs_mount(RECFILESYSTEM **ppfs, void *p_device): u32
- 参数:
ppfs文件系统句柄输出;p_deviceNOR Flash 设备句柄。 - 返回:
0成功;非 0 失败(介质魔数不符等)。
norfs_createfile(RECFILESYSTEM *pfs, REC_FILE **ppfile, u32 *pindex): u32
- 参数:
pfs已挂载句柄;ppfile文件句柄输出;pindex分配到的文件索引号输出。 - 返回:
0成功;E_*空间不足/索引耗尽。
norfs_read(REC_FILE *pfile, u8 *buff, u16 btr): u16
- 参数:
pfile文件句柄;buff目标缓冲;btr请求字节数。 - 返回:实际读取字节数(可能小于
btr,文件末尾截断)。 - 说明:
rw_p游标前移;读到REC_FILE_END (0xFE)标记视为文件结束。
norfs_write(REC_FILE *pfile, u8 *buff, u16 btw): u16
- 参数:
pfile文件句柄;buff源数据;btw请求写入字节数。 - 返回:实际写入字节数;更新
w_len/rw_p。
norfs_seek(REC_FILE *pfile, u32 offsize, u32 type): u32
- 参数:
type取SEEK_SET(0) /SEEK_CUR(1) /SEEK_END(2)(vfs.h)。
norfs_closefile(REC_FILE **ppfile): u32 / norfs_filelen(REC_FILE *pfile, u32 *plen): u32 / norfs_save_sr(REC_FILE *pfile, u16 sr) / norfs_name(REC_FILE *pfile, char *name, u32 len): u32 / norfs_ioctl(REC_FILE *pfile, int cmd, int arg): int
- 分别完成:关闭并释放句柄、查询文件长度、持久化起始扇区、拷贝 16B 文件名、扩展控制。
属性与常量
- 文件属性位:
F_ATTR_RO(0x01)只读、F_ATTR_ARC(0x02)归档、F_ATTR_DIR(0x04)目录、F_ATTR_VOL(0x08)卷标(vfs.h); struct vfs_attr:attr(属性位)、fsize(文件大小)、sclust(起始簇/地址),由ops->fget_attr填充(vfs.h)。
Failure Modes、边界情况与并发
挂载失败路径
E_NO_VFS:VFS 句柄池耗尽(vfs_hdl_malloc失败)。句柄池大小在vfs_resource.c中静态定义,多路同时挂载(音乐 + 录音 + 升级)时可能撞池,属可恢复错误,业务应重试或先卸载。E_NO_FS:所有后端mount均失败,常见于:介质未就绪(卡未插好)、nor_fs 魔数不符(Flash 被擦除或分区信息损坏)、设备句柄非法。type == NULL时主动跳过"norfs":防止无类型探测误挂片内 Flash,但也意味着片内卷必须显式传类型,这是常见误用点。
nor_fs 边界与一致性
- 扇区耗尽:
last_sector写满后norfs_createfile返回失败——nor_fs 无碎片回收,录音满后需格式化(vfs_operations.format)才能复用空间。 - 写中断/掉电:
norfs_save_sr只保存起始扇区,w_len不落盘;掉电恢复后长度可能偏大,上层需以REC_FILE_END标记截断。这是"顺序追加 + 标记结束"模型的固有取舍。 - 256B 页对齐:跨扇区写由
sector_bits换算推进;非页对齐的读写长度在驱动层补齐,业务层应尽量按页对齐以获得确定性写时间。
并发与共享
- VFS 句柄(
imount/imount-file)由专用句柄池管理,同一挂载点的读写不保证线程安全;SDK 中 FAT/nor_fs 通常在单任务(如播放/录音任务)内串行访问。 enc_in_norfs.c/enc_in_fatfs.c通过#pragma bss_seg/.data_seg/.const_seg/.code_seg把录音栈数据隔离到独立内存段,避免与播放 DMA 缓冲冲突——这是 SoC 内存分区约束下的显式并发防护。- FAT 后端的
fscan_interrupt支持扫描中断回调,长扫描期间释放 CPU,播放任务可穿插执行,避免卡顿。
Performance 与运维提示
- nor_fs 写入路径短:无目录树、无日志,一次写仅更新
rw_p/w_len,适合录音高频小块写入;代价是索引线性、文件数上限受u16 total_file约束。 - FAT 扫描开销:TF 卡曲目枚举走
fscan_interrupt,大容量卡扫描耗时可观;FS_IOCTL_SET_NAME_FILTER/FS_IOCTL_SET_EXT_TYPE可在扫描期过滤,显著减少回调解包。 - 缓存释放:
FS_IOCTL_FREE_CACHE可主动释放 FAT 缓存,在内存紧张(如录音与播放并发)时调用。 - 日志定位:
vfs_init打印各后端fs_type;vfs_type_name可运行时确认挂载类型,排障时先核对挂载类型是否与设备匹配。
Extension Points
- 新增文件系统后端:实现
struct vfs_operations(fs_type、mount、openby*、read/write/seek等)并注册到list_for_each_vfs_operation链(参见vfs_fat.c与nor_fs_resource.c的注册方式),上层无需改动即可通过fs_mount(type)使用。 - 自定义 ioctl:在
FS_IOCTL_*枚举后追加命令号,由各后端ioctl自行实现;注意FS_IOCTL_GET_ERR_CODE已预留但"暂不支持",可作为扩展位。 - 格式化策略:
vfs_operations.format(p_fs_hdl, device, clust_size, create_new)暴露了按簇格式化能力,录音满盘恢复与量产初始化均可复用。