Vela 文件系统源码学习

概述

本文档主要介绍 Vela 操作系统中文件操作(open、read、write、close)的主要流程。

文件系统整体架构

Vela 系统采用 VFS(Virtual File System) 架构,主要包含:

VFS 层 – 提供统一的文件操作接口

具体文件系统实现(FAT、PROCFS、TMPFS等)

设备驱动层

文件系统相关的重要数据结构:

struct inode – 文件系统节点,代表文件/目录

struct file – 打开文件实例

struct file_operations – 文件操作方法集

struct mountpt_operations – 挂载点操作方法集

open() 调用流程

应用层调用 open()

进入系统调用 nx_open()

调用 inode_search() 查找路径对应的 inode

如果是普通文件:

调用对应文件系统的 open 操作(如 fat_open)

分配 file 结构体,关联 inode

如果是设备文件:

调用设备驱动的 open 操作

关联驱动的 file_operations

返回文件描述符

open() 详细调用流程

1. 系统调用入口

应用程序调用open()后,进入系统调用层:

int open(const char *path, int oflags, ...) 
  -> int nx_open(const char *path, int oflags, mode_t mode)
    -> int file_open(const char *path, int oflags, mode_t mode)
      -> int file_vopen(struct file *filep, const char *path, int oflags, mode_t mode)

2. 路径解析与inode查找

在file_vopen()中,首先需要解析路径找到对应的inode:

调用inode_search()开始查找:

SETUP_SEARCH(&desc, path, false);  // 初始化查找描述符
ret = inode_find(&desc);           // 开始查找inode

inode_find()内部调用_inode_search():

从根节点(g_root_inode)开始搜索

按路径组件逐级遍历inode树

支持软链接和挂载点的处理

查找过程中的关键步骤:

比较当前节点名与路径组件

处理特殊情况(软链接、挂载点)

返回找到的inode及相关信息

inode_search返回结果包含:

node: 找到的目标inode

peer: 同级前一个节点

parent: 父节点

relpath: 挂载点内的相对路径

3. 文件打开处理

根据找到的inode类型执行不同的打开操作:

设备驱动文件:

if (INODE_IS_DRIVER(inode)) {
    // 调用驱动的open方法
    ret = inode->u.i_ops->open(filep);
}

块设备文件:

if (INODE_IS_BLOCK(inode)) {
    // 创建字符设备代理
    ret = block_proxy(filep, path, oflags);
}

挂载点文件:

if (INODE_IS_MOUNTPT(inode)) {
    // 调用文件系统的open方法
    ret = inode->u.i_mops->open(filep, relpath, oflags, mode);
}

4. 文件结构设置

为打开的文件分配file结构:

memset(filep, 0, sizeof(*filep));
filep->f_oflags = oflags;    // 设置打开标志
filep->f_inode = inode;      // 关联inode

根据oflags检查访问权限:

ret = inode_checkflags(inode, oflags);

设置文件操作方法:

普通文件: 使用文件系统提供的file_operations

设备文件: 使用驱动提供的file_operations

目录: 使用特殊的目录操作方法

5. 错误处理

在open()过程中的主要错误情况:

路径解析错误:

ENOENT: 路径不存在

ENOTDIR: 路径组件不是目录

ELOOP: 符号链接循环

权限检查错误:

EACCES: 无访问权限

EROFS: 只读文件系统写入

资源分配错误:

ENOMEM: 内存不足

EMFILE: 打开文件过多

6. 返回结果

成功打开:

返回有效的文件描述符

file结构完成初始化

inode引用计数增加

打开失败:

返回负的错误码

清理已分配资源

释放inode引用

read() 调用流程

应用层调用 read()

进入系统调用 nx_read()

根据文件描述符获取 file 结构体

如果是普通文件:

调用对应文件系统的 read 方法读取数据

更新文件位置指针

如果是设备文件:

调用设备驱动的 read 操作读取数据

返回读取的字节数

write() 调用流程

应用层调用 write()

进入系统调用 nx_write()

根据文件描述符获取 file 结构体

如果是普通文件:

调用对应文件系统的 write 方法写入数据

更新文件位置指针和大小信息

如果是设备文件:

调用设备驱动的 write 操作写入数据

