Linux VFS架构、数据结构与文件系统注册

Linux VFS深度解析:架构、数据结构与文件系统注册

1. VFS概述

Virtual File System(虚拟文件系统,VFS)是Linux内核中的一个软件层,它为用户空间程序提供了统一的文件系统接口。VFS的核心作用是抽象底层文件系统的差异,使得应用程序可以使用相同的系统调用(如open、read、write)来操作不同类型的文件系统(ext4、XFS、Btrfs等)。

1.1 VFS的设计思想

VFS采用了面向对象的设计思想,虽然Linux内核使用C语言编写,但通过结构体和函数指针实现了类似面向对象的机制:

  • 抽象基类: VFS定义了通用的数据结构(如inode、dentry、file)
  • 多态性: 通过函数指针表(operations)实现不同文件系统的具体操作
  • 统一接口: 对上层应用提供标准的POSIX接口

1.2 VFS架构层次

+------------------+
|   用户空间应用    |
+------------------+
        |
        | (系统调用: open, read, write, close)
        v
+------------------+
|    系统调用层     |
+------------------+
        |
        v
+------------------+
|    VFS 层        |  <-- 核心抽象层
+------------------+
        |
        v
+------------------+
|  具体文件系统     |
|  (ext4/XFS/Btrfs)|
+------------------+
        |
        v
+------------------+
|   块设备层/驱动   |
+------------------+

2. VFS核心数据结构

VFS依赖四个主要的数据结构来描述文件系统的层次结构,每个结构都包含一个操作函数表(operations table)。

2.1 超级块 (super_block)

超级块是文件系统的元信息容器,代表一个已挂载的文件系统实例。

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;     // 超级块操作函数表
    unsigned long       s_flags;             // 挂载标志
    unsigned long       s_magic;             // 文件系统魔数
    struct dentry       *s_root;             // 根目录项
    int                 s_count;             // 引用计数
    atomic_t            s_active;            // 活跃计数
    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 *wbc);
    void (*put_super)(struct super_block *);
    int (*sync_fs)(struct super_block *sb, int wait);
    int (*statfs)(struct dentry *, struct kstatfs *);
    int (*remount_fs)(struct super_block *, int *, char *);
    // ... 更多操作
};

2.2 索引节点 (inode)

inode存储文件的元数据信息,每个文件或目录都对应一个inode。

struct inode {
    umode_t             i_mode;              // 访问权限
    kuid_t              i_uid;               // 所有者ID
    kgid_t              i_gid;               // 组ID
    unsigned long       i_ino;               // inode编号
    loff_t              i_size;              // 文件大小
    struct timespec64   i_atime;             // 访问时间
    struct timespec64   i_mtime;             // 修改时间
    struct timespec64   i_ctime;             // 状态改变时间
    blkcnt_t            i_blocks;            // 块数
    
    const struct inode_operations *i_op;     // inode操作函数表
    const struct file_operations *i_fop;     // 文件操作函数表
    struct super_block  *i_sb;               // 所属超级块
    struct address_space *i_mapping;         // 页缓存映射
    
    union {
        struct pipe_inode_info *i_pipe;      // 管道信息
        struct block_device *i_bdev;         // 块设备
        struct cdev *i_cdev;                 // 字符设备
    };
    // ... 更多字段
};

关键操作函数表:

struct inode_operations {
    int (*create)(struct inode *, struct dentry *, umode_t, bool);
    struct dentry *(*lookup)(struct inode *, struct dentry *, unsigned int);
    int (*link)(struct dentry *, struct inode *, struct dentry *);
    int (*unlink)(struct inode *, struct dentry *);
    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);
    // ... 更多操作
};

2.3 目录项 (dentry)

dentry将文件名与inode关联起来,形成目录树结构。dentry主要存在于内存中,用于加速路径查找。

struct dentry {
    unsigned int        d_flags;             // 目录项标志
    struct dentry       *d_parent;           // 父目录项
    struct qstr         d_name;              // 目录项名称
    struct inode        *d_inode;            // 关联的inode
    
