写点什么

浅谈 Linux 虚拟文件系统

作者:lecury
  • 2021 年 12 月 04 日
  • 本文字数:2793 字

    阅读完需:约 9 分钟

浅谈Linux虚拟文件系统

本文首次发布于知乎专栏:https://www.zhihu.com/column/c_1068890727731240960

1. 虚拟文件系统概述

1.1 VFS 简介

虚拟文件系统(Virtual File System,简称 VFS)是 Linux 内核的子系统之一,它为用户程序提供文件和文件系统操作的统一接口,屏蔽不同文件系统的差异和操作细节。借助 VFS 可以直接使用open()read()write()这样的系统调用操作文件,而无须考虑具体的文件系统和实际的存储介质。


举个例子,Linux 用户程序可以通过read() 来读取ext3NFSXFS等文件系统的文件,也可以读取存储在SSDHDD等不同存储介质的文件,无须考虑不同文件系统或者不同存储介质的差异。

通过 VFS 系统,Linux 提供了通用的系统调用,可以跨越不同文件系统和介质之间执行,极大简化了用户访问不同文件系统的过程。另一方面,新的文件系统、新类型的存储介质,可以无须编译的情况下,动态加载到 Linux 中。


"一切皆文件"是 Linux 的基本哲学之一,不仅是普通的文件,包括目录、字符设备、块设备、套接字等,都可以以文件的方式被对待。实现这一行为的基础,正是 Linux 的虚拟文件系统机制。

1.2 VFS 原理

VFS 之所以能够衔接各种各样的文件系统,是因为它抽象了一个通用的文件系统模型,定义了通用文件系统都支持的、概念上的接口。新的文件系统只要支持并实现这些接口,并注册到 Linux 内核中,即可安装和使用。

举个例子,比如 Linux 写一个文件:

int ret = write(fd, buf, len);
复制代码

调用了write()系统调用,它的过程简要如下:

  • 首先,勾起 VFS 通用系统调用sys_write()处理。

  • 接着,sys_write()根据fd找到所在的文件系统提供的写操作函数,比如op_write()

  • 最后,调用op_write()实际的把数据写入到文件中。

操作示意图如下:


2. 虚拟文件系统组成部分

Linux 为了实现这种 VFS 系统,采用面向对象的设计思路,主要抽象了四种对象类型:

  • 超级块对象:代表一个已安装的文件系统。

  • 索引节点对象:代表具体的文件。

  • 目录项对象:代表一个目录项,是文件路径的一个组成部分。

  • 文件对象:代表进程打开的文件。

每个对象都包含一组操作方法,用于操作相应的文件系统。

备注:Linux 将目录当做文件对象来处理,是另一种形式的文件,它里面包含了一个或多个目录项。而目录项是单独抽象的对象,主要包括文件名和索引节点号。因为目录是可以层层嵌套,以形成文件路径,而路径中的每一部分,其实就是目录项。

接下来介绍一下各个对象的作用以及相关操作。

2.1 超级块

超级块用于存储文件系统的元信息,由super_block结构体表示,定义在<linux/fs.h>中,元信息里面包含文件系统的基本属性信息,比如有:

  • 索引节点信息

  • 挂载的标志

  • 操作方法 s_op

  • 安装权限

  • 文件系统类型、大小、区块数

  • 等等等等

其中操作方法 s_op 对每个文件系统来说,是非常重要的,它指向该超级块的操作函数表,包含一系列操作方法的实现,这些方法有:

  • 分配 inode

  • 销毁 inode

  • 读、写 inode

  • 文件同步

  • 等等

当 VFS 需要对超级块进行操作时,首先要在超级块的操作方法 s_op 中,找到对应的操作方法后再执行。比如文件系统要写自己的超级块:

superblock->s_op->write_supper(sb);
复制代码

创建文件系统时,其实就是往存储介质的特定位置,写入超级块信息;而卸载文件系统时,由 VFS 调用释放超级块。

Linux 支持众多不同的文件系统,file_system_type结构体用于描述每种文件系统的功能和行为,包括:

  • 名称、类型等

  • 超级块对象链表

当向内核注册新的文件系统时,其实是将file_system_type对象实例化,然后加入到 Linux 的根文件系统的目录树结构上。

2.2 索引

