Linux VFS虚拟文件系统杂谈

VFS(Virtual File System,虚拟文件系统)是Linux系统中一个非常重要的概念,是Linux设计思想的核心。作为Linux内核中的一个关键抽象层,它的主要作用包括:为用户程序提供了统一的文件访问接口。支持并兼容不同的文件系统(如ext4、XFS、NFS等)。屏蔽底层实现细节。

Linux 支持数十种文件系统,它们可能位于本地磁盘、内存、网络或特殊设备上。如果没有 VFS,内核就需要为每种文件系统分别实现一套系统调用接口,这不仅使内核变得臃肿,也让应用程序难以跨文件系统移植。VFS 的核心作用就是抽象与统一

为上层应用提供一致的 API。为下层文件系统定义清晰的接口规范,使得新的文件系统可以像"插件"一样方便地加入内核。实现不同文件系统在同一目录树下的共存(例如 / 用 ext4,/proc 用 procfs,/sys 用 sysfs)随着内核版本的迭代,文件系统的数量还在增加。

当进程发起一个文件操作时,系统调用进入内核,调用 VFS 的通用函数。VFS 根据操作所涉及的目录或文件,找到其所属的具体文件系统,然后调用该文件系统注册的实现函数

复制代码
用户空间进程 (应用程序)
        ↓  (系统调用:open, read, write...)
Linux 内核系统调用接口
        ↓
虚拟文件系统 (VFS)  ←→ 目录项缓存 (dentry cache)、inode 缓存
        ↓
具体文件系统 (ext4, tmpfs, NFS...)
        ↓
块设备层 / 网络协议栈 / 内存管理...
        ↓
物理设备 (硬盘、网络、内存...)

每种文件系统都有一套独立的文件访问接口。如果没有VFS,用户程序必须熟悉每种文件系统的文件访问接口,才能够很好地使用文件系统,这样显然是非常不合理的。VFS的出现,将文件系统的文件访问工作交给内核,用户程序只需要学习少量的系统调用(约为10多种)就能够轻松地访问各种文件系统。VFS的核心设计思想是多态(面相对象编程的三大特性之一),用户程序调用相同的文件访问接口能够实现不同的功能。这种设计方式可以屏蔽底层的细节,用户程序不再需要关注底层实现细节,只需要学会调用系统调用即可。另外,这种设计方式非常便于增加新文件系统,新文件系统添加至内核不会对原有的文件系统有任何影响(文件系统之间不存在耦合关系)。

VFS 的核心对象模型

VFS 基于面向对象的思想,定义了四种主要对象类型,每种对象都包含数据以及指向操作函数表的指针。具体文件系统需要提供这些函数的具体实现。

**超级块 (super_block):**表示一个已挂载的文件系统实例。Linux挂载文件系统时,会在内存创建一个超级块,用于管理文件系统。

**索引节点 (inode):**表示文件系统中的一个文件或目录,用于访问文件系统中的文件。

**目录项 (dentry):**表示文件路径中的一个分量,是Linux文件树的树节点,将文件路径名与对应的inode关联起来。

**文件对象 (file):**表示进程中一个已打开文件的实例,同一个文件可以被多个进程同时打开,每打开一次文件都要创建一个文件对象。

ext4文件系统如下:

超级块对象 (super_block)

代表一个已挂载的文件系统。

包含该文件系统的全局信息,例如:

块大小、最大文件大小。挂载选项、设备标识符。

指向各种操作表的指针(如 s_op,指向 super_operations 结构体,其中包含分配 inode、销毁 inode、同步文件系统等方法)。

每个挂载点(如 /home)对应一个超级块对象。