    const struct dentry_operations *d_op;    // 目录项操作
    struct super_block  *d_sb;               // 所属超级块
    void                *d_fsdata;           // 文件系统私有数据
    
    struct list_head    d_child;             // 父目录的子目录项链表
    struct list_head    d_subdirs;           // 当前目录的子目录项链表
    struct hlist_node   d_alias;             // inode的别名链表
    // ... 更多字段
};

dentry状态:

  • 正数状态: d_inode指向有效inode,表示文件存在
  • 负数状态: d_inode为NULL,表示文件不存在(缓存的查找失败结果)

2.4 文件对象 (file)

file结构代表进程打开的文件,包含文件位置、访问模式等运行时信息。

struct file {
    struct path         f_path;              // 文件路径(包含dentry和vfsmount)
    struct inode        *f_inode;            // 关联的inode
    const struct file_operations *f_op;      // 文件操作函数表
    
    atomic_long_t       f_count;             // 引用计数
    unsigned int        f_flags;             // 打开标志(O_RDONLY, O_WRONLY等)
    fmode_t             f_mode;              // 访问模式
    loff_t              f_pos;               // 文件位置指针
    struct fown_struct  f_owner;             // 文件所有者
    
    void                *private_data;       // 私有数据(驱动使用)
    struct address_space *f_mapping;         // 页缓存映射
    // ... 更多字段
};

关键操作函数表:

struct file_operations {
    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 (*open)(struct inode *, struct file *);
    int (*release)(struct inode *, struct file *);
    int (*fsync)(struct file *, loff_t, loff_t, int datasync);
    int (*mmap)(struct file *, struct vm_area_struct *);
    // ... 更多操作
};

2.5 数据结构关系图

进程 (task_struct)
    |
    |-- fs_struct (文件系统上下文)
    |       |-- root (根目录 dentry)
    |       |-- pwd (当前工作目录 dentry)
    |
    |-- files_struct (打开文件表)
            |-- fd_array[] (文件描述符数组)
                    |
                    v
                 file (文件对象)
                    |
                    |-- f_path.dentry --> dentry (目录项)
                    |                         |
                    |                         |-- d_inode --> inode (索引节点)
                    |                                            |
                    |-- f_op (file_operations)                   |-- i_sb --> super_block
                                                                 |
                                                                 |-- i_op (inode_operations)
                                                                 |
                                                                 |-- i_fop (file_operations)

3. 注册新文件系统

注册文件系统需要定义file_system_type结构并调用register_filesystem()

3.1 file_system_type结构

struct file_system_type {
    const char          *name;               // 文件系统名称
    int                 fs_flags;            // 文件系统标志
    
    // 挂载文件系统的回调函数
    struct dentry *(*mount)(struct file_system_type *, int,
                           const char *, void *);
    
    void (*kill_sb)(struct super_block *);   // 卸载文件系统
    struct module       *owner;              // 所属模块
    struct file_system_type *next;           // 链表指针
    struct hlist_head   fs_supers;           // 该类型的所有超级块
};

3.2 注册流程示例

以下是一个简化的文件系统注册示例:

#include <linux/fs.h>
#include <linux/module.h>

// 1. 定义超级块操作
static const struct super_operations myfs_super_ops = {
    .alloc_inode    = myfs_alloc_inode,
    .destroy_inode  = myfs_destroy_inode,
    .write_inode    = myfs_write_inode,
    .put_super      = myfs_put_super,
    .statfs         = myfs_statfs,
    .sync_fs        = myfs_sync_fs,
};

// 2. 定义inode操作
static const struct inode_operations myfs_inode_ops = {
    .lookup         = myfs_lookup,
    .create         = myfs_create,
    .mkdir          = myfs_mkdir,
    .rmdir          = myfs_rmdir,
    .unlink         = myfs_unlink,
};

// 3. 定义文件操作
static const struct file_operations myfs_file_ops = {
    .read_iter      = myfs_read_iter,
    .write_iter     = myfs_write_iter,
    .open           = myfs_open,
    .release        = myfs_release,
    .fsync          = myfs_fsync,
    .llseek         = generic_file_llseek,
};

