VFS (虚拟文件系统) 核心架构

VFS (虚拟文件系统) 核心架构

前言

"一切皆文件"是 Linux 哲学中最著名的格言之一。无论是普通的文本文件、目录,还是设备、套接字、管道,甚至是内核的数据结构(如 /proc/sys),在用户空间看来,它们都是文件。

这种统一的视角背后,最大的功臣就是 VFS (Virtual File System,虚拟文件系统) 。它作为一层抽象层,向上为用户空间提供统一的系统调用接口(如 open, read, write, mount),向下兼容几十种不同的文件系统(如 ext4, xfs, nfs, procfs)。

本文将带你深入 Linux 内核源码,抽丝剥茧,解析 VFS 的核心架构、四大核心对象以及它们之间错综复杂的爱恨纠葛。

1. VFS 整体架构全景图

VFS 位于用户空间和具体文件系统之间,起到承上启下的作用。
内核空间 (Kernel Space)
支持的文件系统类型
open/read/write
调用具体实现
VFS 核心组件
dentry cache

目录项缓存
inode cache

索引节点缓存
Mount Process

挂载管理
用户空间应用程序
系统调用接口
VFS 层 (Virtual File System)
具体文件系统 (Specific FS)
ext4
xfs
nfs
proc
tmpfs
通用块设备层
设备驱动
磁盘硬件
网络协议栈

2. 四大核心对象 (The Big Four)

Linux VFS 将文件系统的操作抽象为四个主要对象,它们是理解 VFS 的基石。

对象 英文名 结构体 描述 生命周期
超级块 Super Block struct super_block 代表一个已挂载的文件系统实例,存储该文件系统的元数据。 挂载时创建,卸载时销毁
索引节点 Inode struct inode 代表文件系统中一个特定的文件(数据),包含文件的元信息(权限、大小、时间等),但不包含文件名。 只要文件被访问就存在内存中,可被缓存
目录项 Dentry struct dentry 代表路径中的一个组成部分(目录或文件名),用于将文件名解析为 inode。 缓存在内存中 (dcache),LRU 淘汰
文件对象 File struct file 代表进程打开的一个文件上下文,包含文件偏移量、打开模式等。 open() 创建,close() 销毁

2.1 核心对象关系图

这四个对象之间环环相扣:
生产
管理
根节点
映射 (硬链接)
父子关系
打开
关联
1 1 1 * * * * * * * * 1 1 1 1 1 1 1 file_system_type
+name: char
+mount()
+kill_sb()
super_block
+s_type: file_system_type
+s_op: super_operations
+s_root: dentry
+s_inodes: list_head
inode
+i_sb: super_block
+i_op: inode_operations
+i_fop: file_operations
+i_ino: unsigned long
+i_mapping: address_space
dentry
+d_inode: inode
+d_parent: dentry
+d_name: qstr
+d_op: dentry_operations
+d_sb: super_block
file
+f_path: path
+f_inode: inode
+f_op: file_operations
+f_pos: loff_t

3. 核心数据结构源码深度解析

让我们深入内核代码(基于较新版本内核),看看这些结构体长什么样。

3.1 super_block (文件系统的控制中心)

存储文件系统的元数据,比如块大小、魔数、根目录等。

c 复制代码
struct super_block {
    struct list_head    s_list;          // 全局超级块链表
    dev_t               s_dev;           // 设备标识符
    unsigned long       s_blocksize;     // 块大小
    struct file_system_type *s_type;     // 所属文件系统类型
    const struct super_operations *s_op; // 超级块操作函数集合
    
    struct dentry       *s_root;         // 文件系统根目录的 dentry
    struct list_head    s_mounts;        // 挂载点链表
    struct list_head    s_inodes;        // 该文件系统所有 inode 的链表
    
    // ... (省略其他字段)
};

3.2 inode (文件的元神)

注意:Linux 中文件名和文件数据是分离的。inode 存储的是"文件数据在哪里"以及"文件是谁的",唯独不包含"文件叫什么"。

c 复制代码
struct inode {
    umode_t             i_mode;          // 权限和类型 (如 rwxr-x---)
    kuid_t              i_uid;           // 用户ID
    kgid_t              i_gid;           // 组ID
    unsigned long       i_ino;           // inode 号 (唯一标识)
    
    const struct inode_operations *i_op; // inode 操作 (如 mkdir, unlink)
    const struct file_operations *i_fop; // 默认的文件操作 (open 之后赋值给 file->f_op)
    struct super_block  *i_sb;           // 所属超级块
    
    struct address_space *i_mapping;     // 【重要】指向 address_space,用于页缓存
    
    loff_t              i_size;          // 文件大小
    // ...
};

3.3 dentry (路径的导航员)

VFS 实际上并不直接处理字符串路径。为了提高查找性能,VFS 使用 dentry 将字符串路径(如 /home/user)转换为 inode。dentry 形成了我们看到的目录树结构。

c 复制代码
struct dentry {
    struct dentry *d_parent;             // 指向父目录的 dentry
    struct qstr d_name;                  // 文件名
    struct inode *d_inode;               // 关联的 inode (如果是 NULL 则为负向 dentry)
    
    const struct dentry_operations *d_op;// dentry 操作
    struct super_block *d_sb;            // 所属超级块
    
    struct list_head d_subdirs;          // 子目录项链表
    // ...
};

性能关键 :访问文件时,内核不需要每次都从磁盘读取目录结构,而是首先在 dcache (Dentry Cache) 中查找。这是 Linux 文件系统高性能的关键之一。

