2. VFS数据结构全景:四大核心结构及其关系

虚拟文件系统(VFS)是Linux内核中文件系统的抽象层,它通过四个核心数据结构——超级块(super_block)、索引节点(inode)、目录项(dentry)和文件对象(file)——实现了对不同文件系统的统一访问。这四个结构分别从不同粒度描述文件系统的元数据、文件属性、路径解析和打开文件状态,构成了VFS的骨架。

2.1 四大核心结构概述

下表从职责、生命周期、关键字段和所属层次四个维度对比了四个核心结构:

结构 职责 生命周期 关键字段 层次
super_block 描述已挂载文件系统的全局元数据 挂载时创建,卸载时销毁 s_fs_info, s_root, s_blocksize, s_op 文件系统级
inode 描述文件或目录的物理元数据(权限、大小、位置) 首次访问时创建,引用计数归零后回收 i_mode, i_uid, i_size, i_ino, i_op, i_fop 文件级
dentry 描述路径名与inode的映射关系 路径解析时创建,缓存保留(dcache) d_name, d_parent, d_inode, d_op 路径级
file 描述进程打开的文件实例(读写位置、标志) open()时创建,close()时销毁 f_path, f_pos, f_flags, f_op 进程级
关键理解:四个结构分别对应文件系统的四个抽象层次——“文件系统本身”(super_block)、“文件实体”(inode)、“路径名”(dentry)和“打开的文件描述”(file)。这种分层设计使得VFS可以同时支持多种底层文件系统,而用户态进程只需通过文件描述符与file结构交互。

2.2 超级块(super_block)——文件系统的“户口本”

超级块是VFS中最高层级的结构,每个已挂载的文件系统对应一个super_block实例。它记录了文件系统的全局信息,包括块大小、最大文件大小、操作函数表等。

// include/linux/fs.h (简化核心字段)
struct super_block {
    struct list_head    s_list;          // 全局超级块链表
    dev_t               s_dev;           // 块设备标识
    unsigned long       s_blocksize;     // 块大小(字节)
    loff_t              s_maxbytes;      // 最大文件大小
    struct file_system_type *s_type;     // 文件系统类型
    struct dentry       *s_root;         // 根目录的dentry
    struct super_operations *s_op;       // 超级块操作函数表
    struct list_head    s_inodes;        // 该文件系统所有inode链表
    struct list_head    s_dentry_lru;    // 未使用的dentry LRU链表
    void                *s_fs_info;      // 指向具体文件系统私有数据
};

super_operations 是超级块的操作函数表,定义了文件系统层面的回调:

  • alloc_inode() / destroy_inode() — 分配/销毁inode
  • write_super() / sync_fs() — 同步超级块/文件系统
  • statfs() — 获取文件系统统计信息
  • remount_fs() — 重新挂载

2.3 索引节点(inode)——文件的“身份证”

inode存储了文件或目录的所有元数据,但不包含文件名。文件名由dentry管理。每个文件(包括目录、设备文件等)在VFS中都有唯一的inode。

struct inode {
    umode_t             i_mode;          // 文件类型与权限
    kuid_t              i_uid;           // 所有者UID
    kgid_t              i_gid;           // 所属组GID
    loff_t              i_size;          // 文件大小(字节)
    struct timespec64   i_atime;         // 最后访问时间
    struct timespec64   i_mtime;         // 最后修改时间
    struct timespec64   i_ctime;         // 状态改变时间
    unsigned long       i_ino;           // inode编号(文件系统内唯一)
    struct super_block  *i_sb;           // 所属超级块
    struct address_space *i_mapping;     // 地址空间(页缓存)
    const struct inode_operations    *i_op;   // inode操作
    const struct file_operations     *i_fop;  // 默认文件操作
    struct list_head    i_sb_list;       // 超级块inode链表节点
    union {
        struct ext2_inode_info     ext2_i;    // 具体文件系统私有数据
        struct ext4_inode_info     ext4_i;
        // ... 其他文件系统
    };
};
设计要点:inode中的i_fop字段为目录和常规文件提供了默认的文件操作函数表。当进程通过open()打开文件时,VFS会将inode的i_fop赋值给file结构的f_op,从而实现文件操作的多态分发。

2.4 目录项(dentry)——路径解析的“缓存加速器”

dentry是VFS中最活跃的结构,它负责将路径名组件(如/usr/bin/gcc中的bin)映射到对应的inode。dentry不直接对应磁盘上的数据结构,而是VFS在内存中构建的路径缓存(dcache)。

struct dentry {
    struct qstr         d_name;          // 目录项名称(快速比较)
    struct inode        *d_inode;        // 指向关联的inode(可为NULL)
    struct dentry       *d_parent;       // 父目录项
    struct list_head    d_child;         // 父目录的子目录项链表
    struct list_head    d_subdirs;       // 子目录项链表(仅目录)
    const struct dentry_operations *d_op; // 目录项操作
    unsigned int        d_flags;         // 标志位
    struct hlist_bl_node d_hash;         // dcache哈希表节点
};

dentry的生命周期状态:

  • 被使用(used):d_inode非空,且d_count > 0,被某个file或进程引用
  • 未使用(unused):d_inode非空,但d_count == 0,保留在LRU链表中等待回收
  • 负目录项(negative):d_inode为NULL,表示路径名不存在,用于缓存“查找失败”结果
性能关键:dcache是VFS性能的核心。每次路径解析(如/home/user/file.txt)都需要从根目录开始逐级查找dentry。dcache通过哈希表和LRU链表加速查找,并缓存负目录项避免重复的磁盘I/O。如果dcache命中率低,系统调用如stat()、open()的性能会急剧下降。