// 4. 填充超级块
static int myfs_fill_super(struct super_block *sb, void *data, int silent)
{
    struct inode *root_inode;
    struct dentry *root_dentry;
    
    // 设置超级块基本信息
    sb->s_blocksize = PAGE_SIZE;
    sb->s_blocksize_bits = PAGE_SHIFT;
    sb->s_magic = MYFS_MAGIC;
    sb->s_op = &myfs_super_ops;
    sb->s_maxbytes = MAX_LFS_FILESIZE;
    
    // 创建根inode
    root_inode = new_inode(sb);
    if (!root_inode)
        return -ENOMEM;
    
    root_inode->i_ino = 1;
    root_inode->i_mode = S_IFDIR | 0755;
    root_inode->i_op = &myfs_inode_ops;
    root_inode->i_fop = &simple_dir_operations;
    set_nlink(root_inode, 2);
    
    // 创建根dentry
    root_dentry = d_make_root(root_inode);
    if (!root_dentry) {
        iput(root_inode);
        return -ENOMEM;
    }
    
    sb->s_root = root_dentry;
    return 0;
}

// 5. 挂载函数
static struct dentry *myfs_mount(struct file_system_type *fs_type,
                                  int flags, const char *dev_name,
                                  void *data)
{
    // 对于基于块设备的文件系统,使用mount_bdev
    return mount_bdev(fs_type, flags, dev_name, data, myfs_fill_super);
    
    // 对于伪文件系统(如procfs),使用mount_nodev
    // return mount_nodev(fs_type, flags, data, myfs_fill_super);
}

// 6. 定义file_system_type
static struct file_system_type myfs_type = {
    .owner          = THIS_MODULE,
    .name           = "myfs",
    .mount          = myfs_mount,
    .kill_sb        = kill_block_super,  // 或kill_litter_super
    .fs_flags       = FS_REQUIRES_DEV,
};

// 7. 模块初始化和清理
static int __init myfs_init(void)
{
    int ret;
    
    // 注册文件系统
    ret = register_filesystem(&myfs_type);
    if (ret) {
        pr_err("Failed to register myfs\n");
        return ret;
    }
    
    pr_info("myfs registered successfully\n");
    return 0;
}

static void __exit myfs_exit(void)
{
    // 注销文件系统
    unregister_filesystem(&myfs_type);
    pr_info("myfs unregistered\n");
}