返回写入的字节数

close() 调用流程

应用层调用 close()

进入系统调用 nx_close()

根据文件描述符获取 file 结构体

如果是普通文件:

调用对应文件系统的 close 方法

释放 file 结构体

如果是设备文件:

调用设备驱动的 close 操作

释放相关资源

返回操作结果

关键数据结构

1. struct inode – 文件系统节点

inode 是文件系统最基本的数据结构,代表文件系统中的一个节点(文件、目录等):

struct inode {
    FAR struct inode *i_parent;   /* 父节点链接 */
    FAR struct inode *i_peer;     /* 同级节点链接 */  
    FAR struct inode *i_child;    /* 子节点链接 */
    atomic_short     i_crefs;     /* 引用计数 */
    uint16_t         i_flags;     /* 节点标志 */
    union inode_ops_u u;          /* 节点操作方法 */
    ino_t            i_ino;       /* inode序号 */
    FAR void        *i_private;   /* 私有数据 */ 
    char             i_name[1];   /* 节点名称 */
}

2. struct file_operations – 文件操作方法集

定义了文件的基本操作方法:

struct file_operations {
    int     (*open)(FAR struct file *filep);
    int     (*close)(FAR struct file *filep);
    ssize_t (*read)(FAR struct file *filep, FAR char *buffer, size_t buflen);
    ssize_t (*write)(FAR struct file *filep, FAR const char *buffer, size_t buflen);
    off_t   (*seek)(FAR struct file *filep, off_t offset, int whence);
    int     (*ioctl)(FAR struct file *filep, int cmd, unsigned long arg);
    int     (*mmap)(FAR struct file *filep, FAR struct mm_map_entry_s *map);
    // ...等其他操作
};

3. struct mountpt_operations – 挂载点操作方法集

定义了文件系统挂载点的操作方法:

struct mountpt_operations {
    // 打开文件
    int (*open)(FAR struct file *filep, FAR const char *relpath,
                int oflags, mode_t mode);
    
    // 基本文件操作                
    int     (*close)(FAR struct file *filep);
    ssize_t (*read)(FAR struct file *filep, FAR char *buffer, size_t buflen);
    ssize_t (*write)(FAR struct file *filep, FAR const char *buffer, size_t buflen);
    
    // 目录操作
    int (*opendir)(FAR struct inode *mountpt, FAR const char *relpath, 
                   FAR struct fs_dirent_s **dir);
    int (*closedir)(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir);
    int (*readdir)(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir,
                   FAR struct dirent *entry);
                   
    // 文件系统操作                
    int (*bind)(FAR struct inode *blkdriver, FAR const void *data,
                FAR void **handle);
    int (*unbind)(FAR void *handle, FAR struct inode **blkdriver,
                  unsigned int flags); 
    // ...等其他操作
};

4. struct fs_dirent_s – 目录项结构

表示打开的目录:

struct fs_dirent_s {
    FAR struct inode *fd_root;  /* 目录对应的inode节点 */
    FAR char *fd_path;          /* 目录路径 */
};

5. union inode_ops_u – inode操作union

将不同类型的操作方法统一到一个union中:

union inode_ops_u {
    FAR const struct file_operations *i_ops;     /* 驱动操作方法 */
    FAR const struct block_operations *i_bops;    /* 块设备操作方法 */
    FAR const struct mountpt_operations *i_mops;  /* 挂载点操作方法 */
};

这些数据结构协同工作,构成了完整的VFS层次结构:

inode 构成树状结构,表示文件系统的层次关系

file_operations/mountpt_operations 提供实际的操作方法

fs_dirent_s 管理目录遍历

inode_ops_u 统一了不同类型节点的操作接口

重要的宏定义

用于判断inode类型:

#define INODE_IS_PSEUDODIR(i)  // 伪目录
#define INODE_IS_DRIVER(i)     // 设备驱动
#define INODE_IS_BLOCK(i)      // 块设备
#define INODE_IS_MOUNTPT(i)    // 挂载点
#define INODE_IS_NAMEDSEM(i)   // 命名信号量
#define INODE_IS_SOCKET(i)     // Socket
#define INODE_IS_PIPE(i)       // 管道