2.5 文件对象(file)——进程与文件的“会话”

file结构代表一个打开的文件实例,是进程与文件系统交互的直接接口。每个open()系统调用都会创建一个新的file结构,多个进程可以打开同一个文件(共享同一个inode),但各自拥有独立的file实例。

struct file {
    struct path         f_path;          // 包含dentry和挂载点信息
    struct inode        *f_inode;        // 指向文件inode(快捷方式)
    const struct file_operations *f_op;  // 文件操作函数表
    loff_t              f_pos;           // 当前读写位置
    unsigned int        f_flags;         // 打开标志(O_RDONLY, O_SYNC等)
    fmode_t             f_mode;          // 读写模式(FMODE_READ, FMODE_WRITE)
    struct address_space *f_mapping;     // 地址空间(与inode共享)
    void                *private_data;   // 具体文件系统私有数据
};

file_operations 是VFS多态性的核心体现,每个文件系统可以自定义这些操作:

操作 对应系统调用 说明
read() read() / pread64() 从文件读取数据
write() write() / pwrite64() 向文件写入数据
llseek() lseek() 修改文件读写位置
mmap() mmap() 内存映射文件
unlocked_ioctl() ioctl() 设备控制命令
poll() poll() / select() I/O多路复用

2.6 四大结构的关系全景

下图展示了四个核心结构之间的引用关系和协作流程:

VFS四大核心结构关系全景图 super_block s_root → dentry (根目录) s_inodes → inode链表 inode i_sb → super_block i_fop → file_operations dentry d_inode → inode d_parent → 父dentry file f_path.dentry → dentry f_inode → inode 进程(用户态) s_inodes链表 d_inode f_path.dentry f_inode(快捷引用) 文件描述符 → file s_root 核心协作流程: ① 进程通过文件描述符找到file结构 → ② file通过f_path.dentry获取dentry → ③ dentry通过d_inode获取inode ④ inode通过i_sb关联到super_block → ⑤ super_block通过s_root管理整个文件系统的目录树

2.7 结构间的协作流程

以一次典型的open("/home/user/doc.txt", O_RDONLY)系统调用为例,展示四个结构的协作:

  1. 路径解析:VFS从当前进程的根目录dentry(或当前工作目录dentry)开始,逐级查找路径组件home、user、doc.txt。每个组件在dcache中查找对应的dentry,若未命中则调用具体文件系统的lookup()从磁盘读取。
  2. 获取inode:最后一个组件doc.txt的dentry通过d_inode指针获取文件的inode。inode中包含了文件的权限、大小、磁盘块位置等元数据。
  3. 创建file结构:VFS分配一个新的file结构,将f_path.dentry指向目标dentry,f_inode指向目标inode,并将inode的i_fop赋值给f_op。
  4. 关联进程:将新创建的file结构指针存入当前进程的文件描述符表(fdtable),返回文件描述符整数给用户态。
多态实现原理:VFS通过函数指针表(super_operations、inode_operations、file_operations)实现多态。每个具体文件系统(如ext4、btrfs、tmpfs)在初始化时注册自己的操作函数。当VFS调用file->f_op->read()时,实际执行的是ext4_read()或btrfs_read(),对上层完全透明。

2.8 驱动适配视角:如何利用四大结构

对于文件系统驱动开发者,理解四大结构的关系是编写新文件系统的前提。以下是一个伪代码示例,展示如何注册一个简单的内存文件系统:

// 伪代码:注册一个简单的内存文件系统
static const struct super_operations myfs_super_ops = {
    .alloc_inode    = myfs_alloc_inode,
    .destroy_inode  = myfs_destroy_inode,
    .statfs         = myfs_statfs,
};

static const struct inode_operations myfs_inode_ops = {
    .lookup         = myfs_lookup,
    .create         = myfs_create,
    .mkdir          = myfs_mkdir,
};

static const struct file_operations myfs_file_ops = {
    .read           = myfs_read,
    .write          = myfs_write,
    .llseek         = generic_file_llseek,
};

// 填充超级块
static int myfs_fill_super(struct super_block *sb, void *data, int silent)
{
    struct inode *root_inode;
    struct dentry *root_dentry;

    sb->s_op = &myfs_super_ops;
    sb->s_blocksize = PAGE_SIZE;

    // 创建根目录inode
    root_inode = new_inode(sb);
    root_inode->i_mode = S_IFDIR | 0755;
    root_inode->i_op = &myfs_inode_ops;
    root_inode->i_fop = &simple_dir_operations;

    // 创建根目录dentry
    root_dentry = d_make_root(root_inode);
    sb->s_root = root_dentry;

    return 0;
}
驱动适配要点:
  • super_block:必须实现alloc_inode和destroy_inode,用于管理inode的内存分配
  • inode:通过i_op提供目录操作(lookup、create),通过i_fop提供默认文件操作
  • dentry:通常不需要自定义dentry操作,但可以通过d_op实现哈希比较优化
  • file:通过f_op提供读写、定位等操作,是用户态与文件系统交互的直接接口

2.9 总结:四大结构的层次与关系

四个核心结构构成了VFS的四层抽象模型:

层次 结构 核心作用 生命周期管理
文件系统层 super_block 管理整个文件系统的全局状态 挂载/卸载
文件元数据层 inode 存储文件属性与磁盘映射 引用计数
路径解析层 dentry 缓存路径名到inode的映射 dcache LRU
进程接口层 file 提供进程与文件的交互会话 open/close