module_init(myfs_init);
module_exit(myfs_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My Custom File System");

3.3 注册过程详解

  1. register_filesystem(): 将file_system_type添加到全局文件系统类型链表
  2. mount操作: 用户执行mount命令时,内核调用对应的mount回调
  3. fill_super: 创建超级块对象,初始化根inode和根dentry
  4. 挂载点连接: VFS将新文件系统的根dentry连接到挂载点

4. 读写操作的传递路径

4.1 read系统调用的完整路径

用户空间: read(fd, buf, count)
    |
    | (系统调用陷入内核)
    v
SYSCALL_DEFINE3(read, ...)
    |
    v
ksys_read()
    |
    v
vfs_read()  <-- VFS层
    |
    | 1. 检查文件权限和模式
    | 2. 更新访问时间(atime)
    |
    v
file->f_op->read_iter()  或  file->f_op->read()
    |
    | (通过函数指针调用具体文件系统的实现)
    v
ext4_file_read_iter()  <-- ext4文件系统层
    |
    v
generic_file_read_iter()  <-- 通用页缓存层
    |
    | 1. 检查页缓存
    | 2. 如果缓存命中,直接从缓存读取
    | 3. 如果缓存未命中,触发页面读取
    |
    v
ext4_readpage() / ext4_readpages()
    |
    | 1. 通过inode->i_mapping找到页缓存
    | 2. 分配页面
    | 3. 通过ext4的extent tree找到物理块地址
    |
    v
submit_bio()  <-- 块层
    |
    | 构造bio请求
    |
    v
块设备驱动 (如SCSI/NVMe)
    |
    v
硬件设备

4.2 write系统调用的完整路径

用户空间: write(fd, buf, count)
    |
    | (系统调用陷入内核)
    v
SYSCALL_DEFINE3(write, ...)
    |
    v
ksys_write()
    |
    v
vfs_write()  <-- VFS层
    |
    | 1. 检查写权限
    | 2. 检查文件大小限制
    |
    v
file->f_op->write_iter()
    |
    | (调用具体文件系统的写函数)
    v
ext4_file_write_iter()  <-- ext4文件系统层
    |
    | 1. 处理O_DIRECT(直接I/O)
    | 2. 处理O_APPEND(追加模式)
    |
    v
__generic_file_write_iter()
    |
    v
generic_perform_write()  <-- 通用写路径
    |
    | 1. 循环处理每一页
    | 2. 调用address_space_operations
    |
    v
ext4_write_begin()
    |
    | 1. 分配或查找页面
    | 2. 如果需要,分配新的数据块
    | 3. 读取部分写的页面内容
    |
    v
ext4_write_end()
    |
    | 1. 复制用户数据到页缓存
    | 2. 标记页面为脏(PG_dirty)
    | 3. 标记inode为脏
    | 4. 更新文件大小和修改时间
    |
    v
(异步回写或同步刷新)
    |
    v
ext4_writepages()
    |
    | 1. 收集脏页
    | 2. 分配物理块(如果需要)
    | 3. 构造写请求
    |
    v
submit_bio()  <-- 块层
    |
    v
块设备驱动
    |
    v
硬件设备

4.3 关键函数分析

4.3.1 vfs_read()

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
    ssize_t ret;
    
    // 检查文件是否可读
    if (!(file->f_mode & FMODE_READ))
        return -EBADF;
    
    // 检查file_operations是否存在
    if (!file->f_op->read && !file->f_op->read_iter)
        return -EINVAL;
    
    // 检查缓冲区是否可写
    if (!access_ok(buf, count))
        return -EFAULT;
    
    // 调用具体文件系统的read操作
    if (file->f_op->read_iter)
        ret = new_sync_read(file, buf, count, pos);
    else
        ret = file->f_op->read(file, buf, count, pos);
    
    // 更新访问时间
    if (ret > 0) {
        fsnotify_access(file);
        add_rchar(current, ret);
    }
    
    inc_syscr(current);
    return ret;
}

4.3.2 ext4_file_read_iter()

static ssize_t ext4_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
    struct inode *inode = file_inode(iocb->ki_filp);
    
    // 处理加密文件
    if (!ext4_inode_readable(inode))
        return -EACCES;
    
    // 对于直接I/O
    if (iocb->ki_flags & IOCB_DIRECT) {
        // 处理Direct I/O
        return ext4_dio_read_iter(iocb, to);
    }
    
    // 标准的buffered I/O,使用通用页缓存
    return generic_file_read_iter(iocb, to);
}

4.3.3 generic_file_read_iter()

ssize_t generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
    // 如果是Direct I/O
    if (iocb->ki_flags & IOCB_DIRECT) {
        struct address_space *mapping = iocb->ki_filp->f_mapping;
        
        // 刷新该范围的脏页
        filemap_write_and_wait_range(mapping, iocb->ki_pos, end);
        
        // 执行Direct I/O读取
        return mapping->a_ops->direct_IO(iocb, iter);
    }
    
    // 标准的页缓存读取
    return filemap_read(iocb, iter, 0);
}

4.4 ext4特定的实现

ext4文件系统使用extent tree来管理文件的物理块映射。

4.4.1 ext4的file_operations

const struct file_operations ext4_file_operations = {
    .llseek         = ext4_llseek,
    .read_iter      = ext4_file_read_iter,
    .write_iter     = ext4_file_write_iter,
    .unlocked_ioctl = ext4_ioctl,
    .open           = ext4_file_open,
    .release        = ext4_release_file,
    .mmap           = ext4_file_mmap,
    .fsync          = ext4_sync_file,
    .splice_read    = generic_file_splice_read,
    .splice_write   = iter_file_splice_write,
    .fallocate      = ext4_fallocate,
};

