设备驱动与文件系统库
本文档介绍 AW31N BLE SDK 中的设备驱动层与文件系统库(VFS 虚拟文件系统、FAT 文件系统适配、设备驱动注册机制及 USB 设备驱动),说明其架构、核心接口、注册机制、数据流与扩展方式。
Purpose and Scope
本页覆盖 SDK 中与"设备驱动"和"文件系统"相关的完整子系统:
- VFS 虚拟文件系统层(
vfs.h/vfs.c):统一文件操作 API、vfs_operations操作表注册机制、imount挂载句柄模型。 - FAT 文件系统适配(
vfs_fat.c/fs_lib.a):VFS 之上最常用的 FAT 实现,以及HAS_VFS_EN关闭时的直连 API 宏。 - 设备驱动层(
device_app.c/device_list.c/ioctl.h):设备注册、设备列表与 ioctl 控制接口。 - USB 设备驱动(
usb_device.c及各 class 驱动:cdc.c、hid_*、msd.c、uac1.c等):作为设备驱动的典型实例。
以下内容不在本页范围,请参考各自页面:蓝牙协议栈与 BLE 应用、电源/低功耗管理、补丁与兼容性说明(patch_release/ 目录)、引导与启动流程。
概述
AW31N 是杰理科技(Jieli)的 BLE SoC,其 SDK 采用"设备驱动 → 文件系统 → 应用"的三层结构。文件系统库建立在设备驱动之上:驱动层通过统一的设备句柄和 ioctl 接口访问 Flash、USB 等物理介质;文件系统层(FAT 为主)把介质上的块数据组织成文件和目录;而最上层的 VFS 则向应用屏蔽不同文件系统(FAT、NOR Flash 文件系统等)的差异,提供一套统一的 fs_*/vfs_* API。
设计上有三个关键决策值得注意:
- 以操作表(operations table)为中心的面向对象风格:
struct vfs_operations相当于 C 语言中的"虚函数表",每种文件系统把自己的init/mount/openbypath/read/write/...填入该结构,通过链接器段(SEC(".vfs_operations"))自动注册,VFS 层遍历注册表即可发现所有可用的文件系统——新增一种文件系统不需要修改 VFS 核心代码。 - 句柄模型统一:文件系统与文件都用
struct imount表示(union { void *pfs; void *pfile; }),分配/释放由 VFS 层统一管理(vfs_hdl_malloc/vfs_fhdl_free),降低上层使用复杂度。 - 可裁剪的双模式编译:
HAS_VFS_EN开启时走完整 VFS 分发路径(灵活、可多文件系统共存);关闭时fs_*宏直接映射到fat_*_api函数(省代码、省开销,适合固定使用 FAT 的场景)。
架构
下图展示了从应用层到硬件层的完整依赖关系:
flowchart TD
subgraph sg_App["应用层 (Application)"]
APP["应用代码 / 播放器 / 录音 / 资源读取"]
end
subgraph sg_VFS["虚拟文件系统层 (VFS)"]
VFS_API["vfs_init / vfs_mount / vfs_openbypath / vfs_read ..."]
OPS_REG["vfs_operations 注册表<br/>(链接器段 .vfs_operations)"]
HDL["imount 句柄池<br/>vfs_hdl_malloc / vfs_fhdl_free"]
end
subgraph sg_FS["文件系统实现"]
FAT["FAT 文件系统<br/>(vfs_fat.c + fs_lib.a)"]
NORFS["NOR Flash 文件系统 (norfs)"]
end
subgraph sg_DEV["设备驱动层"]
DEV_APP["device_app.c / device_list.c<br/>设备注册与调度"]
IOCTL["ioctl 控制接口 (ioctl.h)"]
end
subgraph sg_USB["USB 设备驱动"]
USB["usb_device.c"]
CDC["cdc.c 串口类"]
HID["hid_keyboard / hid_mouse / hid_media"]
MSD["msd.c / msd_upgrade.c 存储类"]
UAC["uac1.c 音频类"]
end
subgraph sg_HW["硬件层"]
FLASH[("Flash / 存储介质")]
end
APP -->|"fs_* / vfs_* API"| VFS_API
VFS_API -->|"遍历注册表"| OPS_REG
VFS_API --> HDL
OPS_REG --> FAT
OPS_REG --> NORFS
FAT -->|"设备读写"| DEV_APP
NORFS -->|"设备读写"| DEV_APP
DEV_APP -->|"ioctl"| IOCTL
DEV_APP --> FLASH
USB --> DEV_APP
各层职责:
- VFS 层(
vfs.c/vfs.h):不关心具体介质,只负责操作表的分发(dispatch)与句柄生命周期。它维护一个由链接器段生成的vfs_operations数组(vfs_ops_begin[]~vfs_ops_end[]),vfs_mount通过fs_type字符串匹配选择具体文件系统。 - 文件系统实现:FAT 是主用实现,声明在
vfs_fat.h,适配代码在apps/app/bsp/common/fs/vfs_fat.c,核心算法以静态库apps/include_lib/liba/bd47/flash/fs_lib.a提供(预编译,源码不可见)。另一个实现是 NOR Flash 文件系统(norfs),挂载时若type为 NULL 会被 VFS 主动跳过(见vfs.c中strcmp(ops->fs_type, "norfs")的判断),说明 NORFS 需要显式指定类型。 - 设备驱动层(
apps/app/bsp/device/):device_app.c提供设备应用入口,device_list.c维护设备列表(设备注册/枚举),ioctl.h定义控制命令。 - USB 设备驱动(
apps/app/bsp/common/usb/device/):usb_device.c是 USB 协议栈核心,各 class 文件(CDC、HID、MSC、UAC)是具体设备功能的实现,属于"设备驱动"在 USB 外设方向的具体化。
VFS 核心设计
操作表结构 struct vfs_operations
每种文件系统必须实现一张操作表,这是 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);
};
Source: vfs.h
注意 fscan_interrupt 支持可中断的目录扫描(回调 callback 用于让出 CPU/退出扫描),file_crc 提供文件校验能力,这些都是嵌入式文件系统常用的实用扩展。头文件里保留了旧版接口注释,说明该结构经历过多次演进,当前以 void * 泛型指针代替具体结构体指针,解耦了 VFS 层与具体文件系统类型。
挂载句柄 struct imount
文件系统实例和文件句柄共用一种结构,靠 union 复用空间:
struct imount {
struct vfs_operations *ops;
union {
void *pfs;
void *pfile;
};
};
Source: vfs.h
ops 指向该句柄所属文件系统的操作表。挂载成功后 pfs 是文件系统实例;打开文件后 pfile 是文件实例。VFS 层 API 通过 pvfs->ops 分派到具体实现,这就是"一个句柄 + 一张表"的 C 语言多态。
注册机制:链接器段 + 宏
文件系统通过宏把操作表放入专用链接段,VFS 启动时按地址顺序遍历:
#define REGISTER_VFS_OPERATIONS(ops) \
const struct vfs_operations ops SEC(.vfs_operations)
Source: vfs.h
配合 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++)
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
设计意图:链接器把分散在各文件中的 SEC(".vfs_operations") 变量按声明顺序聚合成 vfs_ops_begin ~ vfs_ops_end 的连续数组,VFS 无需维护"注册列表",新增文件系统只需 REGISTER_VFS_OPERATIONS 一个宏,天然支持按需裁剪(不链接就不存在)。vfs.c 自身也通过 #pragma code_seg(".vfs.text") 等指令把代码放入独立段,便于在链接脚本中精确控制内存布局(例如放入可运行于 RAM 的段以支持在线升级)。
挂载流程
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
挂载逻辑要点:
- 句柄先分配后使用,分配失败返回
E_NO_VFS。 - 按注册表顺序逐个尝试;
type非空时用strcmp精确匹配fs_type;type为空时跳过norfs(因为 NOR 文件系统需要显式挂载,避免被默认挂载流程误选)。 - 具体
mount失败时调用其close_fs做清理,最后释放句柄返回E_NO_FS。这种"尝试-回滚"模式保证失败后不泄漏句柄。
文件操作流程:打开、读取、关闭
打开文件的路径在 VFS 层统一处理句柄分配与错误回滚:
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) {
vfs_file_close(ppvfile);
}
return err;
}
Source: vfs.c
设计意图:vfs_openbypath 并不自己实现打开逻辑,而是把 pvfs 上的操作表复制给新文件句柄(p_vfile->ops = p_vfs->ops),再委托 ops->openbypath。这保证了"从哪个文件系统挂载的文件,就用哪个文件系统的操作表",且文件句柄自带操作表后,后续 vfs_read/write/seek/close 只需传文件句柄即可正确分派。失败路径统一走 vfs_file_close 释放句柄,避免资源泄漏。vfs_openbyindex(按索引打开)、vfs_openbyfile(按已打开文件扩展打开,如按歌曲名取歌词)、vfs_openbyclust(按簇号打开)遵循完全相同的模式。
vfs.h 中定义的 SEEK_SET/SEEK_CUR/SEEK_END 与文件属性标志:
#define SEEK_SET 0 /* Seek from beginning of file. */
#define SEEK_CUR 1 /* Seek from current position. */
#define SEEK_END 2 /* Seek from end of file. */
#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
vfs_attr 中的 sclust 暴露了 FAT 的起始簇号(起始扇区地址),这是底层 FAT 语义在 VFS 层的少量泄漏——某些应用(如录音连续写、固件升级)需要直接操作簇信息以获得性能,设计上予以保留。
设备驱动层
设备驱动位于 apps/app/bsp/device/,是文件系统访问物理介质的桥梁:
| 文件 | 职责 |
|---|---|
device_app.c / device_app.h | 设备应用入口:驱动初始化、设备管理逻辑 |
device_list.c | 设备列表:设备注册、枚举、查找(驱动以"列表"形式组织,支持多个同类型设备) |
apps/include_lib/device/ioctl.h | ioctl 命令定义:应用/文件系统通过 ioctl 控制设备行为 |
文件系统(如 vfs_fat.c)通过设备驱动完成底层扇区读写;应用也可以通过 ioctl 直接控制设备(例如查询容量、擦除、设置写保护等)。设备驱动层与 USB 驱动层分离:USB 设备驱动(usb/device/)是"设备"的一种,当 USB 枚举为 MSC(msd.c)时,它可把主机端表现为 U 盘,与本地 Flash 文件系统形成"本地文件系统 + USB 虚拟存储"的并存结构。
USB 设备驱动
apps/app/bsp/common/usb/device/ 下实现了完整的 USB 设备侧协议栈:
| 文件 | 功能 |
|---|---|
usb_device.c | USB 设备核心:枚举、端点管理、标准请求处理 |
usb_device_config.c | 设备配置:描述符参数、类配置 |
descriptor.c / user_setup.c | 描述符组织与厂商自定义 setup 请求 |
cdc.c | CDC 串口类(虚拟串口) |
hid_keyboard.c / hid_mouse.c / hid_media.c | HID 键盘/鼠标/多媒体控制 |
custom_hid.c | 自定义 HID 上报 |
msd.c / msd_upgrade.c | 大容量存储类(U 盘)与 U 盘升级 |
uac1.c | USB 音频类(UAC1,音频设备) |
task_pc.c | PC 端交互任务(枚举完成后与主控通信) |
usb_suspend_resume.c | 挂起/恢复电源管理 |
这些驱动共享 usb_device.c 提供的枚举与传输框架,各自实现 class 特定的端点收发。msd_upgrade.c 表明设备驱动与系统升级流程耦合:SDK 支持通过 USB 枚举为 U 盘后拖入固件完成升级,这是驱动层向业务能力(OTA/量产)延伸的典型例子。
核心流程
sequenceDiagram
participant App as 应用层
participant VFS as VFS 层 (vfs.c)
participant OPS as vfs_operations 注册表
participant FAT as FAT 文件系统 (vfs_fat.c / fs_lib.a)
participant DEV as 设备驱动 (device_app.c)
participant HW as Flash 硬件
App->>VFS: vfs_init()
VFS->>OPS: 遍历 vfs_ops_begin[] ~ vfs_ops_end[]
OPS->>FAT: ops->init() 初始化文件系统
App->>VFS: vfs_mount(&pfs, device, "fat")
VFS->>OPS: strcmp 匹配 fs_type == "fat"
OPS->>FAT: ops->mount(&pfs, device)
FAT->>DEV: 打开/绑定底层设备
DEV->>HW: 读取介质 (扇区级)
FAT-->>VFS: 挂载成功,保存 ops
App->>VFS: vfs_openbypath(pfs, &pfile, "/dir/file.mp3")
VFS->>FAT: ops->openbypath(pfs, &pfile, path)
FAT->>DEV: 读目录项/文件项
DEV->>HW: 读扇区
FAT-->>App: 文件句柄就绪
App->>VFS: vfs_read(pfile, buf, len)
VFS->>FAT: ops->read(pfile, buf, len)
FAT->>DEV: 按簇/扇区读数据
DEV->>HW: 读扇区
HW-->>App: 数据返回
App->>VFS: vfs_file_close(&pfile)
VFS->>FAT: ops->close_file(&pfile)
FAT-->>App: 句柄释放
流程要点:
- 系统启动早期调用
vfs_init(),遍历注册表逐个执行各文件系统的init。 - 应用挂载时,VFS 用
fs_type字符串(如"fat")在注册表中匹配操作表,成功则把ops写入挂载句柄。 - 所有文件操作均通过句柄携带的
ops分发到 FAT 实现;FAT 再通过设备驱动完成实际介质访问。 - 关闭时逐级释放:文件句柄 → 文件系统实例 → 设备资源。
双模式编译:HAS_VFS_EN
vfs.h 提供了两种编译模式,这是 SDK 裁剪灵活性的核心:
#if HAS_VFS_EN
struct imount *vfs_hdl_malloc(void);
...
#define fs_resource_init()
#define fs_init vfs_init
#define fs_mount vfs_mount
#define fs_openbypath vfs_openbypath
...
#else
extern u32 fat_monut_api(void **ppfs, void *p_device);
...
#define fs_init()
#define fs_type_name(...) "fat"
#define fs_mount(n,m,x) fat_monut_api(n,m)
#define fs_openbypath fat_openR_api
#define fs_read fat_read_api
#define fs_write fat_write_api
...
#endif
Source: vfs.h
HAS_VFS_EN开启:应用调用fs_*宏时走完整 VFS 分发路径,支持多文件系统共存、可中断扫描(vfs_fscan_new)、目录操作(fs_mk_dir)等高级能力。HAS_VFS_EN关闭:fs_*宏直接映射到fat_*_api裸函数(fat_read_api、fat_write_api、fat_openR_api等),消除了一层间接调用,代码更小、更快,适合产品固定只使用 FAT 的场景。
这种"宏即适配层"的做法让应用代码无需改动即可在两种模式间切换,是嵌入式 SDK 中常见的编译期抽象手法。
配置选项
| 配置/宏 | 类型 | 默认值 | 说明 |
|---|---|---|---|
HAS_VFS_EN | 编译期宏 | 视工程配置 | 使能 VFS 分发层;关闭时 fs_* 宏直连 fat_*_api |
VFS_FILE_NAME_LEN | 宏常量 | 16 | 短文件名缓冲区长度(g_file_sname) |
FS_IOCTL_SET_LFN_BUF | ioctl 命令 | — | 设置长文件名缓冲区(512 字节) |
FS_IOCTL_SET_LDN_BUF | ioctl 命令 | — | 设置长目录名缓冲区(512 字节) |
FS_IOCTL_SET_NAME_FILTER | ioctl 命令 | — | 设置扫描时的文件名过滤规则 |
FS_IOCTL_SET_EXT_TYPE | ioctl 命令 | — | 设置后缀类型过滤(如只扫 mp3) |
SEEK_SET / SEEK_CUR / SEEK_END | 常量 | 0 / 1 / 2 | vfs_seek 的定位模式 |
FS_IOCTL_* 命令枚举定义于 vfs.h,涵盖了文件计数、目录遍历、过滤、文件夹信息、录音文件夹信息、同步、创建目录等能力。这些 ioctl 由各文件系统的 ops->ioctl 实现,VFS 层只负责透传(vfs_ioctl)。
常用 ioctl 命令一览
| 命令 | 用途 |
|---|---|
FS_IOCTL_GET_FILE_NUM | 获取文件总数 |
FS_IOCTL_FILE_CHECK | 文件完整性检查 |
FS_IOCTL_FREE_CACHE | 释放文件系统缓存 |
FS_IOCTL_GET_FOLDER_INFO_3 | 获取文件夹序号与文件夹内文件数 |
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_FILE_SYNC | 文件同步落盘 |
FS_IOCTL_RESET_VFSCAN | 重置目录扫描状态 |
API 参考
vfs_init(void)
遍历 vfs_operations 注册表(.vfs_operations 段),逐个调用非空 init 完成各文件系统初始化。系统启动早期调用一次。
u32 vfs_mount(void **ppvfs, void *device, void *type)
挂载文件系统到 device 设备上。
参数:
ppvfs:挂载句柄指针;*ppvfs == NULL时内部调用vfs_hdl_malloc分配device:底层设备句柄(由设备驱动层提供)type:文件系统类型字符串(如"fat");NULL 表示自动选择并跳过norfs
返回: 0 成功;E_NO_VFS(句柄分配失败)、E_NO_FS(无匹配/挂载失败)
u32 vfs_openbypath(void *pvfs, void **ppvfile, const char *path)
按路径打开文件,文件句柄自动继承挂载句柄的操作表。
参数:
pvfs:挂载句柄ppvfile:文件句柄指针(可为 NULL,内部分配)path:文件路径
返回: 0 成功;E_NO_VFS、E_VFS_HDL(挂载句柄无效)、E_VFS_OPS(操作表缺失)、底层 openbypath 错误码。失败时自动关闭并释放文件句柄。
u32 vfs_openbyindex(void *pvfs, void **ppvfile, u32 index, void *param)
按索引打开文件(配合目录扫描结果使用)。
u32 vfs_openbyfile(void *pcvfile, void **ppvfile, void *ext_name)
基于已打开文件再打开关联文件(如由音频文件打开同名歌词)。
u32 vfs_openbyclust(void *pvfs, void **ppvfile, u32 clust, void *param)
按 FAT 起始簇号打开文件,用于底层快速访问。
u32 vfs_read(void *pvfile, void *buf, u32 len) / u32 vfs_write(void *pvfile, void *buf, u32 len)
按当前文件位置读/写 len 字节,返回实际读/写字节数。
u32 vfs_seek(void *pvfile, u32 offset, u32 mode)
按 SEEK_SET / SEEK_CUR / SEEK_END 模式移动文件读写位置。
u32 vfs_file_close(void **ppvfile) / u32 vfs_fs_close(void **ppvfs)
关闭文件/卸载文件系统并释放句柄(内部调用 vfs_fhdl_free)。
int vfs_ioctl(void *pvfile, int cmd, int arg)
透传到 ops->ioctl,执行上表所列的目录/过滤/同步等扩展操作。
文件系统别名宏(应用层常用)
fs_init、fs_mount、fs_openbypath、fs_read、fs_write、fs_seek、fs_file_close、fs_file_name、fs_get_attrs、fs_get_fsize、fs_ftell、fs_file_delete、fs_fscan_new、fs_fscan、fs_fscan_release、fs_select、fs_ioctl、fs_mk_dir、fs_seek —— 应用代码统一使用这些 fs_* 宏,具体指向 vfs_* 或 fat_*_api 由 HAS_VFS_EN 决定。
使用示例
示例 1:注册一个新的文件系统操作表
任何文件系统实现只需定义操作表并调用注册宏,VFS 启动时自动发现:
const struct vfs_operations myfs_ops = {
.fs_type = "myfs",
.init = myfs_init,
.mount = myfs_mount_api,
.openbypath = myfs_openbypath_api,
.read = myfs_read_api,
.write = myfs_write_api,
.seek = myfs_seek_api,
.close_fs = myfs_fs_close_api,
.close_file = myfs_file_close_api,
/* ... */
};
REGISTER_VFS_OPERATIONS(myfs_ops)
示例 2:应用层挂载与文件读取
void *pfs = NULL;
void *pfile = NULL;
u8 buf[512];
/* 挂载 FAT 文件系统到设备 */
if (0 != vfs_mount(&pfs, device, "fat")) {
/* 处理挂载失败 (E_NO_VFS / E_NO_FS) */
}
/* 打开文件 */
if (0 == vfs_openbypath(pfs, &pfile, "/music/test.mp3")) {
u32 rd = vfs_read(pfile, buf, sizeof(buf));
/* 处理数据... */
vfs_file_close(&pfile);
}
上述
vfs_mount/vfs_openbypath/vfs_read/vfs_file_close的声明见 Source: vfs.h;挂载的"尝试-回滚"实现见 Source: vfs.c
示例 3:非 VFS 模式的直连 FAT API
当 HAS_VFS_EN 关闭时,宏直接指向 FAT 裸函数,同样的应用代码零改动:
/* HAS_VFS_EN == 0 时的等价映射 */
#define fs_mount(n,m,x) fat_monut_api(n,m)
#define fs_openbypath fat_openR_api
#define fs_read fat_read_api
#define fs_write fat_write_api
#define fs_file_close fat_file_close_api
#define fs_seek fat_seek_api
Source: vfs.h
失败模式、边界情况与并发
- 挂载失败:
vfs_mount在注册表匹配失败或具体mount失败时返回E_NO_FS,并在返回前调用close_fs清理、释放句柄(vfs_fhdl_free),避免半初始化状态泄漏。 - 句柄耗尽:
vfs_hdl_malloc返回 NULL 时各vfs_open*返回E_NO_VFS。嵌入式环境下句柄池有限,长时间运行的应用必须保证每次open都配对close。 - 无效句柄:
vfs_openbypath对pvfs == NULL返回E_VFS_HDL;操作表缺函数指针返回E_VFS_OPS——VFS 层做了防御性检查,但具体文件系统内部错误仍需各自处理。 - 打开失败自动回收:
vfs_openbypath/vfs_openbyindex/vfs_openbyfile失败时统一走vfs_file_close(ppvfile)释放句柄,调用方无需重复释放。 - 只读介质/属性:文件属性标志
F_ATTR_RO(只读)、F_ATTR_ARC(归档)、F_ATTR_DIR(目录)由fget_attr返回,写操作前应检查只读位,防止对只读文件或介质写入失败。 - 并发与中断:
fscan_interrupt支持带回调的可中断目录扫描——回调返回非 0 可中止扫描,避免长目录扫描阻塞音频播放等实时任务;这暗示文件系统访问主要在单任务上下文中使用,多任务访问同一文件需应用层自行加锁(VFS 层未内置互斥)。 - 长文件名/长目录名:默认短文件名缓冲区仅 16 字节(
VFS_FILE_NAME_LEN),需要显示/匹配长文件名时必须先通过FS_IOCTL_SET_LFN_BUF/FS_IOCTL_SET_LDN_BUF提供 512 字节缓冲区,否则获取的显示信息可能被截断。 - 缓存一致性:
FS_IOCTL_FREE_CACHE与FS_IOCTL_FILE_SYNC用于在掉电/拔卡/升级前强制刷盘;未同步就断电可能导致 FAT 表不一致。
性能与运维注意事项
- 簇级访问:
vfs_attr.sclust与vfs_openbyclust提供了跳过目录解析、按簇直读的能力,适合录音、固件升级等大批量顺序写场景;普通文件读建议走vfs_read让文件系统维护簇链缓存。 - 编译裁剪:量产固件若固定使用 FAT,可关闭
HAS_VFS_EN消除 VFS 分发层开销;需要多文件系统(FAT + NORFS)或目录扫描等能力时再开启。 - 链接段布局:VFS 代码被放入
.vfs.text等独立段(见vfs.c顶部#pragma code_seg),链接脚本可将其放置在快速 RAM 或特定 Flash 区域,升级时也可按段搬移。 - FAT 核心为预编译库:
apps/include_lib/liba/bd47/flash/fs_lib.a提供 FAT 算法实现,vfs_fat.c是 VFS 与库之间的适配层;修改 FAT 内部行为需通过其公开接口/ioctl 完成,无法直接修改库源码。
扩展点
- 新增文件系统:实现
struct vfs_operations并REGISTER_VFS_OPERATIONS,VFS 自动发现;注意fs_type字符串是挂载匹配的关键字(如"fat"、"norfs")。 - 新增设备驱动:在
device_app.c/device_list.c中注册设备并实现 ioctl 命令(命令定义见apps/include_lib/device/ioctl.h),文件系统通过设备句柄访问。 - 新增 USB class:参考
usb/device/下现有 class(CDC/HID/MSC/UAC),基于usb_device.c的枚举框架实现端点处理,并在usb_device_config.c中配置描述符。 - 文件过滤与扫描:通过
FS_IOCTL_SET_NAME_FILTER、FS_IOCTL_SET_EXT_TYPE定制扫描行为;fscan_interrupt回调实现可中断扫描,可插入播放器/低功耗业务逻辑。 - 升级/量产集成:
msd_upgrade.c展示了设备驱动与 U 盘升级流程的结合点,可作为自定义量产工具链的参考。
相关链接
- VFS 接口定义:vfs.h
- VFS 核心实现:vfs.c
- FAT 适配层:vfs_fat.c / vfs_fat.h
- 资源文件系统:vfs_resource.c
- 设备驱动入口:device_app.c / device_list.c
- ioctl 命令定义:ioctl.h
- USB 设备驱动:usb_device.c
- FAT 预编译库:fs_lib.a
说明:
device_app.c、ioctl.h等设备驱动文件在本页撰写时的源码细节未能全部展开读取,相关描述基于文件清单与 VFS 接口约定;如需精确的设备注册/ioctl 命令清单,请直接查阅上述链接中的源码。