虚拟文件系统(VFS)

VFS 是 Linux 内核中的一个抽象层,为用户空间程序提供统一的文件操作接口,屏蔽了底层不同文件系统的实现细节。

VFS 架构

┌─────────────────────────────────────────────────┐
│           用户空间应用程序                       │
│     open() read() write() close() stat()        │
└────────────────┬────────────────────────────────┘
                 │
        系统调用接口 (System Call Interface)
                 │
┌────────────────┼────────────────────────────────┐
│                ↓                                 │
│    ┌──────────────────────────┐                 │
│    │   虚拟文件系统 (VFS)      │                 │
│    │  ┌────────────────────┐  │                 │
│    │  │ superblock         │  │                 │
│    │  │ inode              │  │                 │
│    │  │ dentry             │  │                 │
│    │  │ file               │  │                 │
│    │  └────────────────────┘  │                 │
│    └──────────┬───────────────┘                 │
│               │                                  │
│    ┌──────────┼──────────────────────┐         │
│    ↓          ↓          ↓           ↓          │
│  ┌────┐   ┌────┐    ┌─────┐     ┌──────┐      │
│  │Ext4│   │XFS │    │Btrfs│     │ NFS  │      │
│  └────┘   └────┘    └─────┘     └──────┘      │
└─────────────────────────────────────────────────┘

VFS 核心数据结构

超级块(superblock)

描述文件系统的元数据:

// 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;    // 文件系统类型
    const struct super_operations *s_op; // 超级块操作
    struct dentry       *s_root;        // 根目录 dentry
    struct list_head    s_inodes;       // inode 链表
    struct list_head    s_dirty;        // 脏 inode 链表
    
    // 文件系统特定数据
    void *s_fs_info;
};

struct super_operations {
    struct inode *(*alloc_inode)(struct super_block *sb);
    void (*destroy_inode)(struct inode *);
    void (*dirty_inode)(struct inode *, int flags);
    int (*write_inode)(struct inode *, struct writeback_control *);
    int (*drop_inode)(struct inode *);
    void (*evict_inode)(struct inode *);
    void (*put_super)(struct super_block *);
    int (*sync_fs)(struct super_block *sb, int wait);
    int (*statfs)(struct dentry *, struct kstatfs *);
};

inode(索引节点)

描述文件的元数据:

// include/linux/fs.h
struct inode {
    // 文件类型和权限
    umode_t             i_mode;         // 文件类型和权限
    unsigned short      i_opflags;
    kuid_t              i_uid;          // 所有者 UID
    kgid_t              i_gid;          // 所有者 GID
    unsigned int        i_flags;
    
    // inode 操作
    const struct inode_operations *i_op;
    struct super_block  *i_sb;          // 所属超级块
    
    // 文件大小和块
    loff_t              i_size;         // 文件大小
    struct timespec64   i_atime;        // 访问时间
    struct timespec64   i_mtime;        // 修改时间
    struct timespec64   i_ctime;        // 状态改变时间
    
    // 链接和引用计数
    unsigned int        i_nlink;        // 硬链接数
    dev_t               i_rdev;         // 设备号
    
    // 文件操作
    const struct file_operations *i_fop;
    
    // 地址空间(页缓存)
    struct address_space *i_mapping;
    
    // 块设备
    unsigned long       i_ino;          // inode 号
    union {
        unsigned long   i_blocks;       // 块数
        struct pipe_inode_info *i_pipe;
        struct block_device *i_bdev;
        struct cdev     *i_cdev;
    };
    
    // 文件系统特定数据
    void                *i_private;
};

struct inode_operations {
    struct dentry *(*lookup)(struct inode *, struct dentry *, unsigned int);
    int (*create)(struct inode *, struct dentry *, umode_t, bool);
    int (*link)(struct dentry *, struct inode *, struct dentry *);
    int (*unlink)(struct inode *, struct dentry *);
    int (*symlink)(struct inode *, struct dentry *, const char *);
    int (*mkdir)(struct inode *, struct dentry *, umode_t);
    int (*rmdir)(struct inode *, struct dentry *);
    int (*rename)(struct inode *, struct dentry *,
                 struct inode *, struct dentry *, unsigned int);
    int (*setattr)(struct dentry *, struct iattr *);
    int (*getattr)(const struct path *, struct kstat *, u32, unsigned int);
};

