文件系统与存储管理
本页面向 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 | 宏 | 16 | nor_fs 文件私有数据区长度 |
REC_FILE_END | 宏 | 0xFE | nor_fs 文件结束标记字节 |
FLASH_PAGE_SIZE | 宏 | 256 | nor_fs 页缓冲大小(匹配 NOR 页编程粒度) |
SEEK_SET / SEEK_CUR / SEEK_END | 宏 | 0 / 1 / 2 | fseek 定位基准 |
F_ATTR_RW / RO / HID / SYS / VOL / DIR / ARC | 宏 | 0x00…0x20 | 文件属性位掩码 |
FSEL_FIRST_FILE … FSEL_BY_PATH | 宏 | 0…10 | fsel 文件选择模式 |
FCYCLE_LIST / ONE / FOLDER / RANDOM | 宏 | 0 / 2 / 3 / 4 | 扫描循环模式 |
NOR_FS_SEEK_SET / NOR_FS_SEEK_CUR | 枚举 | 0 / 0x01 | nor_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 升级(各自页面单独覆盖,本页不展开)