2. Linux 文件系统基础:VFS(虚拟文件系统)架构详解

在 Linux 内核中,VFS(Virtual File System,虚拟文件系统) 是文件系统子系统的核心抽象层。它位于用户空间与具体文件系统实现(如 ext4、SquashFS、NFS)之间,为应用程序提供统一的文件操作接口(open、read、write、close 等),而无需关心底层存储介质或文件系统的具体布局。

核心思想:VFS 定义了一套通用的对象模型和操作函数集,所有具体的文件系统只需实现这些接口,即可无缝挂载到 Linux 内核中。

2.1 VFS 的四大核心对象

VFS 基于面向对象的设计思想,定义了四种关键的数据结构,它们共同构成了文件系统的运行时视图:

对象 数据结构 生命周期 核心作用
超级块 (Super Block) struct super_block 文件系统挂载时创建,卸载时销毁 存储整个文件系统的全局元数据(块大小、inode 总数、挂载选项等)
索引节点 (inode) struct inode 文件被访问时创建,引用计数归零后释放 存储文件的元数据(权限、大小、时间戳、数据块指针),不包含文件名
目录项 (dentry) struct dentry 路径解析时创建,由 dentry cache 管理 将文件名与对应的 inode 关联起来,构成目录树结构
文件对象 (file) struct file 进程调用 open() 时创建,close() 时销毁 代表一个已打开的文件实例,记录读写位置、访问模式、文件描述符标志
记忆口诀:超级块管全局,inode 管元数据,dentry 管名字,file 管打开状态。

2.2 四大对象的关系与协作

下图展示了 VFS 四大核心对象之间的协作关系,以及它们如何共同支撑一次典型的文件访问流程:

VFS 四大核心对象关系图 VFS 四大核心对象关系与协作 超级块 (super_block) 全局元数据:块大小、inode 总数、挂载标志 索引节点 (inode) 权限、大小、时间戳、数据块指针 目录项 (dentry) 文件名 → inode 映射,目录树结构 文件对象 (file) 读写位置、访问模式、文件描述符 用户进程 (fd) 通过文件描述符访问 file 对象 具体文件系统 (ext4/SquashFS) 实现 VFS 接口,管理磁盘数据 s_inodes 链表 s_root 根 dentry d_inode f_path.dentry f_inode 进程文件描述符表 i_sb → super_block 访问流程示例:用户读取 /etc/passwd ① 进程调用 open() → ② 内核路径解析,逐层查找 dentry → ③ dentry 找到对应 inode ④ inode 通过超级块定位到具体文件系统 → ⑤ 创建 file 对象,返回文件描述符给进程

2.3 超级块 (Super Block) 详解

超级块是文件系统的“控制块”,每个挂载的文件系统在内存中对应一个 struct super_block 实例。它包含了文件系统的全局配置和状态信息。

// 关键字段(简化版)
struct super_block {
    dev_t                s_dev;          // 块设备标识
    unsigned long        s_blocksize;    // 块大小(字节)
    unsigned char        s_blocksize_bits; // 块大小的位数
    loff_t               s_maxbytes;     // 文件最大大小
    struct file_system_type *s_type;     // 文件系统类型(ext4、squashfs 等)
    struct super_operations *s_op;       // 超级块操作函数集
    struct dentry        *s_root;        // 根目录的 dentry
    struct list_head     s_inodes;       // 所有 inode 的链表
    struct list_head     s_dentry_lru;   // dentry LRU 链表
    // ... 其他字段
};
注意:超级块中的 s_op 是一个指向 struct super_operations 的指针,该结构体定义了文件系统必须实现的回调函数,如 alloc_inode()、destroy_inode()、write_super() 等。这是 VFS 实现多态的关键机制。

2.4 索引节点 (inode) 详解

inode 是文件系统中最核心的数据结构之一,它存储了文件的所有元数据,唯独不包含文件名。每个文件(包括目录、设备文件、符号链接等)都有一个唯一的 inode 编号。

struct inode {
    umode_t              i_mode;         // 文件类型与权限
    uid_t                i_uid;          // 所有者用户 ID
    gid_t                i_gid;          // 所有者组 ID
    kdev_t               i_rdev;         // 设备文件的设备号
    loff_t               i_size;         // 文件大小(字节)
    struct timespec64    i_atime;        // 最后访问时间
    struct timespec64    i_mtime;        // 最后修改时间
    struct timespec64    i_ctime;        // 最后状态改变时间
    unsigned long        i_blocks;       // 文件占用的块数(512 字节为单位)
    struct inode_operations *i_op;       // inode 操作函数集
    struct super_block   *i_sb;          // 所属超级块
    struct address_space *i_mapping;     // 地址空间(页缓存)
    // ... 数据块指针(ext4 使用 ext4_inode_info 扩展)
};
关键点:inode 的 i_op 字段指向 struct inode_operations,该结构体定义了创建文件、创建目录、查找、链接、删除等操作。不同文件系统通过实现不同的 i_op 来提供差异化的语义。

2.5 目录项 (dentry) 详解

dentry 是 VFS 中用于路径解析的核心数据结构。它将用户看到的文件名(如 /home/user/file.txt)与内核中的 inode 关联起来。dentry 不直接对应磁盘上的数据结构,而是内存中的缓存对象。