cpp 复制代码
struct super_block {
    dev_t             s_dev; /* 设备号 */
    unsignedchar       s_blocksize_bits; /* 文件系统数据块大小的位数 */
    unsignedlong       s_blocksize; /* 文件系统数据块大小(字节为单位)*/
    loff_t            s_maxbytes; /* 该文件系统支持的最大文件长度 */
    struct file_system_type *s_type;/* 文件系统类型 */
    conststruct super_operations   *s_op;/* 超级块操作函数表 */
    unsignedlong       s_magic; /* 魔数,标识文件系统类型(如 Ext4 为 0xEF53)*/
    struct dentry       *s_root;    /* 超级块(文件系统)根目录 */
    struct block_device *s_bdev;/* 指向块设备 */
    void              *s_fs_info; /* 文件系统私有数据指针 */
    struct list_head    s_inodes;    /* 指向文件系统中所有 inode 的链表头 */
    ......
};

super_block是文件系统的控制中心,用于管理整个文件系统。

主要作用:存储文件系统的全局信息和运行状态,减少磁盘I/O。提供文件系统操作方法(s_op文件系统函数表)。以s_root为根目录,建立和维护文件系统内部文件树。通过s_fs_info可以访问实际文件系统超级块。

索引节点对象 (inode)

代表一个文件或目录的元数据(不包含文件名)。

包含:

文件类型(普通文件、目录、符号链接等)、权限、属主、时间戳。

文件大小、数据块指针(指向实际数据的位置)。

指向操作表的指针(i_op 指向 inode_operations,如创建文件、删除文件、创建链接等;i_fop 指向 file_operations,用于打开后的文件操作)。

每个文件(或目录)在磁盘上有一个对应的 inode,在内存中也有一份缓存副本。

cpp 复制代码
struct inode {
    umode_t    i_mode; /* 文件类型和访问权限 */
    kuid_t        i_uid; /* 文件所有者用户ID */
    kgid_t        i_gid; /* 文件所有者组ID */
    conststruct inode_operations *i_op;/* inode操作函数表 */
    struct super_block  *i_sb;    /* 指向所属超级块 */
    struct address_space *i_mapping;/* 文件页缓存 */
    unsignedlong    i_ino; /* inode号,在同一个文件系统内,通过i_ino字段唯一标识一个文件 */
    union {
        constunsignedint i_nlink; /* 硬链接数 */
    };
    loff_t  i_size; /* 文件大小(字节)*/
    struct timespec64    i_atime;/* 最后访问时间 */
    struct timespec64    i_mtime;/* 最后修改时间 */
    struct timespec64  __i_ctime;/* 状态变更时间 */
    blkcnt_t    i_blocks; /* 文件占用的块数量 */
    union {
        conststruct file_operations *i_fop;/* 文件操作函数表 */
    };
    struct address_space i_data;           /* 页缓存实体,i_mmaping指向i_data */
    void    *i_private; /* 私有指针 */
    ......
};

inode包含基本的文件元数据,但不包含文件实际数据(因为inode同样属于抽象层,不能和具体实现有耦合关系)。ext4文件系统中文件的实际数据存储在ext4_inode_info结构中,ext4_inode_info表示一个具体的ext4文件系统文件,ext4_inode_info内部包含一个inode,二者之间为组合关系。不同的文件系统这部分的具体实现存在差异。inode有两个重要的成员i_op(inode操作函数表)和i_fop(文件操作函数表)。

i_op主要负责处理与文件或目录本身相关的元数据操作和结构性操作,如:创建和创建文件、删除文件、查找文件、修改权限等。 i_fop的类型为struct file_operations结构。

i_fop函数表中定义的函数接口是Linux编程中经常会用到的函数,平时调用的文件操作相关的函数接口都是通过i_fop来实现的。VFS之所以能够为用户程序提供统一的文件访问接口,都是因为i_fop默默地完成了这些文件访问接口的具体实现。

当用户程序执行文件操作时,内核通过文件路径查找文件树找到目标文件对应的dentry,再从dentry获取inode,再通过inode的i_fop找到实际文件系统的文件操作函数表,最后调用函数表中对应的函数。

每种文件系统类型的文件操作函数表都不一样(上图只列举了一些常用的文件操作函数表)。文件创建的过程中,内核根据文件系统类型动态的给i_fop赋值,从而实现"接口统一、具体实现不一样"的动态多态特性。

目录项对象 (dentry)

代表路径中的一个组成部分,用于路径名解析。

主要包含:

文件名(例如 "test.txt")。