dentry(目录项)

描述目录项(文件路径的组成部分):

// include/linux/dcache.h
struct dentry {
    // 标志和状态
    unsigned int d_flags;
    seqcount_t d_seq;
    
    // 父目录和子目录
    struct dentry *d_parent;            // 父目录
    struct qstr d_name;                 // 目录项名称
    
    // 关联的 inode
    struct inode *d_inode;              // 关联的 inode
    
    // 目录项操作
    const struct dentry_operations *d_op;
    
    // 超级块
    struct super_block *d_sb;
    
    // 时间戳
    unsigned long d_time;
    
    // 哈希链表
    struct hlist_bl_node d_hash;
    
    // 子目录链表
    struct list_head d_child;
    struct list_head d_subdirs;
    
    // LRU 链表
    struct list_head d_lru;
    
    union {
        struct list_head d_child;
        struct rcu_head d_rcu;
    } d_u;
};

struct dentry_operations {
    int (*d_revalidate)(struct dentry *, unsigned int);
    int (*d_weak_revalidate)(struct dentry *, unsigned int);
    int (*d_hash)(const struct dentry *, struct qstr *);
    int (*d_compare)(const struct dentry *,
                    unsigned int, const char *, const struct qstr *);
    int (*d_delete)(const struct dentry *);
    int (*d_init)(struct dentry *);
    void (*d_release)(struct dentry *);
    void (*d_iput)(struct dentry *, struct inode *);
};

file(文件对象)

描述打开的文件:

// include/linux/fs.h
struct file {
    // 文件路径
    struct path         f_path;
    struct inode        *f_inode;       // 缓存的 inode
    
    // 文件操作
    const struct file_operations *f_op;
    
    // 文件标志和模式
    unsigned int        f_flags;        // 打开标志
    fmode_t             f_mode;         // 访问模式
    
    // 文件位置
    loff_t              f_pos;          // 当前文件位置
    
    // 引用计数
    atomic_long_t       f_count;
    
    // 所有者
    struct fown_struct  f_owner;
    const struct cred   *f_cred;
    
    // 地址空间
    struct address_space *f_mapping;
    
    // 私有数据
    void                *private_data;
};

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 (*iterate)(struct file *, struct dir_context *);
    int (*iterate_shared)(struct file *, struct dir_context *);
    __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);
    int (*mmap)(struct file *, struct vm_area_struct *);
    int (*open)(struct inode *, struct file *);
    int (*flush)(struct file *, fl_owner_t id);
    int (*release)(struct inode *, struct file *);
    int (*fsync)(struct file *, loff_t, loff_t, int datasync);
    int (*fasync)(int, struct file *, int);
    int (*lock)(struct file *, int, struct file_lock *);
};

VFS 缓存机制

dentry cache

// 目录项缓存统计
// fs/dcache.c

// 查看 dcache 统计
cat /proc/sys/fs/dentry-state
# 输出:nr_dentry nr_unused age_limit want_pages

// 清理 dcache
echo 2 > /proc/sys/vm/drop_caches  // 清理 dentry 和 inode

inode cache

// 查看 inode 缓存
cat /proc/sys/fs/inode-state
# 输出:nr_inodes nr_free_inodes preshrink

// slabtop 查看缓存
slabtop -o | grep -E 'dentry|inode'

page cache

// 查看页缓存统计
cat /proc/meminfo | grep -i cache

// 清理页缓存
sync
echo 1 > /proc/sys/vm/drop_caches  // 仅清理 page cache
echo 3 > /proc/sys/vm/drop_caches  // 清理所有缓存

文件路径解析

路径查找过程

// fs/namei.c
// 路径解析流程

1. path_openat()
   ├─ 初始化查找上下文
   └─ 调用 link_path_walk()