用于设置inode类型:

#define INODE_SET_DRIVER(i)     // 设置为驱动
#define INODE_SET_BLOCK(i)      // 设置为块设备  
#define INODE_SET_MOUNTPT(i)    // 设置为挂载点
#define INODE_SET_NAMEDSEM(i)   // 设置为命名信号量

重要注意事项

所有文件系统操作都需要获取信号量保护

设备文件操作需要考虑并发访问

文件系统操作可能会失败,需要正确处理错误

注意资源释放和引用计数管理

主要代码实现

核心代码主要在以下位置:

fs/inode/ – inode 管理相关代码

fs/vfs/ – 虚拟文件系统层代码

fs/driver/ – 设备驱动接口代码

fs/fat/ – FAT文件系统实现

fs/procfs/ – procfs 文件系统实现

6. struct file – 打开文件实例

struct file 结构体代表一个已打开的文件实例,是系统中最基础的文件操作数据结构之一。每个打开的文件都对应一个 struct file 实例。

struct file {
    int               f_oflags;   /* 打开模式标志位 */
#ifdef CONFIG_FS_REFCOUNT  
    int               f_refs;     /* 引用计数 */
#endif
    off_t             f_pos;      /* 文件当前读写位置 */
    FAR struct inode *f_inode;    /* 关联的inode节点 */
    FAR void         *f_priv;     /* 私有数据指针 */
#ifdef CONFIG_FDSAN
    uint64_t          f_tag_fdsan; /* 文件描述符检查标签,初始为0 */
#endif
#ifdef CONFIG_FDCHECK
    uint8_t           f_tag_fdcheck; /* 文件描述符检查标签,初始为0 */
#endif
#if CONFIG_FS_BACKTRACE > 0
    FAR void         *f_backtrace[CONFIG_FS_BACKTRACE]; /* 文件打开时的栈回溯信息 */
#endif
#if CONFIG_FS_LOCK_BUCKET_SIZE > 0
    bool              locked;      /* 文件锁状态: false - 未锁, true - 已锁 */
#endif
};

主要字段说明:

f_oflags – 打开标志

O_RDONLY: 只读打开

O_WRONLY: 只写打开

O_RDWR: 读写打开

O_APPEND: 追加写入

O_CREAT: 如不存在则创建

O_EXCL: 如存在则失败

O_TRUNC: 打开时清空文件

f_refs – 引用计数

记录有多少个文件描述符指向这个文件实例

当计数为0时,这个文件实例可以被释放

f_pos – 文件位置

记录当前读写位置相对于文件开头的偏移量

seek() 操作会修改这个值

read()/write() 操作会根据这个值确定读写位置

f_inode – inode节点

指向这个文件对应的inode节点

通过inode可以找到具体的文件操作方法

文件系统相关的元数据都保存在inode中

f_priv – 私有数据

用于保存驱动程序的私有数据

对于设备文件,通常保存设备特定的状态信息

对于普通文件,可能保存文件系统特定的缓冲区等

文件描述符检查

f_tag_fdsan: 64位检查标签

f_tag_fdcheck: 8位检查标签

用于检测文件描述符泄露和非法使用

调试支持

f_backtrace[ ]: 保存打开文件时的调用栈

便于调试文件资源泄露问题

文件锁

locked: 记录文件是否被加锁

用于实现文件级别的并发控制

使用示例:

// 打开文件
struct file file;
int ret = file_open(&file, "/tmp/test.txt", O_RDWR|O_CREAT);

// 读写文件
char buf[100];
ssize_t n = file_read(&file, buf, sizeof(buf));
file_write(&file, "hello", 5);

// 修改位置
file_seek(&file, 0, SEEK_SET);  // 移到开头
file_seek(&file, 0, SEEK_END);  // 移到末尾

// 关闭文件
file_close(&file);

重要说明:

struct file 实例在打开文件时创建,关闭文件时释放

一个文件可以被多次打开,每次都会创建新的 struct file 实例

父子进程会共享文件表中的 struct file 实例

文件描述符本质是进程文件表中的索引,指向对应的 struct file 实例

标准输入(0)、标准输出(1)、标准错误(2)都对应各自的 struct file 实例

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容