struct dentry {
    struct qstr          d_name;         // 文件名(快速字符串)
    struct inode         *d_inode;       // 指向对应的 inode
    struct dentry        *d_parent;      // 父目录的 dentry
    struct list_head     d_subdirs;      // 子目录项链表
    struct list_head     d_child;        // 兄弟目录项链表
    struct dentry_operations *d_op;      // dentry 操作函数集
    unsigned int         d_flags;        // 标志位(DCACHE_*)
    // ... 哈希表、LRU 链表等
};
dentry 状态 说明 典型场景
DCACHE_DISCONNECTED dentry 未连接到全局目录树 文件系统挂载过程中的临时状态
DCACHE_REFERENCED dentry 最近被访问过 LRU 回收时优先保留
DCACHE_LRU_LIST dentry 在 LRU 链表中 内存压力下的回收候选
DCACHE_DENTRY_KILLED dentry 已被标记为死亡 等待最终释放
性能优化:dentry cache(dcache)是 VFS 最重要的缓存之一。当访问一个文件路径时,内核会从根 dentry 开始逐级查找,如果 dcache 命中,则直接返回 dentry,避免昂贵的磁盘 I/O。dcache 使用 LRU 算法进行回收。

2.6 文件对象 (file) 详解

文件对象代表一个已打开的文件实例。它与进程的文件描述符一一对应,记录了进程对文件的操作状态。同一个文件可以被多个进程同时打开,每个进程拥有独立的 file 对象。

struct file {
    struct path          f_path;         // 包含 dentry 和 mount 信息
    struct inode         *f_inode;       // 指向文件对应的 inode
    const struct file_operations *f_op;  // 文件操作函数集
    loff_t               f_pos;          // 当前读写位置(偏移量)
    unsigned int         f_flags;        // 打开标志(O_RDONLY, O_WRONLY 等)
    fmode_t              f_mode;         // 访问模式(读/写/执行)
    struct file_ra_state f_ra;           // 预读状态
    // ... 私有数据、引用计数等
};
关键点:f_op 指向 struct file_operations,这是 VFS 中最重要的操作函数集之一,包含了 read()、write()、mmap()、ioctl() 等接口。每个文件系统或设备驱动都可以提供自己的 file_operations 实现。

2.7 四大对象的生命周期与协作流程

下面通过一个典型的文件打开和读取流程,展示四大对象如何协同工作:

  1. 挂载阶段:内核读取磁盘上的文件系统元数据,创建 super_block 对象,并初始化根 dentry (s_root)。
  2. 路径解析:用户调用 open("/etc/passwd", O_RDONLY),内核从根 dentry 开始,逐级查找路径中的每个分量,在 dcache 中查找或创建新的 dentry。
  3. inode 加载:当找到目标文件的 dentry 后,如果 d_inode 为空,则调用具体文件系统的 lookup() 方法,从磁盘读取 inode 信息并填充到内存 inode 中。
  4. file 对象创建:内核分配一个新的 file 对象,设置 f_path.dentry 指向目标 dentry,f_inode 指向对应的 inode,f_op 从 inode 的默认操作中获取。
  5. 文件描述符绑定:将 file 对象指针存入进程的文件描述符表,返回文件描述符编号给用户空间。
  6. 读写操作:用户调用 read(fd, buf, count),内核通过 fd 找到 file 对象,调用 f_op->read(),最终通过 inode 的 i_mapping 访问页缓存或磁盘数据。
  7. 关闭操作:用户调用 close(fd),内核释放 file 对象,递减 inode 和 dentry 的引用计数。当引用计数归零时,inode 和 dentry 可能被回收。
文件打开流程时序图 文件打开流程:四大对象协作时序 用户进程 VFS 层 dcache 具体文件系统 open() 路径解析 查找 dentry lookup() 创建 dentry 创建 file 返回 fd 流程说明 ① 用户进程调用 open() 进入 VFS 层 ② VFS 进行路径解析,在 dcache 中逐级查找 dentry ③ 如果 dentry 未命中,调用具体文件系统的 lookup() 从磁盘加载 inode ④ VFS 创建 file 对象并绑定 dentry/inode,返回文件描述符给用户进程

2.8 操作函数集:VFS 多态的实现机制

VFS 通过四个操作函数集实现多态,每个具体文件系统只需实现这些函数指针:

操作函数集 所属对象 关键函数 作用
super_operations super_block alloc_inode, destroy_inode, write_super, sync_fs 管理 inode 生命周期、同步元数据
inode_operations inode lookup, create, mkdir, unlink, symlink 目录和文件创建、删除、链接等操作
dentry_operations dentry d_hash, d_compare, d_delete, d_release dentry 哈希、比较、释放等操作
file_operations file read, write, mmap, ioctl, open, release 文件读写、内存映射、设备控制等
定制文件系统的入口:当我们要实现一个新的文件系统(如自定义的 SquashFS 变体),核心工作就是实现上述四个操作函数集,并通过 register_filesystem() 注册到内核。VFS 会自动将我们的操作函数集挂接到对应的对象上。

2.9 本章小结

VFS 是 Linux 文件系统架构的基石,它通过四个核心对象(超级块、inode、dentry、file)和四个操作函数集,实现了对多种文件系统的统一抽象。理解这些对象的关系与生命周期,是后续学习 ext4 和 SquashFS 定制的基础。

对象 类比 关键字段 操作函数集
super_block 文件系统的“身份证” s_blocksize, s_root, s_op super_operations
inode 文件的“户口本” i_mode, i_size, i_op, i_sb inode_operations
dentry 文件名的“门牌号” d_name, d_inode, d_parent dentry_operations
file 文件的“借阅记录” f_pos, f_flags, f_op, f_inode file_operations