本页目录

VFS — 虚拟文件系统层

覆盖: VFS 对象模型 (super_block/inode/dentry/file) → 系统调用路径 (open/read/write/close) → file_operations → RCU path walk → mount 机制 内核版本: 2.6 ~ 6.x

概述

VFS (Virtual File System / Virtual Filesystem Switch) 是 Linux "一切皆文件" 的基石。它定义了一组抽象对象和操作接口,让 ext4、NFS、procfs 等完全不同的文件系统可以统一暴露给用户空间。一个 read() 系统调用不需要知道底层是本地磁盘还是网络挂载——VFS 层负责路由到正确的文件系统实现。

VFS 的核心设计哲学是面向对象的 C 语言实现⁠:每个 VFS 对象内嵌一个 *_operations 函数表,文件系统通过填充这些函数表来"实现接口"。


VFS 四大对象

super_block:  "这个文件系统是什么?"  — 一个挂载点对应一个 super_block
inode:        "这个文件是谁?"        — 每个文件/目录一个 (存于磁盘或内存)
dentry:       "这个路径名指向谁?"    — 路径名到 inode 的缓存映射
file:         "这个打开的文件在哪里?" — 每次 open() 创建一个, 含当前偏移

super_block

// include/linux/fs.h
struct super_block {
    unsigned long           s_blocksize;
    unsigned char           s_blocksize_bits;
    unsigned long long      s_maxbytes;        // 最大文件大小
    struct file_system_type *s_type;            // 文件系统类型 (ext4, nfs, ...)
    const struct super_operations *s_op;        // 操作函数表
    struct dentry           *s_root;            // 根目录 dentry
    struct list_head        s_inodes;           // 所有 inode 的链表
    struct list_head        s_dentry_lru;       // 未使用的 dentry (LRU)
    void                    *s_fs_info;         // 文件系统私有数据
};

inode

struct inode {
    umode_t                 i_mode;             // 文件类型和权限
    kuid_t                  i_uid; i_gid;
    loff_t                  i_size;             // 文件大小
    struct timespec64       i_atime, i_mtime, i_ctime;

    const struct inode_operations *i_op;        // inode 操作
    const struct file_operations  *i_fop;       // 默认文件操作 (open 时复制的模板)
    struct super_block      *i_sb;
    struct address_space    *i_mapping;         // page cache (核心!)

    union {
        struct pipe_inode_info  *i_pipe;        // 管道
        struct block_device     *i_bdev;        // 块设备
        struct cdev             *i_cdev;        // 字符设备
    };

    unsigned long           i_ino;              // inode 号 (文件系统内唯一)
};

dentry — 路径名缓存

struct dentry {
    struct inode            *d_inode;           // 关联的 inode (可以为 NULL: negative)
    struct dentry           *d_parent;          // 父目录
    struct qstr             d_name;             // 本分量名字
    struct list_head        d_child;            // 父目录的子目录链表
    struct list_head        d_subdirs;          // 子目录链表头
    struct hlist_bl_node    d_hash;             // dcache hash 表
    const struct dentry_operations *d_op;
};

Negative dentry: d_inode == NULL。表示"这个路径名确认不存在"。常见于查找未找到的文件——如果每次 open("/tmp/noexist") 都要重新遍历,会浪费大量 IO。

file — 打开的文件描述

struct file {
    struct path             f_path;             // dentry + vfsmount
    const struct file_operations *f_op;
    atomic_long_t           f_count;            // 引用计数
    unsigned int            f_flags;            // O_RDONLY, O_NONBLOCK, ...
    fmode_t                 f_mode;             // FMODE_READ, FMODE_WRITE
    loff_t                  f_pos;              // 当前读写位置
    struct address_space    *f_mapping;         // page cache
    void                    *private_data;      // 文件系统私有数据
};

系统调用路径

open(): 从路径名到 file

flowchart TD
    SYSCALL["open() 系统调用"] --> GETNAME["getname()<br/>从用户空间复制路径名"]

    GETNAME --> GETFD["get_unused_fd_flags()<br/>分配 fd number"]

    GETFD --> DO_OPEN["do_filp_open()<br/>核心路径解析"]
    DO_OPEN --> PATH["path_openat()"]
    PATH --> WALK["link_path_walk()<br/>逐分量遍历路径"]

    WALK --> LOOKUP{"lookup_fast()<br/>查 dcache (RCU walk)"}
    LOOKUP -->|"命中"| DENTRY["返回 dentry<br/>无锁, 极快 ✅"]
    LOOKUP -->|"未命中"| SLOW["lookup_slow()<br/>调用 i_op->lookup()<br/>从磁盘/inode 块中找<br/>分配新 dentry → 加入 dcache"]

    DENTRY --> LAST{"最后分量?"}
    SLOW --> LAST

    LAST -->|"是"| DO_LAST["do_last()"]
    LAST -->|"否, 继续下一分量"| LOOKUP

    DO_LAST --> CASE{"文件状态?"}
    CASE -->|"已存在"| VFS_OPEN["vfs_open()<br/>→ f_op->open()"]
    CASE -->|"O_CREAT"| VFS_CREATE["vfs_create()<br/>→ i_op->create()"]
    CASE -->|"O_TMPFILE"| TMPFILE["创建无名临时文件"]

    VFS_OPEN --> INSTALL["fd_install()<br/>file 安装到 fdtable"]
    VFS_CREATE --> INSTALL
    TMPFILE --> INSTALL

    INSTALL --> RET["返回 fd ✅"]

    classDef sys call fill:#e3f2fd,stroke:#1565c0
    classDef step fill:#f3e5f5,stroke:#7b1fa2
    classDef decision fill:#fff3e0,stroke:#ef6c00
    classDef done fill:#e8f5e9,stroke:#2e7d32
    class SYSCALL syscall
    class GETNAME,GETFD,DO_OPEN,PATH,WALK,DENTRY,SLOW,VFS_OPEN,VFS_CREATE,TMPFILE,INSTALL step
    class LOOKUP,LAST,CASE decision
    class RET done