指向父目录 dentry 的指针。

指向该文件/目录对应 inode 的指针。

一个哈希表,用于快速查找(目录项缓存 dcache)。

dentry 对象主要用于加速路径查找,避免频繁读取磁盘。它不一定对应磁盘上的持久结构,大部分时间存在于内存中。

dentry是Linux文件树的树节点,对应的结构为struct dentry。每个文件和目录都会对应一个dentry,dentry之间通过父子关系形成一个树形结构(类似于家族树)。如果把Linux文件树比作一个地图,每个dentry都是一个小的站点,文件路径就是导航路线,文件路径用于指导内核快速定位到目标文件对应的dentry。

cpp 复制代码
struct dentry {
    struct dentry *d_parent;   /* 指向父目录dentry */
    struct qstr d_name;  /* 文件名 */      
    struct inode *d_inode;   /* 指向与该目录项关联的inode */
    struct super_block *d_sb;  /* 指向该dentry所属文件系统的超级块 */
    struct list_head d_child;  /* 将当前dentry插入到其父目录dentry的d_subdirs链表中 */
    struct list_head d_subdirs;  /* 链表头,指向该目录下的所有子dentry */
    ......
};

dentry的d_inode和i_sb成员分别指向索引节点(inode)和超级块(super_block)。内核需要通过inode和super_block这两个对象来访问文件系统。inode在Linux系统中用来唯一标识一个文件,对应的结构为struct inode。inode存储了文件的元数据以及提供了文件的操作接口,但是它并不包含的文件实际数据,因为inode属于抽象层,不能够和任何具体实现有耦合关系。每个inode都有一个唯一编号(inode号),用于在文件系统中索引文件。super_block是文件系统的一个实例,对应的结构为struct super_block,它包含了文件系统的元数据信息(如:文件系统类型、容量信息、结构参数、位置信息等),内核需要借助这些元数据信息才能访问文件系统。

dentry不包含文件的实际数据,只包含文件名、指向inode和super_block的指针以及与其他dentry的关联关系。d_name成员为文件名,d_inode成员指向inode(表示具体的文件),d_sb指向super_block(表示具体的文件系统)。 子dentry(文件或目录)的d_parent指向父dentry,表示二者为父子关系。多个子dentry可以指向相同的父dentry(对应一个目录下可以创建多个文件和子目录)

父dentry通过d_subdirs链表来维护所有的子dentry,子dentry通过d_child链表节点插入父dentry的d_subdirs链表。dentry通过d_parent、d_child、d_subdirs三个成员和其他dentry构成树形结构,从而形成Linux文件树。

文件对象 (file)

代表一个进程已经打开的文件。

包含:

当前文件读写位置(偏移量)。

打开模式(读、写、追加等)。

指向对应 dentry 的指针。

指向 file_operations 表的指针(其中包含 read、write、llseek、mmap 等具体操作方法)。

文件对象在进程打开文件时创建,关闭时释放。多个进程可以打开同一个文件,各自拥有独立的 file 对象,共享同一个 inode(但可能有各自的文件偏移)。

cpp 复制代码
struct file {
    fmode_t  f_mode; /* 文件打开模式(读/写/执行)*/
    loff_t            f_pos; /* 当前文件偏移量(读写位置)*/
    unsigned int  f_flags; /* 打开标志(如O_NONBLOCK、O_SYNC)*/
    struct fown_struct  f_owner;     /* 文件属主信息,用于信号通知 */
    struct path       f_path;    /* 文件路径信息(dentry + mount)*/
    struct inode  *f_inode;    /* 指向inode的指针,文件元数据 */
    const struct file_operations *f_op;   /* 文件操作函数表(read/write等)*/
    struct address_space *f_mapping;  /* 文件地址空间,用于mmap */
    ......
} ;

文件对象(file)用于记录已打开文件的状态信息,一个文件可以被多个进程多次打开,每次打开一个文件都需要创建一个新的file。

用户程序调用open函数打开一个文件时,内核会根据open函数传入的文件路径查找文件树,找到目标文件对应的dentry,再通过dentry找到inode。如果文件不存在(文件树中没有对应的dentry和inode),open函数可以创建文件并生成dentry和inode。