4.4.2 ext4的address_space_operations

static const struct address_space_operations ext4_aops = {
    .readpage       = ext4_readpage,
    .readpages      = ext4_readpages,
    .writepage      = ext4_writepage,
    .writepages     = ext4_writepages,
    .write_begin    = ext4_write_begin,
    .write_end      = ext4_write_end,
    .bmap           = ext4_bmap,
    .invalidatepage = ext4_invalidatepage,
    .releasepage    = ext4_releasepage,
    .direct_IO      = ext4_direct_IO,
    .migratepage    = buffer_migrate_page,
    .is_partially_uptodate = block_is_partially_uptodate,
    .error_remove_page = generic_error_remove_page,
};

4.4.3 块地址转换

ext4使用extent tree将逻辑块号转换为物理块号:

// 逻辑块号 -> 物理块号
int ext4_map_blocks(handle_t *handle, struct inode *inode,
                    struct ext4_map_blocks *map, int flags)
{
    struct ext4_extent_header *eh;
    struct ext4_extent *ex;
    
    // 1. 在extent tree中查找逻辑块对应的extent
    ex = ext4_find_extent(inode, map->m_lblk);
    
    // 2. 如果找到extent,直接返回物理块号
    if (ex) {
        map->m_pblk = ext4_ext_pblock(ex) + (map->m_lblk - le32_to_cpu(ex->ee_block));
        return 0;
    }
    
    // 3. 如果是写操作且没找到,需要分配新块
    if (flags & EXT4_GET_BLOCKS_CREATE) {
        return ext4_ext_map_blocks(handle, inode, map, flags);
    }
    
    return -ENOENT;
}

5. 完整流程示例:读取ext4文件

假设用户程序执行 read(fd, buf, 4096):

1. 用户空间
   - 应用程序调用read()
   - C库(glibc)包装系统调用
   
2. 系统调用入口
   - 陷入内核态
   - SYSCALL_DEFINE3(read, ...)
   
3. VFS层 (vfs_read)
   - 通过fd找到struct file
   - 检查文件权限(FMODE_READ)
   - 检查参数合法性
   - 调用file->f_op->read_iter
   
4. ext4文件系统层
   - ext4_file_read_iter()
   - 判断是否Direct I/O
   - 调用generic_file_read_iter()
   
5. 页缓存层
   - generic_file_read_iter()
   - 调用filemap_read()
   - 查找页缓存(find_get_page)
   
6a. 缓存命中路径
    - 从页缓存直接copy_to_user()
    - 更新统计信息
    - 返回用户空间
    
6b. 缓存未命中路径
    - 调用readpage()/readpages()
    - ext4_readpage()被调用
    - 通过extent tree找到物理块号
    - ext4_map_blocks()转换地址
    - 构造bio请求
    - submit_bio()提交到块层
    
7. 块I/O层
   - 块层调度器处理请求
   - 合并相邻请求
   - 提交到设备驱动
   
8. 设备驱动层
   - SCSI/NVMe驱动处理
   - DMA传输数据
   
9. I/O完成中断
   - 硬件中断
   - 标记页面为最新(PG_uptodate)
   - 唤醒等待进程
   - copy_to_user()复制到用户缓冲区
   
10. 返回用户空间
    - 系统调用返回
    - 恢复用户态执行

6. 总结

VFS通过以下机制实现了文件系统的统一抽象:

  1. 分层设计: 清晰的层次分离了通用逻辑和文件系统特定实现
  2. 函数指针表: 实现了类似面向对象的多态性
  3. 页缓存: 提高了I/O性能,减少了磁盘访问
  4. dentry缓存: 加速了路径查找操作

理解VFS的工作原理对于文件系统开发、性能优化和内核调试都至关重要。无论是开发新的文件系统,还是分析I/O性能问题,VFS都是必须深入理解的核心组件。

参考资源

  • Linux内核源码: fs/目录
  • Documentation/filesystems/vfs.txt
  • ext4源码: fs/ext4/
  • 《Linux内核设计与实现》第13章
  • 《深入理解Linux内核》第12章

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