索引节点对象包含 Linux 内核在操作文件、目录时,所需要的全部信息,这些信息由inode结构体来描述,定义在<linux/fs.h>中,主要包含:

  • 超级块相关信息

  • 目录相关信息

  • 文件大小、访问时间、权限相关信息

  • 引用计数

  • 等等

一个索引节点inode代表文件系统中的一个文件,只有当文件被访问时,才在内存中创建索引节点。与超级块类似的是,索引节点对象也提供了许多操作接口,供 VFS 系统使用,这些接口包括:

  • create(): 创建新的索引节点(创建新的文件)

  • link(): 创建硬链接

  • symlink(): 创建符号链接。

  • mkdir(): 创建新的目录。

等等,我们常规的文件操作,都能在索引节点中找到相应的操作接口。

2.3 目录项

前面提到 VFS 把目录当做文件对待,比如/usr/bin/vimusrbinvim都是文件,不过vim是一个普通文件,usrbin都是目录文件,都是由索引节点对象标识。

由于 VFS 会经常的执行目录相关的操作,比如切换到某个目录、路径名的查找等等,为了提高这个过程的效率,VFS 引入了目录项的概念。一个路径的组成部分,不管是目录还是普通文件,都是一个目录项对象。/usrbinvim都对应一个目录项对象。不过目录项对象没有对应的磁盘数据结构,是 VFS 在遍历路径的过程中,将它们逐个解析成目录项对象。

目录项由dentry结构体标识,定义在<linux/dcache.h>中,主要包含:

  • 父目录项对象地址

  • 子目录项链表

  • 目录关联的索引节点对象

  • 目录项操作指针

  • 等等

目录项有三种状态:

  • 被使用:该目录项指向一个有效的索引节点,并有一个或多个使用者,不能被丢弃。

  • 未被使用:也对应一个有效的索引节点,但 VFS 还未使用,被保留在缓存中。如果要回收内存的话,可以撤销未使用的目录项。

  • 负状态:没有对应有效的索引节点,因为索引节点被删除了,或者路径不正确,但是目录项仍被保留了。

将整个文件系统的目录结构解析成目录项,是一件费力的工作,为了节省 VFS 操作目录项的成本,内核会将目录项缓存起来。

2.4 文件

文件对象是进程打开的文件在内存中的实例。Linux 用户程序可以通过open()系统调用来打开一个文件,通过close()系统调用来关闭一个文件。由于多个进程可以同时打开和操作同一个文件,所以同一个文件,在内存中也存在多个对应的文件对象,但对应的索引节点和目录项是唯一的。

文件对象由file结构体表示,定义在<linux/fs.h>中,主要包含:

  • 文件操作方法

  • 文件对象的引用计数

  • 文件指针的偏移

  • 打开文件时的读写标识

  • 等等等等

类似于目录项,文件对象也没有实际的磁盘数据,只有当进程打开文件时,才会在内存中产生一个文件对象。

每个进程都有自己打开的一组文件,由file_struct结构体标识,该结构体由进程描述符中的files字段指向。主要包括:

  • fdt

  • fd_array[NR_OPEN_DEFAULT]

  • 引用计数

fd_array 数组指针指向已打开的文件对象,如果打开的文件对象个数 > NR_OPEN_DEFAULT,内核会分配一个新数组,并将 fdt 指向该数组。

除此之外,内核还为所有打开文件维持一张文件表,包括:

  • 文件状态标志

  • 文件偏移量

关于多进程打开同一文件以及文件共享更详细的信息,可以阅读《UNIX 环境高级编程》第三章。

3. 总结

Linux 支持了很多种类的文件系统,包含本地文件系统ext3ext4到网络文件系统NFSHDFS等,VFS 系统屏蔽了不同文件系统的操作差异和实现细节,提供了统一的实现框架,也提供了标准的操作接口,这大大降低了操作文件和接入新文件系统的难度。

4. 参考

  • 深入理解 Linux 内核

  • Linux 内核设计与实现


本文首次发布于知乎专栏:https://www.zhihu.com/column/c_1068890727731240960

发布于: 1 小时前阅读数: 9
用户头像

lecury

关注

还未添加个人签名 2018.04.27 加入

还未添加个人简介

评论

发布
暂无评论
浅谈Linux虚拟文件系统