RCU Path Walk — VFS 的性能秘密

传统路径解析 (2.6 之前):
  每个分量都要拿锁 → 高竞争 → 多核性能差

RCU path walk (2.6.38+):
  1. 在 RCU read lock 下遍历 dcache
  2. 只需要读 dentry 和 inode (不修改)
  3. 如果所有分量都在 dcache 中 → 全程无锁
  4. 如果遇到不在 dcache 的分量 → 退化为 ref-walk (加锁)

性能: 多核系统上 open() 吞吐量提升数倍
实现: fs/namei.c 的 link_path_walk() 中两套逻辑 (RCU/REF)

read(): 从 page cache 读

// fs/read_write.c
SYSCALL_DEFINE3(read, ...)ksys_read()vfs_read()

vfs_read(file, buf, count, pos)
  ├─ 检查权限 (FMODE_READ)
  ├─ 调用 f_op->read() 或 f_op->read_iter()
  │   └─ 对于常规文件: generic_file_read_iter()
  │       └─ filemap_read()
  │           ├─ 对每个 page-aligned 范围:
  │           │   ├─ filemap_get_page()find_get_page()
  │           │   │   ├─ page cache 命中 → 直接返回 (无 IO)
  │           │   │   └─ page cache miss → page_cache_sync_readahead()
  │           │   │       └─ 从块层读取 → 页面插入 page cache
  │           │   └─ copy_page_to_iter()copy_to_user()
  │           └─ 更新 f_pos
  └─ 返回已读字节数

write(): 写 page cache + 回写

vfs_write(file, buf, count, pos)
  └─ f_op->write_iter()generic_file_write_iter()
      └─ __generic_file_write_iter()
          ├─ filemap_write()
          │   ├─ 对每个 page-aligned 范围:
          │   │   ├─ filemap_get_page() → 找到或创建 page cache 页
          │   │   ├─ copy_page_from_iter()copy_from_user()
          │   │   └─ 标记页面 dirty (SetPageDirty)
          │   └─ 更新 f_pos
          │
          └─ 写回的时机?
              不是这里! generic_file_write_iter 只写到 page cache
              writeback 由后台 flusher 线程异步完成
              (或者 fsync/fdatasync 触发同步写回)

file_operations

// include/linux/fs.h
struct file_operations {
    struct module *owner;
    loff_t (*llseek)(struct file *, loff_t, int);
    ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter)(struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter)(struct kiocb *, struct iov_iter *);
    int (*mmap)(struct file *, struct vm_area_struct *);
    unsigned long (*mmap_supported_flags);
    int (*open)(struct inode *, struct file *);
    int (*release)(struct inode *, struct file *);  // close() → 最后一个引用释放
    int (*fsync)(struct file *, loff_t, loff_t, int datasync);
    int (*fasync)(int, struct file *, int);
    __poll_t (*poll)(struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
    long (*compat_ioctl)(struct file *, unsigned int, unsigned long);
};

文件系统可以只填充一部分——VFS 会补齐默认实现。例如没有 read_iter 但有 read,VFS 会自动包装。


Mount 机制

// fs/namespace.c
// mount 将一个文件系统附着到 VFS 树的一个点:

struct vfsmount {
    struct mount            *mnt_parent;   // 挂载点父级
    struct dentry           *mnt_root;     // 挂载的根目录
    struct super_block      *mnt_sb;       // 文件系统
    struct list_head        mnt_mounts;    // 挂在此 mount 上的子 mount
    struct list_head        mnt_child;     // 父 mount 的子节点
};

// mount /dev/sda1 /mnt:
//   1. 读 /dev/sda1 的 superblock → 创建 super_block
//   2. 创建 vfsmount → mnt_root = super_block->s_root
//   3. 挂到 /mnt 上 → /mnt 的 dentry 变为 mountpoint
//   4. 路径遍历遇到 mountpoint → 切换到 vfsmount->mnt_root 继续

// namespace:
//   每个进程属于一个 mount namespace
//   CLONE_NEWNS → 新 mount namespace (容器的基础)

调试

# VFS 统计
cat /proc/sys/fs/dentry-state
# nr_dentry, nr_unused, age_limit

# 当前挂载
cat /proc/mounts
findmnt  # 树状显示

# 文件系统类型
cat /proc/filesystems

# 追踪 open/read/write
strace -e trace=open,read,write cat /etc/hostname

参考与延伸

  • 内核文档⁠: Documentation/filesystems/vfs.rst, Documentation/filesystems/path-lookup.rst
  • LWN:
    • "Pathname lookup in Linux" (lwn.net/Articles/649115/)
    • "RCU-walk: faster pathname lookup" (lwn.net/Articles/649294/)
  • 源码文件⁠:
    • fs/namei.c — path walk, open()
    • fs/open.c — do_sys_open()
    • fs/read_write.c — vfs_read/vfs_write
    • include/linux/fs.h — super_block, inode, dentry, file, file_operations
    • fs/namespace.c — mount 机制
    • fs/dcache.c — dentry cache

关键词: VFS, super_block, inode, dentry, file, file_operations, RCU path walk, dcache, negative dentry, mount namespace