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

    • SDK 概览与 AC791N 芯片平台
    • 环境搭建与编译指南
    • 烧录与固件升级
    • 工程结构导览
  • 产品方案应用

    • WiFi 摄像头方案
    • WiFi IPC 可视对讲方案
    • WiFi 故事机方案
    • 扫码枪 HID 方案
    • 开发板示例工程
  • 公共应用组件

    • 语音识别 ASR 引擎
    • LLM 与 AI 语音助手接入
    • 摄像头传感器驱动
    • UI 显示框架与驱动
    • USB 主机与设备栈
    • 文件系统与存储管理
    • 系统服务与外设管理
    • 生产测试与射频工具
  • 蓝牙协议栈

    • 经典蓝牙 BR/EDR
    • BLE 低功耗蓝牙
    • 蓝牙 Mesh 网络
    • 蓝牙扩展协议(RCSP/广播/无线麦克风)
  • WiFi 与网络协议栈

    • WiFi 驱动与网络模式
    • lwIP TCP/IP 协议栈
    • 网络安全与加密库
    • 应用层网络协议
    • 流媒体与音视频传输
    • 云平台接入 SDK
    • P2P 远程访问与设备互联
  • 芯片平台与驱动

    • wl82 平台与硬件加速
    • 外设驱动框架
    • 平台配置与固件打包工具
  • 媒体与音频引擎

    • 音频编解码与音源
    • 音效处理引擎
    • 视频与图像处理
  • 操作系统与运行时

    • 实时操作系统与 POSIX 层
    • C/C++ 运行时库
  • 开发资源与文档

    • 文档与规格书
    • 公共示例工程
    • UI 资源工程与打包
    • SDK 辅助工具与脚本

文件系统与存储管理

本页面向 Jieli AC79 AIoT SDK 的存储与文件系统能力,涵盖 VFS(虚拟文件系统)抽象层、分区/挂载模型、NOR Flash 简易文件系统(nor_fs),以及 FS API 在 TLS/HTTP 等网络组件中的接入方式。

Purpose and Scope

本页覆盖「文件系统与存储管理」这一公共组件的完整技术面:

  • VFS 抽象层(include_lib/utils/fs/fs.h):统一的文件流 FILE、挂载点 imount、分区 vfs_partition、文件系统操作函数表 vfs_operations、文件扫描上下文 vfscan,以及定位/属性/选择/循环/ioctl 等常量定义。
  • NOR Flash 文件系统(include_lib/utils/fs/nor_fs.h):面向录音等场景的轻量级文件系统,文件系统句柄 RECFILESYSTEM、文件句柄 REC_FILE 与初始化/扫描/读写/建删文件接口。
  • 存储设备接入:SD/TF 卡(sdfile.h)与 SPI NOR Flash 通过注入底层 read/write/erase 函数指针接入 VFS。
  • 跨组件集成:wolfSSL 通过 fs_open 系列 API 读取证书文件,uC-HTTPc 使用 FS_FILE 句柄。

以下主题属于兄弟页面,不在本页展开:底层 Flash/SD 硬件驱动细节、具体业务(录音服务、音乐播放、OTA 升级)的存储策略、littlefs 移植本身的完整 API。littlefs 在本 SDK 中作为可移植第三方文件系统示例出现于 apps/common/example/third_party/littlefs,本页仅作接入方式上的对照说明。

Overview

AC79 系列 AIoT 芯片的存储介质以 SPI NOR Flash(内置/外置) 与 SD/TF 卡 为主。上层业务(录音、播放、配网、证书加载)需要以统一的方式读写这些差异巨大的介质,因此 SDK 在 include_lib/utils/fs 目录下设计了一套 VFS(Virtual File System) 抽象:

  • 以 C 语言函数指针表 struct vfs_operations 定义文件系统必须实现的操作(mount、fopen、fread、fwrite、fseek、fscan、fsel、ftruncate、ioctl 等),类似面向对象中的多态接口。
  • 以 struct imount 表示一个挂载点,绑定设备信息(vfs_devinfo)与分区信息(vfs_partition),并内置**引用计数(atomic_t ref)与互斥量(OS_MUTEX)**保证多任务并发访问安全。
  • 上层应用拿到的是统一的 FILE 流句柄,通过 fs_open/fs_read/fs_write/fs_close 家族 API(fs.h 后半部分声明,被 wolfSSL 直接映射使用)操作文件,与具体介质和文件系统类型解耦。

对于录音等对功耗、实时性敏感的场景,SDK 另提供一套极简的 NOR 文件系统(nor_fs.h):以扇区为分配单元、16 位文件索引、256 字节页缓冲,通过注入 eraser/read/write 函数指针适配不同 Flash,文件句柄 REC_FILE 直接持有物理地址 addr,读写走顺序流式接口 recf_read/recf_write。

关键概念

