文件系统层(FAT、NOR_FS、SYDF 等)
AD24N SDK 的文件系统层以 VFS(虚拟文件系统)为核心:通过 struct vfs_operations 函数指针表将 FAT、NOR_FS、free_fs 等具体文件系统统一挂载到同一套 vfs_* 接口之下,上层应用无需关心底层介质与格式差异;SYDF 则是独立于 VFS 之外的 boot/uboot 级文件系统,用于固件启动阶段的参数与文件读取。
Purpose and Scope
本页面系统性地介绍 AD24N SDK 中与"文件系统"相关的完整实现,涵盖:
- VFS 虚拟文件系统层:
sdk/app/bsp/common/fs/vfs.c与sdk/include_lib/fs/vfs.h定义的分发表、挂载/打开/读写分发机制、句柄分配与错误码约定。 - FAT 文件系统:
sdk/include_lib/fs/fat/下的 Tiny-FAT 移植(tff.h)、句柄资源管理(fat_resource.h)以及sdk/app/bsp/common/fs/vfs_fat.c提供的封装辅助函数。 - NOR_FS 简易文件系统:
sdk/include_lib/fs/nor_fs/nor_fs.h定义的面向 NOR Flash 录音/文本存储的扇区式文件系统。 - free_fs:
sdk/app/bsp/common/fs/free_fs/下的简易文件系统。 - SYDF:
sdk/include_lib/common/boot.h中的USE_SYDFILE_NEW开关及其在my_malloc.h中对应的内存池(MM_SYDFS / MM_SYDFF),属于 boot/uboot 级文件系统。
以下内容不在本页范围,留给对应的 catalog 页面:底层 Flash/SD 设备驱动、USB 协议栈、以及依赖文件系统的具体业务模块(如 sdk/app/src/voice_func/toy_record/enc_in_norfs.c 的录音编码流程)。
Overview
AD24N 是杰理(Jieli)面向音频/玩具/物联网市场的 MCU SDK。音频播放、录音、固件升级等业务都依赖文件系统读写,但不同介质的文件格式差异很大:
- FAT(FAT12/16/32):用于 SD 卡、U 盘等可移动介质,便于与 PC 交换文件;
- NOR_FS:一种极简的扇区式文件系统,专门为 NOR Flash 上的录音文件设计,占用资源极小;
- SYDF:boot 阶段使用的文件系统,用于存放固件(uboot/应用)与 VM 参数;
- free_fs:另一个轻量级实现,挂在 VFS 之下。
为了让上层业务用同一套 API 操作所有这些介质,SDK 采用面向对象的分发表模式(dispatch table):每个文件系统实现一个 struct vfs_operations 结构体,通过 REGISTER_VFS_OPERATIONS(ops) 宏放入链接脚本指定的 .vfs_operations 段;vfs.c 在运行时遍历该段,按 fs_type 字符串匹配并调用对应实现。这就是整个文件系统层的核心设计意图:把"介质差异"封装在 VFS 之下,上层只面对统一的句柄与函数签名。
关键概念:
| 概念 | 说明 |
|---|---|
struct vfs_operations | 文件系统实现必须填写的函数指针表(约 20 个操作) |
struct imount | VFS 句柄,ops 指向实现表,pfs/pfile 指向具体文件系统对象 |
REGISTER_VFS_OPERATIONS | 把实现表放入 .vfs_operations 链接段的宏 |
vfs_ops_begin[] / vfs_ops_end[] | 链接器生成的段边界符号,用于遍历所有注册的实现 |
fs_type | 实现表的字符串标识,如 "norfs"、"freefs",mount 时按此匹配 |
FS_IOCTL_* | 通过 vfs_ioctl 透传给具体实现的扩展命令集 |
Architecture
flowchart TD
subgraph sg_App["应用层"]
App["业务模块(播放/录音/升级)"]
end
subgraph sg_VFS["VFS 层 vfs.c / vfs.h"]
VFS["vfs_init / vfs_mount / vfs_openbypath<br/>vfs_read / vfs_write / vfs_seek"]
Ops["struct vfs_operations 分发表"]
HDL["struct imount 句柄<br/>vfs_hdl_malloc / vfs_fhdl_free"]
IOCTL["vfs_ioctl + FS_IOCTL_* 命令集"]
end
subgraph sg_Reg["已注册文件系统 (.vfs_operations 段)"]
FAT["FAT<br/>tff.h / fat_resource / vfs_fat.c"]
NORFS["NOR_FS<br/>nor_fs.h"]
FREEFS["free_fs<br/>free_fs.c"]
end
subgraph sg_Dev["底层介质"]
SD["SD / U 盘(块设备)"]
NOR["NOR Flash(扇区设备)"]
end
App -->|"vfs_* 统一接口"| VFS
VFS --> Ops
VFS --> HDL
VFS --> IOCTL
Ops -->|"ops->mount / openbypath / read..."| FAT
Ops --> NORFS
Ops --> FREEFS
FAT --> SD
NORFS --> NOR
架构说明:
- VFS 层是唯一的对外入口。
vfs_mount分配struct imount句柄并遍历.vfs_operations段找到匹配的实现;vfs_openbypath/vfs_read/vfs_write等函数则从句柄取出ops指针,转发给具体实现(见 vfs.c 的vfs_init与 vfs.c 的vfs_mount)。 - 分发表(
struct vfs_operations)是整个层的"接口契约",定义在 vfs.h。每个文件系统只需实现自己关心的字段,未实现的置 NULL,VFS 层会返回E_VFS_OPS。 - 挂载策略:当调用方不指定
type时,vfs_mount会跳过"norfs"与"freefs",即默认优先尝试 FAT(见 vfs.c)——这是因为 SD/U 盘场景最常用,且 FAT 挂载失败后才会回退到简易系统。 - SYDF 不经过 VFS:它属于 boot/uboot 级实现,通过
boot.h的USE_SYDFILE_NEW宏控制数据结构布局,并通过my_malloc.h的MM_SYDFS/MM_SYDFF内存池与其他文件系统隔离(见 boot.h 与 my_malloc.h)。
VFS 层实现详解
分发表:struct vfs_operations
struct vfs_operations 是文件系统层的核心契约,定义在 vfs.h:
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);
u32(*openbyfile)(void *pcvfile, void **ppfile, void *ext_name);
u32(*openbyclust)(void *pfs, void **ppfile, u32 clust);
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)(struct vfscan *, void *, const char *path, u8 max_deepth, int (*callback)(void));
void (*fscan_release)(struct vfscan *);
int (*fsel)(struct vfscan *, void *, int sel_mode, void **, int);
int (*file_crc)(void *pfile);
};
设计意图:这是一个典型的策略模式。fs_type 是运行时匹配的"名字",其余都是可选的函数指针——某文件系统若不支持某个操作(如 NOR_FS 不支持目录),对应指针置 NULL,VFS 层返回 E_VFS_OPS 即可。文件/目录的通用属性通过 struct vfs_attr(attr/fsize/sclust)传递,文件属性位定义在 vfs.h:F_ATTR_RO(只读)、F_ATTR_ARC(归档)、F_ATTR_DIR(目录)、F_ATTR_VOL(卷标)。
注册机制:REGISTER_VFS_OPERATIONS
#define REGISTER_VFS_OPERATIONS(ops) \
const struct vfs_operations ops SEC(.vfs_operations)
该宏把实现表放入名为 .vfs_operations 的链接段(见 vfs.h)。链接脚本会生成段边界符号 vfs_ops_begin / vfs_ops_end,vfs.c 用下面的宏遍历所有已注册实现:
extern struct vfs_operations vfs_ops_begin[];
extern struct vfs_operations vfs_ops_end[];
#define list_for_each_vfs_operation(ops) \
for (ops=vfs_ops_begin; ops<vfs_ops_end; ops++)
(见 vfs.c)这种"编译期登记 + 链接期收集"的方式在 MCU 上非常常见:不需要动态注册表,也不占额外 RAM,新增一个文件系统只需实现 struct vfs_operations 并在某处调用 REGISTER_VFS_OPERATIONS。
vfs_init() 在系统启动时遍历该段,逐个调用非 NULL 的 init()(见 vfs.c)。
挂载流程:vfs_mount
vfs_mount 是文件系统层最重要的入口,其完整逻辑见 vfs.c:
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 == strcmp(ops->fs_type, "freefs")) {
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;
}
关键点:
- 句柄复用:若
*ppvfs为 NULL 则先vfs_hdl_malloc()分配struct imount,失败返回E_NO_VFS; - 按名字匹配:调用方传入
type时用strcmp(ops->fs_type, type)精确匹配;不传type时显式跳过norfs与freefs,优先尝试 FAT——这保证了"插上 SD 卡自动挂 FAT"的默认行为; - 失败回退:某个
ops->mount返回非 0 时调用其close_fs清理现场,继续尝试下一个实现;全部失败则释放句柄并返回E_NO_FS。
挂载后的 imount 结构(见 vfs.h):
struct imount {
struct vfs_operations *ops;
union {
void *pfs; // 文件系统对象(如 FATFS *)
void *pfile; // 文件对象(如 FIL *)
};
};
这个联合体非常巧妙:同一个 imount 结构既充当"已挂载文件系统句柄"(存 pfs)又充当"已打开文件句柄"(存 pfile),由 ops 指向的实现表决定语义。vfs_openbypath 会先把 p_vfile->ops = p_vfs->ops 复制过来,再调用 ops->openbypath(p_vfs->pfs, &p_vfile->pfile, path)(见 vfs.c),从而让文件句柄自动继承所属文件系统的实现。
挂载时序
sequenceDiagram
participant App as 应用
participant VFS as vfs_mount(vfs.c)
participant Ops as vfs_operations[] (.vfs_operations段)
participant FAT as FAT mount
participant NOR as norfs_mount
App->>VFS: vfs_mount(&pvfs, device, type)
VFS->>VFS: *ppvfs==NULL ? vfs_hdl_malloc() : 复用
VFS->>VFS: 失败则返回 E_NO_VFS
loop list_for_each_vfs_operation
VFS->>Ops: 检查 ops->mount 与 ops->fs_type
alt type 非空
VFS->>VFS: strcmp 不匹配则 continue
else type 为空
VFS->>VFS: 跳过 "norfs" / "freefs"
end
VFS->>FAT: ops->mount(&pfs, device)
alt 挂载成功 (返回 0)
FAT-->>VFS: 0
VFS->>VFS: pvfs->ops = ops
VFS-->>App: 返回 0
else 挂载失败
FAT-->>VFS: 非 0
VFS->>FAT: ops->close_fs(&pfs) 清理
end
end
VFS-->>App: 释放句柄,返回 E_NO_FS
句柄分配与内存池
VFS 句柄的内存来自 my_malloc.h 定义的统一内存池枚举(见 my_malloc.h):
MM_VFS, // VFS 句柄 (imount)
MM_SYDFS, // SYDF 文件系统对象
MM_SYDFF, // SYDF 文件对象
...
MM_NORFF, // NOR_FS 文件对象
MM_FATFS, // FAT 文件系统对象 (FATFS)
MM_FATFF, // FAT 文件对象 (FIL)
每一种句柄类型对应独立的内存池,vfs_hdl_malloc/vfs_fhdl_free(VFS)、fat_fshdl_alloc/fat_fshdl_free(FAT,见 fat_resource.h)分别从对应池中取/还内存。这种"按类型分池"的做法可以避免不同文件系统之间的内存碎片相互影响,也是嵌入式系统里常用的资源隔离手段。
各文件系统实现
FAT 文件系统(Tiny-FAT 移植)
FAT 实现位于 sdk/include_lib/fs/fat/,核心头文件是 tff.h(Tiny-FatFS 的杰理定制版)。文件系统对象 struct _FATFS 从 FAT 起始扇区开始记录布局(见 tff.h):
/* File system object structure */
struct _FATFS {
u32 fatbase; /* FAT start sector */
...
};
typedef struct _FATFS FATFS;
tff.h 同时定义了文件对象(含 FFOBJID obj)、目录对象以及带消息队列的异步文件操作结构(FSMSG fs_msg,见 tff.h 与 tff.h),说明该移植支持通过消息队列做异步读写。
资源管理接口在 fat_resource.h:FATFS *fat_fshdl_alloc(void) 与 FATFS *fat_fshdl_free(FATFS *fshdl)(见 fat_resource.h),配合 MM_FATFS 内存池使用。
vfs_fat.c 为 FAT 提供 VFS 之上的封装辅助函数,例如 vfs_file_delete 演示了"通过句柄取 ops 再转发"的标准模式(见 vfs_fat.c):
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;
}
注意其注释明确说明"SH 系列文件关闭在外面应用,删除接口里面不处理"——删除与关闭解耦,由上层负责 close_file。同文件还提供 vfs_get_fsize(包装 ops->flen)与 vfs_ftell(包装 ops->ftell),以及 __fscan_arg_handler 解析扫描参数:-t 文件类型、-r 包含子目录、-d 扫描文件夹、-a 文件属性、-s 排序方式(按时间 t 或按文件号 n,见 vfs_fat.c)。
NOR_FS 简易文件系统
NOR_FS 是专为 NOR Flash 录音文件设计的极简扇区式文件系统,完整定义在 nor_fs.h:
#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;
设计要点:
- 扇区即页:
NORF_SECTOR_SIZE == FLASH_PAGE_SIZE == 256字节,文件系统按 256 字节页管理 NOR Flash,天然对齐 Flash 的页擦除/编程粒度; - 扁平索引:
RECF_INDEX_INFO只有"索引号 + 扇区号"两个字段,没有目录树,适用于"第 N 段录音"这类顺序访问场景; - 魔数校验:
NORFS_MAGIC_NUM(0x11223344)配合norfs_flash_info_t(magic/first_sector/last_sector/sector_size)在挂载时校验 Flash 中是否已有合法文件系统; - 句柄内嵌介质指针:
hdev指向底层 Flash 设备,文件系统通过它读写,与具体 Flash 驱动解耦。
数据结构关系:
erDiagram
RECFILESYSTEM ||--o{ REC_FILE : "打开多个文件"
RECFILESYSTEM {
RECF_INDEX_INFO index "当前文件索引"
u16 total_file "文件总数"
u16 first_sector "起始扇区"
u16 last_sector "结束扇区"
u8 sector_bits "扇区位数"
void hdev "底层设备"
}
REC_FILE {
RECF_INDEX_INFO index "文件索引"
RECFILESYSTEM pfs "所属文件系统"
u32 len "文件长度"
u32 w_len "已写长度"
u32 rw_p "读写指针"
u16 sr "当前扇区"
char name "文件名 name[16]"
}
RECF_INDEX_INFO {
u32 index "文件索引号"
u16 sector "文件所在扇区"
}
NOR_FS 对外 API(见 nor_fs.h):
| 函数 | 作用 |
|---|---|
norfs_init_api() | 注册/初始化底层 Flash 操作 |
norfs_init(sector_start, sector_end, sector_bit) | 划定文件系统占用的扇区区间 |
norfs_mount(RECFILESYSTEM **ppfs, void *p_device) | 挂载,校验魔数并读取文件总数 |
norfs_createfile(pfs, &pfile, &pindex) | 创建新文件,返回索引号 |
norfs_openbyindex(pfs, &pfile, index) | 按索引打开文件 |
norfs_read / norfs_write | 按扇区读写,btr/btw 为 16 位长度 |
norfs_seek(pfile, offsize, type) | 定位读写指针 |
norfs_closefile / norfs_save_sr | 关闭文件 / 保存当前扇区 |
norfs_ioctl / norfs_name | 控制命令 / 获取文件名 |
NOR_FS 的读写在 VFS 中暴露为 "norfs" 类型的 vfs_operations 实现;vfs_mount 在未指定 type 时会跳过它,但业务层(如录音模块 enc_in_norfs.c)可通过 norfs_* 直接调用,或显式指定 "norfs" 类型挂载。
free_fs 简易文件系统
free_fs 位于 sdk/app/bsp/common/fs/free_fs/,包含 free_fs.c 与 free_fs_resource.c(头文件见 sdk/include_lib/fs/free_fs/free_fs.h)。它同样以 "freefs" 类型注册到 VFS,vfs_mount 默认跳过它。free_fs 面向"无需目录树、文件数有限"的轻量场景(如芯片内部资源区、调试数据),与 NOR_FS 定位类似但实现独立。
SYDF 文件系统(boot 级)
SYDF(Serial/Simple Data File)是独立于 VFS 的 boot/uboot 级文件系统,主要用于启动阶段读取固件与 VM(参数管理)数据。它的存在由 boot.h 的编译开关控制:
#define USE_SYDFILE_NEW 1
struct vm_info {
#if (USE_SYDFILE_NEW == 1)
u8 align; //from uboot, 按 n * 256 对齐
#endif
...
};
当 USE_SYDFILE_NEW == 1 时,vm_info、bt_mac_addr 等启动期结构体采用新布局(按 n*256 对齐,见 boot.h 与 boot.h)。由于 SYDF 运行在 boot 阶段,它不能依赖应用级 VFS,因此拥有独立的内存池 MM_SYDFS(文件系统对象)与 MM_SYDFF(文件对象)(见 my_malloc.h)。
SYDF 与 VFS 体系的分工可以概括为:boot 阶段用 SYDF 完成最小化文件读取(固件、MAC、VM 参数)→ 进入应用后由 VFS 挂载 FAT/NOR_FS/free_fs 提供完整文件服务。
使用示例
示例一:通过 VFS 挂载并打开文件
以下是 VFS 的标准调用序列(挂载 → 打开 → 读写),对应 vfs_mount 与 vfs_openbypath 的实现(见 vfs.c):
struct imount *pvfs = NULL;
struct imount *pvfile = NULL;
// 1. 挂载:type 传 NULL 时默认优先尝试 FAT
u32 err = vfs_mount(&pvfs, device, NULL);
if (0 != err) {
return err; // E_NO_VFS / E_NO_FS
}
// 2. 按路径打开文件(自动继承 pvfs 的 ops)
err = vfs_openbypath(pvfs, &pvfile, "/music/test.mp3");
if (0 != err) {
vfs_fs_close(&pvfs);
return err; // E_VFS_HDL / E_VFS_OPS / 底层错误
}
// 3. 读写/关闭由 ops 分发
// vfs_read(pvfile, buf, len);
// vfs_write(pvfile, buf, len);
vfs_file_close(&pvfile);
vfs_fs_close(&pvfs);
打开失败时 vfs_openbypath 内部会自动调用 vfs_file_close(ppvfile) 释放句柄(见 vfs.c),因此调用方只需在返回非 0 时处理 pvfile 即可。
示例二:NOR_FS 直接调用(录音场景)
NOR_FS 不依赖 VFS 时可直接调用其 API,先 norfs_init 划定扇区区间,再 norfs_mount 挂载(见 nor_fs.h):
NORFS *pfs = NULL;
REC_FILE *pfile = NULL;
u32 file_index = 0;
norfs_init_api();
norfs_init(0, 1023, 0); // 扇区 0~1023,每扇区 256B
norfs_mount(&pfs, nor_device); // 校验 NORFS_MAGIC_NUM
norfs_createfile(pfs, &pfile, &file_index); // 创建新录音文件
norfs_write(pfile, pcm_buf, 512);
norfs_closefile(&pfile); // 写满/停止录音时关闭
录音业务侧的完整用法可参考 enc_in_norfs.c(编码数据直接写入 NOR_FS)。
示例三:注册一个新的文件系统实现
任何文件系统接入 VFS 只需实现 struct vfs_operations 并用宏登记(见 vfs.h):
static u32 myfs_mount(void **ppfs, void *device) { /* ... */ }
static u32 myfs_read(void *pfile, void *buff, u32 len) { /* ... */ }
const struct vfs_operations myfs_ops = {
.fs_type = "myfs",
.mount = myfs_mount,
.openbypath = myfs_openbypath,
.read = myfs_read,
/* 其余置 NULL 即可,VFS 返回 E_VFS_OPS */
};
REGISTER_VFS_OPERATIONS(myfs_ops);
配置选项
| 配置项 | 位置 | 类型/默认值 | 说明 |
|---|---|---|---|
USE_SYDFILE_NEW | boot.h | 宏,默认 1 | 启用新版 SYDF 数据结构布局(vm_info 按 n*256 对齐) |
NORFS_MAGIC_NUM | nor_fs.h | 0x11223344 | NOR_FS 挂载时的魔数校验 |
NORF_SECTOR_SIZE | nor_fs.h | 256(=FLASH_PAGE_SIZE) | NOR_FS 扇区大小 |
VFS_FILE_NAME_LEN | vfs.h | 16 | 全局短文件名缓冲 g_file_sname 长度 |
SEEK_SET/CUR/END | vfs.h | 0/1/2 | seek 模式常量 |
| 内存池枚举 | my_malloc.h | MM_VFS 等 | 各文件系统句柄独立内存池 |
API 参考
VFS 层(vfs.h)
| 函数签名 | 说明 |
|---|---|
void vfs_init(void) | 遍历 .vfs_operations 段调用各实现 init() |
u32 vfs_mount(void **ppvfs, void *device, void *type) | 挂载;type==NULL 时跳过 norfs/freefs;返回 0 或 E_NO_VFS/E_NO_FS |
u32 vfs_openbypath(void *pvfs, void **ppvfile, const char *path) | 按路径打开文件,失败自动关闭句柄 |
u32 vfs_openbyindex(void *pvfs, void **ppvfile, u32 index) | 按索引打开(NOR_FS 类系统常用) |
u32 vfs_createfile(void *pvfs, void **ppvfile, u32 *pindex) | 创建文件并返回索引 |
u32 vfs_read/vfs_write/vfs_seek(...) | 转发到 ops->read/write/seek |
u32 vfs_file_close(void **ppvfile) / vfs_fs_close(void **ppvfs) | 关闭文件/文件系统 |
u32 vfs_file_name(void *pvfile, void *name, u32 len) | 获取文件名(转发 ops->name) |
int vfs_get_attrs(void *pvfile, void *pvfs_attr) | 获取 struct vfs_attr(属性/大小/簇地址) |
int vfs_ioctl(void *pvfile, int cmd, int arg) | 透传 FS_IOCTL_* 命令(见下方) |
int vfs_delete_dir(void *pvfs, char *path) | 删除目录 |
struct imount *vfs_hdl_malloc(void) / vfs_fhdl_free(...) | 句柄分配/释放(MM_VFS 池) |
FS_IOCTL 命令集(vfs.h)
FS_IOCTL_GET_FILE_NUM、FS_IOCTL_FILE_CHECK、FS_IOCTL_FREE_CACHE、FS_IOCTL_SET_NAME_FILTER、FS_IOCTL_GET_FOLDER_INFO、FS_IOCTL_SET_LFN_BUF(512)、FS_IOCTL_SET_LDN_BUF(512)、FS_IOCTL_SET_EXT_TYPE、FS_IOCTL_OPEN_DIR/ENTER_DIR/EXIT_DIR、FS_IOCTL_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、FS_IOCTL_FILE_SYNC、FS_IOCTL_FILE_INDEX/FS_INDEX、FS_IOCTL_RESET_VFSCAN、FS_IOCTL_GET_PARTITION_INFO(簇大小/容量)、FS_IOCTL_GET_FREE_SPACE。
NOR_FS 层(nor_fs.h)
| 函数 | 参数要点 | 返回 |
|---|---|---|
norfs_init(u32 sector_start, u32 sector_end, u32 sector_bit) | 划定 Flash 扇区区间 | void |
norfs_mount(RECFILESYSTEM **ppfs, void *p_device) | 校验魔数、读取文件总数 | u32,0 成功 |
norfs_createfile(RECFILESYSTEM *pfs, REC_FILE **ppfile, u32 *pindex) | 创建文件,输出索引号 | u32 |
norfs_openbyindex(RECFILESYSTEM *pfs, REC_FILE **ppfile, u32 index) | 按索引打开 | u32 |
u16 norfs_read(REC_FILE *pfile, u8 *buff, u16 btr) | 读,返回实际字节数 | u16 |
u16 norfs_write(REC_FILE *pfile, u8 *buff, u16 btw) | 写,返回实际字节数 | u16 |
u32 norfs_seek(REC_FILE *pfile, u32 offsize, u32 type) | 定位读写指针 | u32 |
u32 norfs_closefile(REC_FILE **ppfile) | 关闭文件 | u32 |
void norfs_save_sr(REC_FILE *pfile, u16 sr) | 保存当前扇区号 | void |
故障模式、边界情况与并发
错误码约定
VFS 层使用 errno-base.h 中的错误码,挂载/打开路径上的关键错误:
| 错误码 | 触发场景 |
|---|---|
E_NO_VFS | vfs_hdl_malloc() 分配 imount 句柄失败(MM_VFS 池耗尽) |
E_NO_FS | 遍历完所有 vfs_operations 仍无实现挂载成功(介质不可读/格式不识别) |
E_VFS_HDL | vfs_openbypath 收到 NULL 文件系统句柄 |
E_VFS_OPS | 文件系统未实现对应操作(ops 指针为 NULL) |
E_FS_PFILE | 文件句柄/文件对象为 NULL(如 vfs_file_delete 删除失败) |
边界与回退行为
- 默认挂载顺序:
vfs_mount未指定 type 时跳过norfs/freefs,意味着只插了录音 NOR Flash、没插 SD 卡的设备默认挂不上简易系统——业务方必须显式传"norfs"或直接调用norfs_*API。这是设计上刻意的取舍:FAT 是通用介质格式,优先尝试成本最低。 - 句柄自清理:
vfs_openbypath失败路径调用vfs_file_close(ppvfile)释放句柄(见 vfs.c),避免调用方遗漏释放;但成功路径的句柄必须由调用方显式关闭,否则内存池泄漏。 - 挂载失败现场清理:某个
ops->mount失败后 VFS 会调用其close_fs再尝试下一个实现(见 vfs.c),防止前一个实现残留状态影响后续挂载。 - NOR_FS 容量约束:
norfs_read/norfs_write的长度参数是u16(见 nor_fs.h),单次读写最大 65535 字节;文件总数上限由u16 total_file决定。超过这些上限的读写需要调用方分片进行。
并发与重入
- VFS 层本身不提供锁:
struct vfs_operations是纯函数表,读写并发由底层文件系统(如 FAT 的FSMSG消息队列机制)与业务层互斥保证。从tff.h中文件对象内嵌FSMSG(见 tff.h)可以看出 FAT 实现支持异步消息队列,但调用方仍需遵守"同一文件句柄不跨任务并发读写"的约定。 - 句柄池竞争:
vfs_hdl_malloc/fat_fshdl_alloc等从内存池取句柄,若多个任务同时挂载/打开文件,需由系统内存池分配器保证原子性。
性能与运维注意事项
- 内存按类型分池(MM_VFS / MM_SYDFS / MM_SYDFF / MM_NORFF / MM_FATFS / MM_FATFF,见 my_malloc.h):每种句柄有独立预算,可通过各池的剩余量定位"句柄泄漏"属于哪个文件系统——运维排查时优先检查
E_NO_VFS/挂载失败是否由池耗尽引起。 - 长文件名缓冲:
FS_IOCTL_SET_LFN_BUF/FS_IOCTL_SET_LDN_BUF需要调用方提供 512 字节缓冲(见 vfs.h),处理长文件名前必须先设置,否则FS_IOCTL_GET_DISP_INFO拿不到完整名称。 - NOR_FS 无磨损均衡:录音文件固定落在
first_sector..last_sector区间,高频擦写会加速该区间 Flash 老化;业务层应规划好循环覆盖策略(如按REC_FILE_END标记文件结束)。 - 扫描中断机制:
fscan_interrupt允许在目录扫描期间周期性回调,供上层让出 CPU/处理按键,避免大目录扫描阻塞系统(见 vfs.h)。
扩展点
- 新增文件系统:实现
struct vfs_operations(fs_type+ 必要函数指针),调用REGISTER_VFS_OPERATIONS(ops)登记,即被vfs_init/vfs_mount自动发现——不需要修改vfs.c任何代码。 - 控制面扩展:通过
FS_IOCTL_*命令集(vfs.h)向具体实现透传私有命令,如分区信息、剩余空间、目录导航、文件过滤等,上层用vfs_ioctl统一调用。 - 扫描策略定制:
__fscan_arg_handler的-t/-r/-d/-a/-s参数体系(见 vfs_fat.c)定义了文件扫描/排序的通用语义,新的文件系统可复用这套参数解析。 - boot 级替换:SYDF 的
USE_SYDFILE_NEW开关(boot.h)允许在新旧结构布局之间切换,升级固件时用于保持与 uboot 的数据兼容。
测试情况
- 头文件与实现文件未发现独立单元测试工程;文件系统的验证主要依赖板级集成测试:播放器播放 SD 卡 FAT 文件、录音写入 NOR_FS 后断电重启读取、以及
toy_record(enc_in_norfs.c)的端到端录音-回读链路。 - 内存池枚举(MM_*)在
my_malloc.h中与 VFS/SYDF/FAT 一一对应,可作为运行时内存统计(各池水位)的检测点,用于定位句柄泄漏类问题。
Related Links
- VFS 头文件与实现:vfs.h、vfs.c
- FAT 封装:vfs_fat.c、tff.h、fat_resource.h
- NOR_FS:nor_fs.h、nor_fs_resource.c
- free_fs:free_fs.c、free_fs.h
- SYDF 与内存池:boot.h、my_malloc.h
- 相关业务示例:enc_in_norfs.c(录音写入 NOR_FS)