2. link_path_walk()
   ├─ 逐个组件解析路径
   ├─ 查找 dentry cache
   └─ 如果未命中,调用 i_op->lookup()

3. lookup_slow()
   ├─ 调用文件系统的 lookup 函数
   ├─ 读取磁盘数据
   └─ 创建新的 dentry 和 inode

4. do_last()
   └─ 处理最后一个路径组件

示例:打开 /home/user/test.txt

1. 从根目录 dentry 开始
2. 查找 "home" 目录项
   - 检查 dcache
   - 如果未命中,读取根目录的磁盘块
3. 查找 "user" 目录项
   - 检查 dcache
   - 如果未命中,读取 /home 的磁盘块
4. 查找 "test.txt" 文件
   - 检查 dcache
   - 如果未命中,读取 /home/user 的磁盘块
5. 创建 file 对象
6. 返回文件描述符

实践:查看 VFS 信息

查看文件系统类型

#!/bin/bash
# show-filesystems.sh

echo "=== 已注册的文件系统 ==="
cat /proc/filesystems

echo ""
echo "=== 已挂载的文件系统 ==="
mount | column -t

echo ""
echo "=== 各文件系统使用情况 ==="
df -hT

查看 inode 信息

# 查看文件的 inode 号
ls -i file.txt

# 查看 inode 详细信息
stat file.txt

# 查看文件系统 inode 使用情况
df -i

# 找到特定 inode 号的文件
find / -inum 12345

# 查看进程打开的文件
lsof -p <PID>

监控文件操作

# 使用 inotify 监控文件变化
inotifywait -m /path/to/watch

# 监控特定事件
inotifywait -m -e modify,create,delete /path/to/watch

# 使用 fanotify 监控文件系统
# 需要编写 C 程序使用 fanotify API

VFS 调试

// 示例:跟踪文件操作
// trace-file-ops.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main() {
    int fd;
    struct stat st;
    char buf[1024];
    
    // open 系统调用
    printf("Opening file...\n");
    fd = open("/tmp/test.txt", O_RDWR | O_CREAT, 0644);
    
    // fstat 系统调用
    printf("Getting file stats...\n");
    fstat(fd, &st);
    printf("Inode: %ld, Size: %ld\n", st.st_ino, st.st_size);
    
    // write 系统调用
    printf("Writing to file...\n");
    write(fd, "Hello VFS\n", 10);
    
    // lseek 系统调用
    printf("Seeking to beginning...\n");
    lseek(fd, 0, SEEK_SET);
    
    // read 系统调用
    printf("Reading from file...\n");
    read(fd, buf, sizeof(buf));
    printf("Content: %s", buf);
    
    // close 系统调用
    printf("Closing file...\n");
    close(fd);
    
    return 0;
}

使用 strace 跟踪

gcc -o trace trace-file-ops.c
strace -e trace=open,close,read,write,lseek,fstat ./trace

性能优化

减少系统调用

// 使用 mmap 代替 read/write
#include <sys/mman.h>

int fd = open("large_file.dat", O_RDONLY);
struct stat st;
fstat(fd, &st);

void *mapped = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
// 直接访问内存,无需 read() 系统调用
close(fd);
munmap(mapped, st.st_size);

批量操作

// 使用 readv/writev 批量读写
#include <sys/uio.h>

struct iovec iov[3];
iov[0].iov_base = buf1;
iov[0].iov_len = len1;
iov[1].iov_base = buf2;
iov[1].iov_len = len2;
iov[2].iov_base = buf3;
iov[2].iov_len = len3;

// 一次系统调用读取多个缓冲区
readv(fd, iov, 3);

异步 I/O

// 使用 io_uring (Linux 5.1+)
#include <liburing.h>

struct io_uring ring;
io_uring_queue_init(32, &ring, 0);

struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, len, offset);
io_uring_submit(&ring);

struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
// 处理完成事件
io_uring_cqe_seen(&ring, cqe);

下一步:学习 Ext4 文件系统详解 章节。