7.1 文件系统
7.1 文件系统
📚 本节导读
学习时长: 约 60 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: C 语言文件操作、POSIX 文件 API、第 5 章设备驱动框架
🎯 学习目标
- 理解 OneOS VFS(虚拟文件系统)的设计理念与分层架构
- 掌握 vfs_file、vfs_filesystem_ops、vfs_mountpoint 核心结构体
- 掌握 vfs_open / vfs_close / vfs_read / vfs_write 等 POSIX 文件 API
- 了解 DevFS 设备文件系统及其将设备映射文件的设计
- 了解 LittleFS、FatFs、JFFS2、YAFFS、NFS 等文件系统类型
- 掌握 Kconfig 配置项(挂载点、文件描述符上限、自动挂载等)
- 能够使用 VFS 进行文件读写操作
一、概述
OneOS 采用 VFS(Virtual File System,虚拟文件系统) 架构,上层应用提供统一的 POSIX 标准文件操作接口,同时支持多种底层文件系统实现。VFS 的核心设计思想是抽象与解耦:上层应用无需底层是 Flash 文件系统、FAT 文件系统还是网络文件系统,统一使用 open / read / write / close 等标准 API 进行文件操作。
VFS 的源码位于 components/fs/ 目录,核心头文件 vfs.h。
核心设计特点:
- POSIX 兼容:提供标准的 open / close / read / write / lseek / stat / opendir / readdir 等接口
- 多文件系统支持:LittleFS(掉电保护)、FatFs(FAT/exFAT)、JFFS2(日志 Flash)、YAFFS(NAND Flash)、NFS(网络)、DevFS(设备文件)
- 挂载点机制:每个文件系统挂载到独立的目录路径,支持多分区挂载
- 设备文件系统(DevFS):将设备抽象文件节点,通过文件 API 操作设备
- 自动挂载:支持启动时根据配置文件自动挂载文件系统
- IO 多路复用:支持 select/poll 机制
二、核心数据结构
2.1 vfs_file —— 文件描述符结构体
每个打开的文件都对应一个 vfs_file 结构体(源码 vfs.h 第52-64行):
struct vfs_file
{
unsigned short type; /* Type (regular or directory) */
int flags; /* Open mode flags */
const char *path; /* Name (below mount point) */
off_t off_ptr; /* offset of rw pos */
struct vfs_mountpoint *mnt_point;
void *desc; /* Record the descriptor of open file. */
#ifdef OS_USING_VFS_DEVFS
void *private; /* Record the private data when register dev. */
#endif
};| 字段 | 类型 | 说明 |
|---|---|---|
type | unsigned short | 文件类型:FT_REGULAR(1) 普通文件、FT_DIRECTORY(2) 目录、FT_DEVICE(3) 设备文件 |
flags | int | 打开模式(如 O_RDONLY、O_WRONLY、O_RDWR、O_CREAT 等) |
path | const char * | 挂载点下的相对路径 |
off_ptr | off_t | 当前文件读写位置,支持 lseek 修改 |
mnt_point | struct vfs_mountpoint * | 指向所属挂载点的指针 |
desc | void * | 底层文件系统描述符,实现文件系统间的隔离 |
2.2 vfs_filesystem_ops —— 文件系统操作接口
每种文件系统类型需要实现 vfs_filesystem_ops 中定义的操作函数集(源码 vfs.h 第72-105行):
struct vfs_filesystem_ops
{
char *fs_name;
/* Operations for file */
int (*open) (struct vfs_file *fp);
int (*close) (struct vfs_file *fp);
int (*read) (struct vfs_file *fp, void *buf, size_t count);
int (*write) (struct vfs_file *fp, const void *buf, size_t count);
int (*lseek) (struct vfs_file *fp, off_t offset, int whence);
int (*sync) (struct vfs_file *fp);
int (*ioctl) (struct vfs_file *fp, unsigned long cmd, void *args);
#ifdef OS_USING_IO_MULTIPLEXING
int (*poll) (struct vfs_file *fp, struct vfs_pollfd *poll_fd, os_bool_t poll_setup);
#endif
/* Operations for dirctory */
int (*opendir) (struct vfs_dir *dp);
int (*closedir) (struct vfs_dir *dp);
int (*readdir) (struct vfs_dir *dp, struct dirent *dentry);
int (*seekdir) (struct vfs_dir *dp, off_t offset);
long (*telldir) (struct vfs_dir *dp);
/* Operations for file or dircotry */
int (*rename) (struct vfs_mountpoint *mnt_point, const char *oldpath, const char *newpath);
int (*unlink) (struct vfs_mountpoint *mnt_point, const char *path);
int (*stat) (struct vfs_mountpoint *mnt_point, const char *path, struct stat *buf);
/* Operations for filesystem */
int (*mkfs) (void *dev);
int (*statfs) (struct vfs_mountpoint *mnt_point, struct statfs *buf);
int (*mount) (struct vfs_mountpoint *mnt_point, unsigned long mountflag, const void *data);
int (*unmount) (struct vfs_mountpoint *mnt_point);
};该结构体将文件系统操作分为四个层次:
- 文件操作:open / close / read / write / lseek / sync / ioctl / poll
- 目录操作:opendir / closedir / readdir / seekdir / telldir
- 通用操作:rename / unlink / stat
- 文件系统操作:mkfs / statfs / mount / unmount
2.3 vfs_mountpoint —— 挂载点结构体
每个已挂载的文件系统对应一个挂载点(源码 vfs.h 第107-115行):
struct vfs_mountpoint
{
unsigned short is_inited;
unsigned short ref_cnt; /* Record referecnt count of this mount point. */
void *dev; /* Attached device pointer */
char *mnt_path; /* File system mount point */
const struct vfs_filesystem_ops *ops; /* Operations for file system type */
void *fs_instance; /* Actual file system */
};2.4 vfs_mountinfo —— 自动挂载配置结构体
用于启动时自动挂载文件系统(源码 vfs.h 第117-125行):
struct vfs_mountinfo
{
char *mnt_path; /*mount point*/
char *fal_part; /*fal partition name*/
char *fs_type; /*filesystem type: littlefs ...*/
uint8_t auto_format; /*set aoto mkfs if mount failed*/
char reserved[7];
};三、文件类型
VFS 定义了四种文件类型(源码 vfs.h 第45-50行):
| 宏 | 值 | 含义 |
|---|---|---|
FT_INVALID | 0 | 无效类型 |
FT_REGULAR | 1 | 普通文件 |
FT_DIRECTORY | 2 | 目录 |
FT_DEVICE | 3 | 设备文件(DevFS) |
FT_TYPEMAX | 4 | 最大类型值(越界检查用) |
四、核心 API
4.1 文件操作
VFS 提供标准的 POSIX 文件操作接口:
int open(const char *path, int flags, ...); /* 打开/创建文件 */
int close(int fd); /* 关闭文件 */
int read(int fd, void *buf, size_t count); /* 读取文件 */
int write(int fd, const void *buf, size_t count); /* 写入文件 */
off_t lseek(int fd, off_t offset, int whence); /* 移动文件读写位置 */
int fsync(int fd); /* 同步文件到存储 */
int stat(const char *path, struct stat *buf); /* 获取文件状态 */
int unlink(const char *path); /* 删除文件 */
int rename(const char *oldpath, const char *newpath); /* 重命名文件 */4.2 目录操作
DIR *opendir(const char *path); /* 打开目录 */
int closedir(DIR *dp); /* 关闭目录 */
struct dirent *readdir(DIR *dp); /* 读取目录项 */
int mkdir(const char *path, mode_t mode); /* 创建目录 */
int rmdir(const char *path); /* 删除目录 */4.3 文件系统操作
VFS 的文件系统级接口(vfs_fs.h 声明):
extern int vfs_mount(const char *device_name, const char *path, const char *fs_name,
unsigned long mountflag, const void *data); /* 挂载文件系统 */
extern int vfs_unmount(const char *path); /* 卸载文件系统 */
extern int vfs_mkfs(const char *fs_name, const char *device_name);/* 格式化文件系统 */
extern int vfs_statfs(const char *path, struct statfs *buf); /* 获取文件系统信息 */关键理解(易错点):
vfs_mkfs的参数顺序是vfs_mkfs(fs_name, device_name)——文件系统类型在前、设备名在后(与直觉相反)!auto_mount_fs源码中即vfs_mkfs(mount_table[i].fs_type, mount_table[i].fal_part)。vfs_mount则是device_name, path, fs_name, mountflag, data。
4.4 fd_to_fp —— 文件描述符转换
struct vfs_file *fd_to_fp(int fd);将文件描述符(整数)转换为 VFS 文件结构体指针,用于内部实现。该函数声明于 vfs.h 第126行。
五、DevFS 设备文件系统
DevFS 是 OneOS 特有的设备文件系统,将设备抽象为文件节点,使开发者可以通过文件 API 操作设备(对应 Kconfig 中 OS_USING_VFS_DEVFS 配置项)。
启用 DevFS 后,设备文件通常挂载在 /dev/ 目录下:
// 例如:通过文件 API 操作串口设备
int fd = open("/dev/uart0", O_RDWR);
write(fd, "Hello", 5);
char buf[64];
read(fd, buf, sizeof(buf));
close(fd);六、支持的文件系统类型
6.1 LittleFS
- 配置项:
OS_USING_VFS_LFS - 特点:专为微控制器设计,支持掉电保护(power-loss resilience),磨损均衡
- 适用场景:SPI NOR Flash、内部 Flash 等需要数据安全性的场景
- 关键配置:
LFS_NAME_MAX、LFS_FILE_MAX、LFS_LOOKAHEAD_SIZE、LFS_THREADSAFE
6.2 FatFs
- 配置项:
OS_USING_VFS_FATFS - 特点:通用 FAT/exFAT 文件系统,兼容性好,支持长文件名
- 适用场景:SD 卡、U 盘等需要与 PC 交换数据的场景
- 关键配置:
OS_VFS_FAT_CODE_PAGE(OEM 代码页,默认 437)、OS_VFS_FAT_USE_LFN(长文件名模式 0/1/2/3,默认 3)、OS_VFS_FAT_MAX_LFN(最大文件名长度,默认 255)、OS_VFS_FAT_DRIVES(逻辑盘数量,默认 2)、OS_VFS_FAT_MAX_SECTOR_SIZE(最大扇区,默认 4096)、OS_VFS_FAT_REENTRANT(线程安全,默认 y)
6.3 JFFS2
- 配置项:
OS_USING_VFS_JFFS2 - 特点:日志型 Flash 文件系统 v2,支持磨损均衡和掉电保护
- 适用场景:NOR Flash 大容量存储
6.4 YAFFS
- 配置项:
OS_USING_VFS_YAFFS - 特点:专为 NAND Flash 设计的文件系统
- 适用场景:NAND Flash 存储设备
6.5 NFS
- 配置项:
OS_USING_VFS_NFS - 特点:网络文件系统 v3 客户端,可挂载远程 NFS 服务器
- 适用场景:需要访问远程文件服务器的场景
- 关键配置:
OS_NFS_HOST_EXPORT(NFS 服务器地址和导出路径)
6.6 CuteFS
- 配置项:
OS_USING_VFS_CUTEFS - 特点:轻量级内存文件系统,运行在 RAM Disk 上
- 适用场景:临时文件存储、测试
七、Kconfig 配置详解
VFS 配置位于 components/fs/Kconfig:
7.1 基础配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
OS_USING_VFS | bool | y | 启用虚拟文件系统 |
VFS_MOUNTPOINT_MAX | int | 4 | 最大挂载点数量 |
VFS_FILESYSTEM_TYPES_MAX | int | 4 | 最大文件系统类型数量 |
VFS_FD_MAX | int | 16 | 最大打开文件数 |
7.2 自动挂载
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
OS_USING_AUTO_MOUNT | bool | n | 启用自动挂载(需 fs_cfg.c 描述挂载信息) |
7.3 文件系统类型
| 配置项 | 支持的文件系统 |
|---|---|
OS_USING_VFS_DEVFS | 设备文件系统 |
OS_USING_VFS_CUTEFS | CuteFS 内存文件系统 |
OS_USING_VFS_JFFS2 | JFFS2 日志 Flash 文件系统 |
OS_USING_VFS_YAFFS | YAFFS NAND Flash 文件系统 |
OS_USING_VFS_FATFS | FatFs FAT/exFAT 文件系统 |
OS_USING_VFS_NFS | NFS v3 网络文件系统 |
OS_USING_VFS_LFS | LittleFS 文件系统 |
八、使用示例
8.1 基本文件操作
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
void basic_file_ops(void)
{
int fd;
char buf[128];
struct stat st;
/* 打开文件(创建并写入) */
fd = open("/data/test.txt", O_CREAT | O_WRONLY, 0666);
if (fd < 0) {
os_kprintf("Failed to open file\r\n");
return;
}
/* 写入数据 */
write(fd, "Hello OneOS!\r\n", 14);
close(fd);
/* 读取文件 */
fd = open("/data/test.txt", O_RDONLY);
read(fd, buf, sizeof(buf));
os_kprintf("Read: %s\r\n", buf);
close(fd);
/* 获取文件状态 */
stat("/data/test.txt", &st);
os_kprintf("File size: %ld bytes\r\n", st.st_size);
}8.2 目录遍历
#include <dirent.h>
void list_directory(const char *path)
{
DIR *dp;
struct dirent *entry;
dp = opendir(path);
if (dp == NULL) {
os_kprintf("Failed to open directory: %s\r\n", path);
return;
}
os_kprintf("Contents of %s:\r\n", path);
while ((entry = readdir(dp)) != NULL) {
os_kprintf(" %s (%s)\r\n", entry->d_name,
entry->d_type == DT_DIR ? "DIR" : "FILE");
}
closedir(dp);
}8.3 文件系统挂载
#include <vfs.h>
#include <vfs_fs.h>
void mount_filesystems(void)
{
/* 格式化 SPI Flash 分区为 LittleFS(注意参数顺序:fs_type 在前、dev 在后) */
if (vfs_mkfs("littlefs", "spi_flash0") != 0) {
os_kprintf("Mkfs failed!\r\n");
return;
}
/* 挂载 LittleFS 到 /data 目录 */
if (vfs_mount("spi_flash0", "/data", "littlefs", 0, NULL) != 0) {
os_kprintf("Mount failed!\r\n");
return;
}
os_kprintf("LittleFS mounted at /data\r\n");
/* 挂载 SD 卡为 FatFs */
if (vfs_mount("sd0", "/sdcard", "fat", 0, NULL) != 0) {
os_kprintf("SD card mount failed!\r\n");
return;
}
os_kprintf("FatFs mounted at /sdcard\r\n");
}8.4 自动挂载配置
开启 OS_USING_AUTO_MOUNT 后,VFS 会在启动阶段(auto_mount_fs,应用级、中子级)遍历挂载表 fs_mount_info[] 逐个挂载。挂载表定义在 components/fs/source/fs_cfg.h,完整源码:
#include "vfs.h"
const struct vfs_mountinfo fs_mount_info[] = {
/* point, part, fs_type, auto_format */
{ "/", "filesystem", "fat", 1},
};关键理解:
- 表类型为
const struct vfs_mountinfo fs_mount_info[](注意数组名是fs_mount_info,不是g_fs_mount_table); - 每个表项按
{mnt_path, fal_part, fs_type, auto_format}四元组描述一次挂载:挂载路径、FAL 分区名、文件系统类型、挂载失败是否自动格式化; auto_mount_fs处理流程:非yaffs类型时先fal_blk_device_create创建块设备 → 若挂载路径不是/且不存在则vfs_mkdir创建目录 →vfs_mount挂载;挂载失败且auto_format=1时执行vfs_mkfs格式化后重新挂载(对应源码vfs_fs.c第774-868行)。
用户可按需增删表项,例如新增 LittleFS 数据分区:
const struct vfs_mountinfo fs_mount_info[] = {
/* point, part, fs_type, auto_format */
{ "/", "filesystem", "fat", 1},
{ "/data", "files", "littlefs", 1},
};📝 本节小结
核心要点回顾
- VFS 分层:上层 POSIX 标准 API → VFS 框架(
vfs_file/vfs_filesystem_ops/vfs_mountpoint)→ 具体文件系统实现(LittleFS/FatFs/JFFS2/YAFFS/NFS/DevFS/CuteFS),上层无需关心底层介质。 - 文件描述符:每个打开文件对应一个
vfs_file,封装type/flags/path/off_ptr(读写位置)/mnt_point/desc(底层描述符),DevFS 启用时额外含private。 - 操作接口:
vfs_filesystem_ops把操作分四层——文件操作(open/close/read/write/lseek/sync/ioctl/poll)、目录操作(opendir/readdir...)、通用操作(rename/unlink/stat)、文件系统操作(mkfs/statfs/mount/unmount)。 - 挂载点:
vfs_mountpoint通过dev/mnt_path/ops/fs_instance把设备、挂载路径、操作接口、文件系统实例绑定,多个文件系统可挂到不同路径共存。 - 文件类型:
FT_INVALID(0)/FT_REGULAR(1)/FT_DIRECTORY(2)/FT_DEVICE(3)/FT_TYPEMAX(4)。 - 自动挂载:
OS_USING_AUTO_MOUNT开启后,auto_mount_fs(应用级)遍历fs_mount_info[],先建块设备、必要时建目录,再挂载;失败且auto_format=1时格式化后重挂。 - 易错签名:
vfs_mkfs(fs_name, device_name)参数顺序是类型在前、设备在后(与直觉相反)。 - 配置项:
VFS_MOUNTPOINT_MAX(4)、VFS_FILESYSTEM_TYPES_MAX(4)、VFS_FD_MAX(16) 决定容量上限。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
| 在源码结构体中加中文注释 | 违反"逐字一致"规范 → 保留源码英文注释,中文解释写正文 |
结构体名写 vfs_files_ops | 正确名是 vfs_filesystem_ops → 注意拼写 |
vfs_mkfs 参数顺序搞反 | 正确为 vfs_mkfs(fs_type, device),类型在前、设备在后 |
卸载接口写 vfs_umount(单 u) | 正确为 vfs_unmount(双 u) |
自动挂载表名写 g_fs_mount_table | 正确为 fs_mount_info[](定义在 fs_cfg.h) |
忘开 OS_USING_VFS 就调文件 API | open/read/write 全部无效 → 先开 VFS 及其依赖(select OS_USING_MUTEX/OS_USING_LIBC_ADAPTER) |
文件类型枚举漏 FT_TYPEMAX | FT_TYPEMAX(4) 用于越界检查 → 类型有效范围是 [0, 4) |
| 挂载前不先 mkfs | 未格式化直接 mount 会失败 → 先 vfs_mkfs 或用 auto_format=1 自动格式化 |
下一步
- 7.2 网络协议栈:VFS 已提供文件与存储抽象,下一节进入 OneOS 的 TCP/IP 网络协议栈(LWIP),掌握网络接口、Socket 编程与网络配置,可与文件系统配合实现数据上云、日志落盘等完整业务。