概念说明
挂载点(imount)一个已挂载文件系统的运行时实例,含设备/分区信息、操作函数表、引用计数与互斥量
分区(vfs_partition)介质上的逻辑区域,含起始偏移、簇大小、总容量、文件系统类型与挂载路径
文件流(FILE)打开文件的句柄,指向挂载点、设备与分区,私有数据由具体文件系统持有
文件扫描(vfscan)目录/文件遍历上下文,支持扩展名过滤、排序、循环模式与多级子目录
函数指针注入nor_fs 通过 eraser/read/write 指针适配任意 Flash;VFS 通过 vfs_operations 适配任意文件系统

Architecture

flowchart TD
    subgraph sg_App["应用层"]
        APP["录音 / 播放 / 配网 / OTA 业务"]
        NET["wolfSSL / uC-HTTPc 网络栈"]
    end

    subgraph sg_API["FS API 层 (fs.h)"]
        FSAPI["fs_open / fs_read / fs_write / fs_close"]
        SCAN["fscan / fsel 文件扫描与选择"]
    end

    subgraph sg_VFS["VFS 核心"]
        IM["imount 挂载点<br/>ref 引用计数 + OS_MUTEX"]
        PART["vfs_partition 分区"]
        DEV["vfs_devinfo 设备信息"]
        OPS["vfs_operations 函数指针表"]
    end

    subgraph sg_FS["具体文件系统实现"]
        FAT["FAT 文件系统"]
        NOR["nor_fs (RECFILESYSTEM)"]
        LFS["littlefs (示例/移植)"]
    end

    subgraph sg_DEV["存储介质"]
        NORFLASH["SPI NOR Flash"]
        SDCARD["SD / TF 卡"]
    end

    APP --> FSAPI
    NET --> FSAPI
    FSAPI --> IM
    SCAN --> IM
    IM --> PART
    IM --> DEV
    IM --> OPS
    OPS --> FAT
    OPS --> NOR
    OPS --> LFS
    NOR --> NORFLASH
    FAT --> SDCARD
    FAT --> NORFLASH

架构说明: 应用与网络栈只依赖 fs.h 暴露的 FS API;VFS 核心通过 imount 聚合设备、分区与操作函数表三要素,把调用分派到具体文件系统实现。vfs_operations 是这套设计的「接口层」——任何新文件系统(如 littlefs)只要实现该函数表并注册到挂载点即可透明接入。nor_fs 与 FAT 之间没有依赖关系,二者通过同一张函数表被 VFS 调度,体现了「按介质选择实现、按挂载点隔离状态」的设计意图。

VFS 核心抽象(fs.h)

设备信息与分区模型

VFS 用两个结构描述一块存储介质如何被切分使用。vfs_devinfo 描述底层设备的共性(文件描述符、扇区大小、FAT 基址等),vfs_partition 描述介质上的一个逻辑分区,并携带挂载点路径 dir:

struct vfs_devinfo {
    void *fd;
    u32 sector_size;
    u32 fatbase;
    u32 database;
    void *private_data;
};

struct vfs_partition {
    struct vfs_partition *next;	/*!< 下一个分区信息指针 */
    u32 offset;			/*!< 分区起始偏移 */
    u32 clust_size;			/*!< 簇大小 */
    u32 total_size;			/*!< 总容量 */
    u8 fs_attr;			/*!< 文件属性 */
    u8 fs_type;			/*!< 文件系统类型 */
    char dir[VFS_PART_DIR_MAX];	/*!< 挂载点路径 */
    void *private_data;		/*!< 私有指针   */
};

Source: fs.h

