杰理 SDK 文档中心
首页
首页
  • 项目概览与快速开始

    • 项目概述与芯片支持
    • 环境搭建与工具链
    • 工程与构建系统
    • 烧录与升级工具
    • 文档与硬件资料
  • 系统架构与芯片平台

    • 芯片平台与启动流程
    • 预编译库与头文件体系
    • 消息、定时器与中断服务
    • 通用外设驱动
  • 存储与文件系统

    • 文件系统实现
    • 存储设备驱动
    • VM 参数存储系统
  • 音频处理

    • 音频解码器
    • 音频编码器
    • MIDI 合成与播放
    • 音效、变速变调与降噪
  • 语音玩具应用

    • 应用框架与状态机
    • 音乐播放与外部音源
    • MIDI 乐器模式
    • 录音应用
    • 待机、电源管理与 USB 从机
  • 小音箱应用

    • 应用框架与模式管理
    • 播放源:音乐、FM、录音与 LineIn
  • 应用层与示例工程

    • 通用 MCU 应用
  • 固件更新与补丁

    • 固件升级机制
    • AD14N 主动降噪补丁

文件系统实现

本文档介绍杰理 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 系列)中,文件系统需要同时满足以下约束:

  1. 多文件系统共存:同一套 SDK 可能同时使用 FAT(U 盘/SD 卡)、nor_fs(片内 NOR Flash 的简易文件系统)、simple_fat、sydf(杰理自研日志文件系统)。
  2. 统一上层接口:应用层(音频播放、录音、资源读取)不应关心底层是哪种文件系统,需要一套与平台无关的抽象 API。
  3. 极小的代码与内存开销:不能引入重量级抽象(如 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

关键行为与设计意图:

  1. 链接器段遍历:list_for_each_vfs_operation(ops) 通过链接器段收集所有编译进固件的文件系统操作表。这意味着只要链接进工程,文件系统就会自动被 VFS 发现,无需手工维护注册数组——这是嵌入式 SDK 常见的"零注册表"扩展方式。
  2. 挂载句柄惰性分配:若调用方传入的 *ppvfs 为空,vfs_mount 先调用 vfs_hdl_malloc() 分配句柄;分配失败返回 E_NO_VFS。句柄被复用而非每次新建,减少内存碎片。
  3. 类型匹配策略:type 非空时按 strcmp(ops->fs_type, type) 精确匹配;type 为空时显式跳过 norfs,其含义是"未指定类型时优先挂载可移动介质文件系统(如 FAT)",避免默认挂载到片内 NOR Flash。这是一个值得注意的业务决策:nor_fs 通常作为系统保留文件系统,只有显式指定 "norfs" 才会被挂载。
  4. 失败回滚: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_initvoid vfs_init(void)遍历链接器段,调用所有注册文件系统的 init
vfs_type_namevoid *vfs_type_name(void *p_vfs)返回当前文件系统的 fs_type 字符串
vfs_mountu32 vfs_mount(void **ppvfs, void *device, void *type)按类型挂载文件系统;成功返回 0,失败返回 E_NO_VFS/E_NO_FS
vfs_openbypathu32 vfs_openbypath(void *pvfs, void **ppvfile, const char *path)按路径打开文件
vfs_openbyindexu32 vfs_openbyindex(void *pvfs, void **ppvfile, u32 index)按扫描索引打开文件
vfs_openbyfileu32 vfs_openbyfile(void *pcvfile, void **ppvfile, void *ext_name)由已打开文件打开关联文件(如歌词)
vfs_openbyclustu32 vfs_openbyclust(void *pvfs, void **ppvfile, u32 clust)按簇号打开文件
vfs_createfileu32 vfs_createfile(void *pvfs, void **ppvfile, u32 *pindex)创建文件,可选回传文件索引

所有打开类 API 的通用行为:句柄为空时先 vfs_hdl_malloc() 分配;把文件系统句柄的 ops 拷贝到文件句柄;分发到对应 ops 回调;失败时统一 vfs_file_close() 回滚。

通用封装 API(vfs_fat.c)

API签名说明
vfs_get_fsizeint vfs_get_fsize(void *pvfile, void *parm)获取文件大小(写入 parm),返回 0 表示成功
vfs_ftellint vfs_ftell(void *pvfile, void *parm)获取当前读写位置
vfs_file_deleteu32 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:

  • vfs_fat.c
  • vfs_fat.c

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 为空或删除失败

边界情况

  1. 句柄惰性分配:所有 vfs_open*/vfs_mount 在调用方传入 NULL 句柄时会自动分配,但调用方必须负责最终释放;打开失败路径统一走 vfs_file_close 回滚,避免泄漏。
  2. 操作表缺失回调:VFS 层对每个回调都做 NULL 检查,未实现的能力返回 E_VFS_OPS 而非崩溃。新增文件系统时可以只实现必要回调。
  3. type == NULL 的默认挂载策略:未指定类型时跳过 "norfs",保证默认挂载不会占用片内 Flash 文件系统。若业务需要挂载 nor_fs,必须显式传入 "norfs" 类型。
  4. 空指针安全:vfs_get_fsize/vfs_ftell/vfs_file_delete 均先校验 p_vfile 与 ops,返回 0 或错误码而非解引用空指针。
  5. 删除与关闭分离: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 只需三步:

  1. 实现 struct vfs_operations 实例:参考 fat_resource.c、nor_fs_resource.c、smpl_fat_resource.c、sydf_resource.c(位于 sdk/app/bsp/common/fs/ 下),填充 fs_type 与所需回调。
  2. 注册到链接器段:使用 list_for_each_vfs_operation 对应的段注册宏(与遍历宏配套,通常通过 __attribute__((section)) 放入指定段),使 vfs_init/vfs_mount 能发现该操作表。
  3. 实现挂载与文件私有结构: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 适配器

Related Links

  • VFS 头文件定义(vfs.h)
  • VFS 核心实现(vfs.c)
  • 通用文件封装(vfs_fat.c)
  • FAT 适配器(fat_resource.c)
  • nor_fs 适配器(nor_fs_resource.c)
  • sydf 文件系统接口(sydfile.h)
  • simple_fat 接口(simple_fat.h)
  • 录音与 nor_fs 的集成示例:enc_in_norfs.c
Next
存储设备驱动