获取到inode后,内核会创建一个新的file,同时将inode的关键信息复制给file,如:file的f_op将指向inode的i_fop,file将直接使用inode的文件操作函数表。file的f_mapping成员将指向inode的f_mmaping,通过file可以访问inode的页缓存。同时file的f_inode将指向inode,file和inode进行绑定。

内核最后会申请一个未使用的fd(文件描述符),将fd和file进行映射,并将这种映射关系记录在进程的已打开文件表。后续的文件操作,只需要通过fd查找进程已打开文件表获取file,就能通过file访问文件了,从而避免每次文件访问都需要索引Linux文件树。

操作示例

①用户调用 open("/home/user/notes.txt", O_RDONLY)。

②系统调用进入内核,VFS 接收请求。

③VFS 进行路径名解析:从根目录 / 或当前目录开始,逐级查找 dentry 缓存。

如果 home 的 dentry 不在缓存中,则从其父目录的 inode 中读取目录内容,创建 dentry 并关联 inode。继续解析 user、notes.txt,最终找到目标文件的 dentry,并获取其 inode。

④VFS 根据 inode 中的信息确定文件所属的文件系统(例如 ext4),并找到该文件系统已注册的 file_operations 表。

⑤VFS 分配一个新的 file 对象,设置其文件操作指针为该文件系统的实现,并将文件位置初始化为 0。

⑥调用具体文件系统的 open 实现(如果需要执行额外的操作,如 NFS 的网络连接)。

⑦返回一个文件描述符给用户进程,该描述符指向内核中的 file 对象。

此后,用户调用 read(fd, ...) 时,VFS 通过 fd 找到对应的 file 对象,再通过 file->f_op->read 调用具体文件系统的读函数,最终读取数据并返回。

VFS 的重要功能

**文件系统注册与挂载:**每种文件系统在初始化时向 VFS 注册自己(提供超级块读取函数等)。挂载时 VFS 会调用该函数读取超级块,建立根 dentry 和 inode。

缓存机制:

dentry 缓存:加速路径解析。inode 缓存:减少对磁盘 inode 的重复读取。页缓存(page cache):虽不完全属于 VFS,但与 VFS 紧密相关,用于缓存文件数据页,提高读写性能。

**特殊文件系统:**如 procfs、sysfs、devpts 等,它们没有对应的磁盘设备,而是在访问时动态生成信息,VFS 同样通过标准接口管理它们。

Linux 的虚拟文件系统是内核中一个精巧的设计,它通过定义一组通用的对象和操作接口,成功地将各种不同类型的文件系统统一到同一个命名空间下,为用户空间提供了透明、高效的文件访问能力。VFS 的存在极大地增强了 Linux 的可扩展性和兼容性,也是"一切皆文件"这一设计哲学的核心支撑。

相关推荐
m0_531237171 小时前
C语言-函数练习
c语言·开发语言
暴力求解1 小时前
Linux--进程(七)环境变量
linux·运维·服务器
济6171 小时前
ARM Linux 驱动开发篇---Linux设备树特殊节点及linux内核解析dtb文件过程--- Ubuntu20.04
linux·嵌入式·嵌入式linux驱动开发
Doro再努力1 小时前
【Linux操作系统14】操作系统概念与管理思想深度解析
linux·运维·服务器
Trouvaille ~1 小时前
【Linux】poll 多路转接:select 的改良版,以及它留下的遗憾
linux·运维·服务器·操作系统·select·poll·多路复用
Doro再努力1 小时前
【Linux操作系统13】GDB调试进阶技巧与冯诺依曼体系结构深度解析
linux·运维·服务器
blueSatchel1 小时前
GPIO子系统源码研究
linux·c语言
8125035331 小时前
计算机网络全栈连载计划
linux·网络·网络协议·计算机网络
袁袁袁袁满1 小时前
Linux如何保留当前目录本身并清空删除目录内的所有内容(文件+文件夹)?
linux·运维·服务器·清空删除目录内的所有内容