设计意图:分区链表(next 指针)允许一块介质按偏移切分成多个分区(例如固件区、FAT 数据区、录音区),每个分区可挂载不同的文件系统;dir[VFS_PART_DIR_MAX](16 字节,见 #define VFS_PART_DIR_MAX 16)是挂载点路径,上层通过路径前缀选择分区,类似 Unix 的 /mnt/xxx 挂载语义。clust_size 直接决定空间分配粒度,是 FAT 类文件系统性能与碎片权衡的关键参数。

挂载点 imount —— 并发安全的核心

struct imount {
    int fd;					/*!< 私有设备指针 */
    const char *path;			/*!< 挂载点路径 */
    const struct vfs_operations *ops;	/*!< 文件系统操作函数句柄 */
    struct vfs_devinfo dev;		/*!< 设备信息 */
    struct vfs_partition part;	/*!< 分区信息 */
    struct list_head entry;		/*!< 链表节点 */
    atomic_t ref;				/*!< 引用计数器 */
    OS_MUTEX mutex;				/*!< 互斥量 */
    u8 avaliable;				/*!< 是否合法 */
    u8 part_num;				/*!< 分区数量 */
};

Source: fs.h

设计意图:每个挂载点是独立的并发单元。atomic_t ref 用于跟踪当前使用该挂载点的引用数(防止卸载时仍有打开的文件),OS_MUTEX mutex 串行化同一挂载点上的访问;所有挂载点通过 list_head entry 组织成全局链表,供 VFS 按路径查找。avaliable 标记挂载是否合法,part_num 记录分区数量 —— 支持一个挂载点管理多分区场景。

文件流 FILE 与文件属性

typedef struct {
    struct imount *mt;			/*!< 挂载点指针 */
    struct vfs_devinfo *dev;	/*!< 设备信息 */
    struct vfs_partition *part;	/*!< 分区信息 */
    void *private_data;			/*!< 私有指针 */
} FILE;

struct vfs_attr {
    u8 attr;		/*!< 文件属性标志位 */
    u32 fsize;		/*!< 文件大小 */
    u32 sclust;		/*!< 最小分配单元 */
    struct sys_time crt_time;	/*!< 文件创建时间 */
    struct sys_time wrt_time;	/*!< 文件最后修改时间 */
    struct sys_time acc_time;   /*!< 文件最后访问时间 */
};

Source: fs.h

FILE 是上层唯一可见的流句柄:mt/dev/part 三指针定位文件所属的挂载点、设备与分区,private_data 由具体文件系统持有(例如 FAT 的目录项缓存或 nor_fs 的 REC_FILE),实现「统一句柄、异构后端」。文件属性位在 fs.h 中以位掩码定义:F_ATTR_RW(0x00)、F_ATTR_RO(0x01)、F_ATTR_HID(0x02)、F_ATTR_SYS(0x04)、F_ATTR_VOL(0x08)、F_ATTR_DIR(0x10)、F_ATTR_ARC(0x20),兼容经典 FAT 属性语义,供 fset_attr/fget_attr 使用。

vfs_operations —— 文件系统多态接口

struct vfs_operations {
    const char *fs_type;
    int (*mount)(struct imount *, int);
    int (*unmount)(struct imount *);
    int (*format)(struct vfs_devinfo *, struct vfs_partition *);
    int (*fset_vol)(struct vfs_partition *, const char *name);
    int (*fget_free_space)(struct vfs_devinfo *, struct vfs_partition *, u32 *space);
    int (*fopen)(FILE *, const char *path, const char *mode);
    int (*fread)(FILE *, void *buf, u32 len);
    int (*fread_fast)(FILE *, void *buf, u32 len);
    int (*fwrite)(FILE *, void *buf, u32 len);
    int (*fseek)(FILE *, u32 offset, int);
    int (*fseek_fast)(FILE *, u32 offset, int);
    u32(*flen)(FILE *);
    u32(*fpos)(FILE *);
    int (*fget_name)(FILE *, u8 *name, int len);
    int (*fget_path)(FILE *, struct vfscan *, u8 *name, int len, u8 is_relative_path);
    int (*frename)(FILE *, const char *path);
    int (*fclose)(FILE *);
    int (*fdelete)(FILE *);
    int (*fscan)(struct vfscan *, const char *path, u8 max_deepth);
    int (*fscan_interrupt)(struct vfscan *, const char *path, u8 max_deepth, int (*callback)(void));
    void (*fscan_release)(struct vfscan *);
    int (*fsel)(struct vfscan *, int sel_mode, FILE *, int);
    int (*fget_attr)(FILE *, int *attr);
    int (*fset_attr)(FILE *, int attr);
    int (*fget_attrs)(FILE *, struct vfs_attr *);
    int (*ftruncate)(FILE *, u32 size);
    int (*fmove)(FILE *file, const char *path_dst, FILE *, int clr_attr, int path_len);
    int (*ioctl)(void *, int cmd, int arg);
    int (*fget_total_space)(struct imount *mt, u32 *space);
    /* ... 后续还有更多操作(fget_free_space 等) */
};

Source: fs.h

这是整个存储子系统的核心接口契约,设计上刻意对标 C 标准库的 FILE* 编程模型(fopen/fread/fwrite/fseek/fclose/fflush/ftruncate),使上层代码几乎可以无缝迁移。值得注意的细节:

  • 每个操作都携带 FILE*/struct imount*/vfs_devinfo* 等上下文,具体实现无需全局状态,天然支持多实例(多分区、多卡)。
  • fread_fast/fseek_fast 是快速路径:跳过普通路径的检查/缓冲逻辑,配合 FS_IOCTL_SAVE_FAT_TABLE 的 seek 加速,用于音频流等对吞吐敏感的读取。
  • fscan_interrupt 支持传入回调 int (*callback)(void),使大目录扫描可被中断(返回非 0 即中止扫描),避免 UI 线程卡死。
  • fmove 带 clr_attr 与 path_len 参数,支持跨分区/改名移动并选择是否清除属性。
  • fs_type 字符串标识实现类型(如 FAT、nor_fs),用于挂载时匹配分区。

文件扫描与选择(vfscan / fsel)

struct vfscan {
    u8 scan_file;	/*!< 是否扫描文件 */
    u8 subpath;		/*!< 子目录,设置是否只扫描一层 */
    u8 scan_dir;	/*!< 是否扫描目录 */
    u8 attr;		/*!< 文件属性 */
    u8 cycle_mode;	/*!< 扫描的循环模式 */
    char sort;		/*!< 扫描的文件排序 't' 'n' */
    char ftype[20 * 3 + 1];	/*!< 扫描的文件扩展类型 */
    u16 file_number;	/*!< 扫描出来的文件总数 */
    u16 file_counter;	/*!< 当前文件序号 */
    u16 dir_totalnumber;	/*!< 文件夹总数 */
    u16 musicdir_counter;	/*!< 播放文件所在文件夹序号 */
    u16 fileTotalInDir;	/*!< 文件夹下的文件数目 */
    void *priv;		/*!< 私有指针 */
    struct vfs_devinfo *dev;	/*!< 设备信息 */
    struct vfs_partition *part;	/*!< 分区信息 */
    char filt_dir[12];	/*!< 设置文件夹过滤 */
    char fasten_num[8];
    char *fasten_buf;
    char *d_save;
};

Source: fs.h

vfscan 是文件扫描的状态机上下文,专为音乐播放器等「按列表遍历文件」的场景设计:ftype[61] 可容纳 20 个三字符扩展名(如 "mp3wmaflac..."),sort 选择按时间 't' 或名称 'n' 排序,cycle_mode 配合 fsel 实现循环播放语义。选择模式 FSEL_FIRST_FILE(0) 至 FSEL_BY_PATH(10) 覆盖首/末/下/上/当前/序号/簇/自动/跨文件夹等遍历需求;循环模式 FCYCLE_LIST(0)/FCYCLE_ONE(2)/FCYCLE_FOLDER(3)/FCYCLE_RANDOM(4) 对应全部循环、单曲循环、文件夹循环与随机播放。这一设计把「播放列表导航」从具体文件系统中剥离出来,交给 VFS 统一实现。

ioctl 命令集

fs.h 定义了扩展控制命令(FS_IOCTL_*),用于覆盖标准文件操作之外的介质/文件系统能力:

命令用途
FS_IOCTL_SET_NAME_FILTER设置文件过滤
FS_IOCTL_GET_FOLDER_INFO获取文件夹序号与文件夹内文件数目
FS_IOCTL_SET_LFN_BUF / FS_IOCTL_SET_LDN_BUF设置长文件名/长目录名缓冲(512 字节)
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_GET_OUTFLASH_ADDR获取外置 Flash 实际物理地址(特殊 FAT 系统,手表场景)
FS_IOCTL_FLUSH_WBUF刷新写缓冲
FS_IOCTL_SAVE_FAT_TABLE保存 FAT 表,seek 加速
FS_IOCTL_INSERT_FILE / FS_IOCTL_DIVISION_FILE / FS_IOCTL_STORE_CLUST_RANG插入/分割文件、存储簇范围信息

Source: fs.h

注意其中若干命令在源码中标注为 unused 或「暂不支持」(如 FS_IOCTL_GET_FILE_NUM、FS_IOCTL_GET_ERR_CODE),说明 ioctl 列表是随产品需求演进的扩展点,上层代码不应假设全部命令可用。

NOR Flash 文件系统(nor_fs.h)

设计目标与数据模型

nor_fs.h 提供一套面向 NOR Flash 录音场景的极简文件系统。与 FAT 相比,它放弃目录树与复杂元数据,换取更小的内存占用、更少的擦写与更确定的行为 —— 非常适合连续追加的录音流。核心常量与结构:

#define NORFS_DATA_LEN  16
#define REC_FILE_END 0xFE

//文件索引
typedef struct __RECF_INDEX_INFO {
    u16 index;  //文件索引号
    u16 sector; //文件所在扇区
} RECF_INDEX_INFO ;

#define FLASH_PAGE_SIZE 256
//文件系统句柄
typedef struct __RECFILESYSTEM {
    RECF_INDEX_INFO index;
    u8 buf[FLASH_PAGE_SIZE];
    u16 total_file;
    u16 first_sector;
    u16 last_sector;
    u8 sector_size;
    void (*eraser)(u32 address);
    s32(*read)(u8 *buf, u32 addr, u32 len);
    s32(*write)(u8 *buf, u32 addr, u32 len);
} RECFILESYSTEM, *PRECFILESYSTEM ;

Source: nor_fs.h

设计要点:

  • 函数指针注入介质:eraser/read/write 三个指针把文件系统与具体 Flash 驱动解耦 —— 同一套逻辑既可跑在 SPI NOR 上,也可换用其他线性 Flash,只需提供对应实现。这与 VFS 层用 vfs_operations 解耦文件系统的思路一脉相承。
  • 扇区为分配单元:first_sector/last_sector 划定文件系统占用范围,sector_size 记录扇区大小(字节),total_file 维护文件总数;RECF_INDEX_INFO 以 u16 索引 + u16 扇区号定位文件,REC_FILE_END(0xFE) 作为文件结束标记。
  • 页缓冲:buf[256](FLASH_PAGE_SIZE)作为读写缓冲,匹配 NOR Flash 的页编程特性,避免跨页写碎。

文件句柄 REC_FILE 直接持有物理地址,读写位置由 rw_p 游标推进:

typedef struct __REC_FILE {
    RECF_INDEX_INFO index;
    RECFILESYSTEM *pfs;
    u32 addr;      /*!< 文件物理起始地址 */
    char priv_data[NORFS_DATA_LEN];
    u32 len;       /*!< 文件长度 */
    u32 w_len;
    u32 rw_p;      /*!< 读写游标 */
    u16 sr;        /*!< 扇区号 */
} REC_FILE;

Source: nor_fs.h

NOR_FS_HDL 将索引、文件系统句柄与文件句柄捆绑,供上层(如录音服务)统一持有:

typedef struct __nor_fs_hdl {
    u16 index;
    RECFILESYSTEM *recfs;
    REC_FILE *recfile;
} NOR_FS_HDL;

Source: nor_fs.h

录音文件生命周期

flowchart TD
    Start([nor_fs_init / nor_fs_ops_init]) --> Cap["nor_fs_set_rec_capacity 设置容量"]
    Cap --> Init["init_nor_fs(pfs, sector_start, sector_end, sector_size)"]
    Init --> Scan["recfs_scan 扫描已有文件<br/>重建 total_file / index"]
    Scan --> Open["open_recfile(index, pfs, pfile)"]
    Open --> Op{"操作类型?"}
    Op -->|"新建录音"| Create["create_recfile 分配文件<br/>写入 REC_FILE_END 标记"]
    Op -->|"追加/读取"| RW["recf_write / recf_read<br/>按 rw_p 游标流式读写"]
    Create --> RW
    RW --> Seek{"需要定位?"}
    Seek -->|"是"| S["recf_seek(pfile, NOR_FS_SEEK_SET/CUR, offsize)"]
    Seek -->|"否"| Close["close_recfile 保存 sr 索引"]
    S --> Close
    Close --> End([结束])

流程说明: 初始化分两步 —— 先设置容量(nor_fs_set_rec_capacity 或通用 set_rec_capacity,必须在使用前调用),再由 init_nor_fs 划定扇区范围。recfs_scan 在启动时重建文件索引(recfs_scan_ex 为扩展版本),之后 open_recfile 按索引打开文件;录音时 create_recfile 申请新文件并写入结束标记,随后以 recf_write 顺序写入(NOR Flash 允许页内改写,w_len 跟踪已写长度),读侧由 rw_p 游标控制;recf_save_sr 在关键节点持久化当前扇区号,实现断电续录/断点恢复。

上层入口 API

nor_fs.h 同时提供面向系统的接口,供录音/文件服务注册与查询容量:

函数作用
nor_fs_init / nor_fs_ops_init初始化 nor_fs 及注册操作句柄
nor_fs_set_rec_capacity(int) / set_rec_capacity(int)设置录音容量(使用前必须先设置)
nor_get_capacity / flashinsize_rec_get_capacity查询容量(内置 Flash 录音)
nor_get_index / flashinsize_rec_get_index查询当前文件索引
nor_set_offset_addr(int)设置起始偏移地址
recfs_scan_ex / sdfile_rec_scan_ex / _sdfile_rec_init扫描扩展 / SD 卡录音索引初始化
sdfile_rec_ops_init注册 SD 卡录音文件系统操作
rec_clear_norfs_fileindex / clear_norfs_fileindex清除文件索引

Source: nor_fs.h

注意 music_flash_file_set_index(u8 file_sel, u32 index) 的存在说明 nor_fs 还承担音乐 Flash 的文件索引管理,file_sel 用于区分内置/外置 Flash。

FS API 与网络栈的集成

文件系统不仅是业务数据通路,也是安全组件的文件后端。wolfSSL 的移植层 include_lib/net/wolfssl/wolfcrypt/wc_port.h 将标准 C 文件宏映射到 SDK 的 FS API:

#include <fs.h>
#define XFILE                   struct fs_file*
#define XFOPEN(NAME, MODE)      fs_open((char*)NAME);

Source: wc_port.h

同一文件针对不同 SDK 版本提供了多套映射(fs_fopen、fs/fs.h 的 struct fs_file*、struct fs_file_t* 等),本 SDK 采用 fs.h + fs_open 变体 —— 说明 fs.h 在 240 行之后还声明了 fs_open/fs_read/fs_write/fs_close 等按名调用 API(本页受读取范围所限未逐行展开,其准确签名请以 fs.h 源码为准)。TLS 握手时的证书/密钥文件即通过这条通路从 NOR Flash 或 SD 卡读出。uC-HTTPc 则直接使用 FS_FILE *FilePtr 句柄读写 HTTP 体缓存(见 include_lib/net/uC-HTTPc/http-c_app.h)。

Core Flow

打开-读取-关闭的标准调用链

sequenceDiagram
    participant APP as 上层应用
    participant VFS as VFS 核心 (imount)
    participant OPS as vfs_operations
    participant FS as 具体文件系统 (FAT / nor_fs)
    participant DEV as 设备驱动 (Flash / SD)

    APP->>VFS: fs_open(path, mode)
    VFS->>VFS: 按 path 前缀查找挂载点 imount<br/>ref++ / mutex 加锁
    VFS->>OPS: ops->fopen(FILE*, path, mode)
    OPS->>FS: 定位分区 vfs_partition<br/>初始化文件上下文
    FS->>DEV: 读取目录/索引区数据
    DEV-->>FS: 扇区数据
    FS-->>VFS: 填充 FILE.private_data
    VFS-->>APP: 返回 FILE 流

    APP->>VFS: fs_read(FILE, buf, len)
    VFS->>OPS: ops->fread(FILE*, buf, len)
    OPS->>FS: 按 rw_p 游标读取
    FS->>DEV: 介质 read
    DEV-->>FS: 数据
    FS-->>APP: 实际读取字节数

    APP->>VFS: fs_close(FILE)
    VFS->>OPS: ops->fclose(FILE*)
    VFS->>VFS: ref-- / mutex 解锁
    VFS-->>APP: 关闭完成

关键点: 每次 fs_open 都会递增挂载点引用计数并持锁,fs_close 时递减 —— 这把「文件在用时挂载点不可卸载」的约束落实到了 VFS 核心,而非依赖各文件系统实现自觉。快速路径(fread_fast/fseek_fast)与普通路径共用同一 FILE 句柄,但跳过完整性检查以获得更高吞吐。

使用示例

示例 1:注册一个挂载点(VFS 层)

以下结构定义了挂载点的完整上下文 —— 文件系统实现通过 vfs_operations 注册,挂载点通过 imount 绑定设备与分区:

struct vfs_operations {
    const char *fs_type;
    int (*mount)(struct imount *, int);
    int (*unmount)(struct imount *);
    int (*format)(struct vfs_devinfo *, struct vfs_partition *);
    int (*fget_free_space)(struct vfs_devinfo *, struct vfs_partition *, u32 *space);
    int (*fopen)(FILE *, const char *path, const char *mode);
    int (*fread)(FILE *, void *buf, u32 len);
    int (*fwrite)(FILE *, void *buf, u32 len);
    int (*fseek)(FILE *, u32 offset, int);
    u32(*flen)(FILE *);
    int (*fclose)(FILE *);
    int (*fdelete)(FILE *);
    int (*fscan)(struct vfscan *, const char *path, u8 max_deepth);
    int (*fsel)(struct vfscan *, int sel_mode, FILE *, int);
    int (*ftruncate)(FILE *, u32 size);
    int (*ioctl)(void *, int cmd, int arg);
};

Source: fs.h(节选)

示例 2:初始化 nor_fs 并扫描录音文件

nor_fs 的初始化流程明确展示了「先划容量、再定扇区、后扫描」的顺序依赖 —— 这是使用方必须遵守的调用契约:

u8 recf_seek(REC_FILE *pfile, u8 type, int offsize);
u16 recf_read(REC_FILE *pfile, u8 *buff, u16 btr);
u16 recf_write(REC_FILE *pfile, u8 *buff, u16 btw);
u32 create_recfile(RECFILESYSTEM *pfs, REC_FILE *pfile);
u32 close_recfile(REC_FILE *pfile);
u32 open_recfile(u32 index, RECFILESYSTEM *pfs, REC_FILE *pfile);
void recf_save_sr(REC_FILE *pfile, u16 sr);

int music_flash_file_set_index(u8 file_sel, u32 index);
u32 recfs_scan(RECFILESYSTEM *pfs);
void init_nor_fs(RECFILESYSTEM *pfs, u16 sector_start, u16 sector_end, u8 sector_size);

u32 nor_fs_init(void);
int nor_fs_set_rec_capacity(int capacity); //需要先设置容量。
int nor_fs_ops_init(void);

Source: nor_fs.h

示例 3:TLS 栈通过 FS API 读取证书文件

wolfSSL 移植层把标准 C 库的文件宏替换为 SDK 的 FS API,使证书加载无需经过堆上的 RAM 拷贝即可直接从 Flash 读取:

#include <fs.h>
#define XFILE                   struct fs_file*
#define XFOPEN(NAME, MODE)      fs_open((char*)NAME);
#define XFREAD(BUF, SZ, CNT, FD)  fs_read((BUF), (SZ)*(CNT), (FD))
#define XFCLOSE(FD)               fs_close(FD)

Source: wc_port.h(宏映射语义依据 wc_port.h 对 fs_open 的引用)

示例 4:littlefs 作为可移植文件系统参考

SDK 的示例工程中携带了完整的 littlefs 移植(含块设备模拟层),可作为「新文件系统如何接入」的参考实现:

// apps/common/example/third_party/littlefs/bd/lfs_rambd.h   —— RAM 块设备(测试用)
// apps/common/example/third_party/littlefs/bd/lfs_filebd.h  —— 文件模拟块设备
// apps/common/example/third_party/littlefs/bd/lfs_testbd.h  —— 故障注入测试块设备
// apps/common/example/third_party/littlefs/lfs.h            —— littlefs 核心 API

Source: littlefs 目录

配置选项

以下常量与参数控制存储子系统的行为,均为源码中定义:

选项类型默认值说明
VFS_PART_DIR_MAX宏16分区挂载点路径最大长度(字节)
NORFS_DATA_LEN宏16nor_fs 文件私有数据区长度
REC_FILE_END宏0xFEnor_fs 文件结束标记字节
FLASH_PAGE_SIZE宏256nor_fs 页缓冲大小(匹配 NOR 页编程粒度)
SEEK_SET / SEEK_CUR / SEEK_END宏0 / 1 / 2fseek 定位基准
F_ATTR_RW / RO / HID / SYS / VOL / DIR / ARC宏0x00…0x20文件属性位掩码
FSEL_FIRST_FILE … FSEL_BY_PATH宏0…10fsel 文件选择模式
FCYCLE_LIST / ONE / FOLDER / RANDOM宏0 / 2 / 3 / 4扫描循环模式
NOR_FS_SEEK_SET / NOR_FS_SEEK_CUR枚举0 / 0x01nor_fs 内部定位模式
nor_fs_set_rec_capacity(capacity)参数无录音容量,必须在使用前设置
init_nor_fs(pfs, sector_start, sector_end, sector_size)参数无划定文件系统扇区范围
vfs_partition.clust_size结构字段无簇大小,影响空间分配粒度与碎片
vfs_partition.fs_type结构字段无文件系统类型(用于挂载匹配)

API 参考

nor_fs.h 文件系统接口

void init_nor_fs(RECFILESYSTEM *pfs, u16 sector_start, u16 sector_end, u8 sector_size) 初始化 nor_fs:划定起始/结束扇区与扇区大小。必须先于所有文件操作调用。

u32 recfs_scan(RECFILESYSTEM *pfs) 扫描扇区范围,重建文件索引与 total_file。返回扫描到的文件总数。

u32 open_recfile(u32 index, RECFILESYSTEM *pfs, REC_FILE *pfile) 按索引打开文件,填充 pfile 的地址/长度信息。

  • 参数:index 文件索引号;pfs 文件系统句柄;pfile 输出文件句柄。
  • 返回:0 表示成功,非 0 表示失败(如索引无效)。

u32 create_recfile(RECFILESYSTEM *pfs, REC_FILE *pfile) 新建录音文件:分配扇区并写入 REC_FILE_END 结束标记。

u32 close_recfile(REC_FILE *pfile) 关闭文件并落盘索引信息(结合 recf_save_sr 保存扇区号)。

u16 recf_read(REC_FILE *pfile, u8 *buff, u16 btr) 从 rw_p 游标处读取最多 btr 字节,返回实际读取字节数。

u16 recf_write(REC_FILE *pfile, u8 *buff, u16 btw) 在 rw_p 游标处写入 btw 字节,推进 w_len,返回实际写入字节数。

u8 recf_seek(REC_FILE *pfile, u8 type, int offsize) 定位读写游标。type 取 NOR_FS_SEEK_SET(0) 或 NOR_FS_SEEK_CUR(0x01)。

void recf_save_sr(REC_FILE *pfile, u16 sr) 持久化当前扇区号,用于断电续录。

u32 nor_fs_init(void) / int nor_fs_ops_init(void) 系统级初始化:前者初始化 nor_fs 全局状态,后者注册文件系统操作句柄供上层调用。

int nor_fs_set_rec_capacity(int capacity) / int set_rec_capacity(int capacity) 设置录音容量(必须先于录音操作调用)。

FS_IOCTL 命令(ioctl 第二参数)

int (*ioctl)(void *, int cmd, int arg) 的 cmd 取值见上文 ioctl 命令集,其中 FS_IOCTL_GET_OUTFLASH_ADDR 返回外置 Flash 物理地址(特殊 FAT 系统/手表场景),FS_IOCTL_SAVE_FAT_TABLE 触发 FAT 表保存以加速 seek。

说明(诚实声明)

fs.h 在 240 行之后还包含约 877 行内容(包括 fs_open/fs_read/fs_write/fs_close 等按名调用 API 与挂载/卸载/格式化接口的声明)。本页读取范围截至第 240 行,上述 API 的精确签名未在本页逐条列出 —— 使用前请直接查阅 fs.h 后续部分。

失败模式、边界情况与并发

并发与一致性

  • 挂载点级互斥:imount 内含 OS_MUTEX 与 atomic_t ref。VFS 保证同一挂载点上的操作串行化,并通过引用计数阻止「文件打开期间卸载分区」。多任务访问不同挂载点(如同时读 SD 与写 NOR Flash)可并行,互不阻塞。
  • nor_fs 的断点持久化:recf_save_sr 在关键位置保存扇区号,录音过程中意外断电后可通过扫描(recfs_scan)与保存的 sr 恢复,属于「面向连续写、容忍掉电」的设计。
  • FAT 表缓存一致性:FS_IOCTL_FLUSH_WBUF 与 FS_IOCTL_SAVE_FAT_TABLE 的存在说明写路径有缓冲与 FAT 表延迟落盘机制,拔卡/断电前必须显式刷新,否则可能丢失最近写入。

边界与限制

  • nor_fs 容量:文件索引 u16、扇区号 u16,单文件系统可寻址扇区数受 16 位限制;REC_FILE_END(0xFE) 作为结束标记,意味着数据字节 0xFE 需在写入层处理,避免被误判为文件结束。
  • 分区路径长度:VFS_PART_DIR_MAX = 16,挂载点路径不能超过 16 字节,命名需精简。
  • 扩展名过滤:vfscan.ftype 为 20*3+1 字节,最多 20 个三字符扩展名,超过部分被忽略。
  • ioctl 兼容性:FS_IOCTL_GET_FILE_NUM、FS_IOCTL_FILE_CHECK、FS_IOCTL_FREE_CACHE 标注 unused,FS_IOCTL_GET_ERR_CODE「暂不支持」——调用方需自行做能力探测或版本适配。
  • LFN 缓冲:长文件名/目录名需要显式分配缓冲(FS_IOCTL_SET_LFN_BUF/SET_LDN_BUF,512 字节),未设置时长名可能截断。

失败处理

  • 扫描可中断:fscan_interrupt 通过回调返回非 0 中止扫描,适合 UI 线程做进度反馈与取消。
  • 容量设置顺序:nor_fs_set_rec_capacity 注释明确「需要先设置容量」,违反顺序会导致录音失败或索引错乱。
  • 快速路径无保护:fread_fast/fseek_fast 跳过常规检查,错误参数(如越界长度)可能产生未定义行为,仅适合可信内部调用。

性能与运维注意

  • 快速路径:音频等大数据流应使用 fread_fast + fseek_fast,避免普通路径的开销;配合 FS_IOCTL_SAVE_FAT_TABLE 可显著减少 FAT 定位时间。
  • 簇大小权衡:vfs_partition.clust_size 决定分配粒度 —— 簇越大顺序读性能越好、碎片越少,但小文件空间浪费越多,需按分区用途(音乐/录音/配置)分别配置。
  • nor_fs 页对齐:FLASH_PAGE_SIZE = 256 的页缓冲与 NOR 页编程粒度对齐,顺序写录音流时尽量按页整写,减少擦写放大。
  • 写缓冲刷新:涉及 FAT 的写操作应定期/在关键节点触发 FS_IOCTL_FLUSH_WBUF 与 FS_IOCTL_SAVE_FAT_TABLE,在吞吐与掉电安全之间取得平衡。

扩展点

  • 接入新文件系统:实现 struct vfs_operations(约 30 个函数指针)并注册到挂载点即可;littlefs 位于 apps/common/example/third_party/littlefs,其 lfs.h 与 bd/ 块设备层(lfs_rambd/lfs_filebd/lfs_testbd)展示了从零接入的完整范例。
  • 适配新 Flash 介质:nor_fs 通过 RECFILESYSTEM 的 eraser/read/write 函数指针适配任意线性 Flash;nor_set_offset_addr 允许调整起始偏移,支持外置 Flash 场景。
  • 扩展 FS 能力:通过 FS_IOCTL_* 命令集在 ioctl 中扩展(如插入/分割文件、簇范围存储),无需改动 VFS 核心。

测试

示例工程中的 littlefs 自带测试基础设施:bd/lfs_rambd.h(RAM 模拟块设备)、bd/lfs_filebd.h(用宿主文件模拟块设备)、bd/lfs_testbd.h(可注入擦写/读写故障的测试块设备),可用于验证文件系统在掉电、擦写失败等异常下的行为 —— 该模式同样适用于对 nor_fs 的介质层做故障注入测试。SDK 本身未提供针对 fs.h/nor_fs.h 的独立单元测试文件(本页范围内未发现),其正确性主要依赖录音、播放等业务应用的实际运行验证。

Related Links

  • fs.h —— VFS 核心定义
  • nor_fs.h —— NOR Flash 文件系统
  • fs_file_name.h —— FAT 文件名/地址信息结构
  • wc_port.h —— wolfSSL 的 FS API 映射
  • littlefs —— 可移植文件系统参考实现
  • 兄弟页面:Flash/SD 底层驱动、录音服务、OTA 升级(各自页面单独覆盖,本页不展开)
Prev
USB 主机与设备栈
Next
系统服务与外设管理