3.4 file (进程视角的文件)

这是我们在应用程序中 open() 得到句柄对应的内核对象。它是进程级别的,不同进程打开同一个文件会有不同的 struct file,但它们指向同一个 inode

c 复制代码
struct file {
    struct path         f_path;          // 包含 {mnt, dentry}
    struct inode        *f_inode;        // 指向 inode (缓存,方便访问)
    const struct file_operations *f_op;  // 文件操作函数表 (read, write...)
    
    loff_t              f_pos;           // 当前读写偏移量 (Cursor)
    unsigned int        f_flags;         // 打开标志 (O_RDONLY, O_NONBLOCK...)
    fmode_t             f_mode;          // 读写模式
    
    struct address_space *f_mapping;     // 再次指向页缓存
    // ...
};

4. 关键操作流程图解

4.1 open() 系统调用流程

当你调用 fd = open("/home/test.txt", O_RDONLY) 时,内核发生了什么?
具体FS (ext4) 路径查找(path_lookup) VFS核心 用户进程 具体FS (ext4) 路径查找(path_lookup) VFS核心 用户进程 opt [Cache Miss] loop [每一级目录] sys_open("/home/test.txt") path_openat() 解析路径 查找 dcache inode_ops->>lookup() 从磁盘读 inode 创建 dentry 返回目标 dentry 和 inode alloc_file() 创建 struct file 关联 file->>f_op = inode->>i_fop file_ops->>open() (可选) 返回 fd (索引指向 file)

4.2 read() 系统调用流程

拿到 fd 后,调用 read(fd, buf, len)
磁盘驱动 PageCache/MM file_operations sys_read 用户进程 磁盘驱动 PageCache/MM file_operations sys_read 用户进程 alt [Page Cache Hit] [Page Cache Miss] read(fd) fd 获取 struct file file->>f_op->>read_iter() generic_file_read_iter() 数据 copy_to_user a_ops->>readpage() 提交 BIO 请求 DMA 数据传输完成 数据 copy_to_user

5. 内存中的对象关系实例 (Instance View)

假设进程 A 打开了 /mnt/data/file.txt。内存中的对象关系如下:

text 复制代码
  进程 A Task Struct
       │
       ▼
  文件描述符表 (fd array)
  ┌───┬───┬───┐
  │ 0 │ 1 │ 3 │ (fd=3)
  └───┴───┴─┬─┘
            │
            ▼
      struct file (存活于 RAM)
      ┌────────────────────────┐
      │ f_pos: 0               │ <--- 每个进程独立
      │ f_mode: O_RDONLY       │
      │ f_path.dentry: ptr ────┼──┐
      │ f_path.mnt: ptr ───────┼┐ │
      │ f_op: ext4_file_ops    ││ │
      └────────────────────────┘│ │
                                │ │
           ┌────────────────────┘ │
           │                      │
           ▼                      ▼
  struct vfsmount        struct dentry (缓存于 dcache)
  ┌─────────────┐        ┌───────────────────────────┐
  │ mnt_root    │        │ d_name: "file.txt"        │
  │ mnt_sb      │        │ d_parent: ptr to "data"   │
  └─────────────┘        │ d_inode: ptr ─────────────┼──┐
                         └───────────────────────────┘  │
                                                        │
                                                        ▼
                                             struct inode (缓存于 RAM)
                                             ┌──────────────────────────┐
                                             │ i_ino: 1024              │
                                             │ i_size: 4096             │
                                             │ i_bytes: 4096            │
                                             │ i_sb: ptr to super_block │
                                             │ i_ops: ext4_inode_ops    │
                                             │ f_ops: ext4_file_ops     │
                                             │ i_mapping: address_space │
                                             └──────────────────────────┘

6. 总结

Linux VFS 的精妙之处在于它成功地将"接口"与"实现"分离。

  • 对于开发者:只需要掌握一套标准的文件 I/O API。
  • 对于内核 :通过 super_block, inode, dentry, file 四大对象构建了统一的模型。
  • 对于性能 :通过 dcacheinode cache 极大地掩盖了底层磁盘慢速的特性。

理解 VFS 不仅有助于理解 Linux 的文件系统,更是理解 Linux 核心设计思想(如面向对象风格的 C 语言编程)的绝佳案例。

相关推荐
Y1rong1 小时前
STM32之串口(二)
stm32·单片机·嵌入式硬件
Y1rong1 小时前
STM32之串口(一)
网络·stm32·嵌入式硬件
UP_Continue1 小时前
Linux--OS和认识进程
linux·运维·服务器
旭意2 小时前
数据结构-红黑树和set
数据结构·c++·算法·蓝桥杯
IT摆渡者2 小时前
Rocky Linux 10.1中找不到传统的 /etc/sysconfig/network-scripts 目录是正常现象。
linux·运维·服务器·网络·经验分享
无心水2 小时前
8、吃透Go语言container包:链表(List)与环(Ring)的核心原理+避坑指南
java·开发语言·链表·微服务·架构·golang·list
想睡觉的树2 小时前
解决keil5编译慢的问题-亲测有效-飞一般的感觉
c语言·stm32·嵌入式硬件
无小道2 小时前
基于epoll的单进程Reactor服务器
运维·服务器·c++·网络编程·reactor·epoll
__万波__2 小时前
STM32L475串口打印改为阻塞式打印兼DMA, 两种打印方式实时切换
stm32·单片机·嵌入式硬件