欢迎光临散文网 会员登陆 & 注册

一文读懂|Linux 虚拟文件系统(VFS)

2023-08-16 14:53 作者:补给站Linux内核  | 我要投稿

前言

虚拟文件系统是一个很庞大的架构,如果要分析的面面俱到,会显得特别复杂而笨拙,让人看着看着,就不知所云了(当然主要还是笔者太菜),所以这篇博客,以 open() 函数为切入点,来试着分析分析VFS文件系统的运转机理,本文的代码来源于 linux3.4.2。

基础知识

首先我们来看一张图:

(图1)

从这张图中,我们可以看出,系统调用函数并不是直接操作真正的文件系统,而是通过一层中间层,也就是我们说的虚拟文件系统,为什么要有虚拟文件系统?

linux中常见的文件系统有三类:基于磁盘的文件系统;基于内存的文件系统;网络文件系统,(这三类文件系统是共存于文件系统层,为不同类型的数据提供存储服务,这三类文件系统格式是不一样的,也就是说如果不通过虚拟文件系统,直接对真正的文件系统进行读取,有种类型的文件系统,你就得写几种相对应的读取函数),所以说虚拟文件的出现(VFS)就是为了通过使用同一套文件 I/O 系统 调用即可对 Linux 中的任意文件进行操作而无需考虑其所在的具体文件系统格式。

VFS的数据结构

VFS依靠四个主要的数据结构和一些辅助的数据结构来描述其结构信息,这些数据结构表现得就像是对象;每个主要对象中都包含由操作函数表构成的操作对象,这些操作对象描述了内核针对这几个主要的对象可以进行的操作。

1、超级块对象

存储一个已安装的文件系统的控制信息,代表一个已安装的文件系统;每次一个实际的文件系统被安装时, 内核会从磁盘的特定位置读取一些控制信息来填充内存中的超级块对象。一个安装实例和一个超级块对象一一对应。超级块通过其结构中的一个域s_type记录它所属的文件系统类型。


2、索引节点对象

索引节点对象存储了文件的相关信息,代表了存储设备上的一个实际的物理文件。当一个 文件首次被访问时,内核会在内存中组装相应的索引节点对象,以便向内核提供对一个文件进行操 作时所必需的全部信息;这些信息一部分存储在磁盘特定位置,另外一部分是在加载时动态填充的。

3、目录项对象

引入目录项的概念主要是出于方便查找文件的目的。一个路径的各个组成部分,不管是目录还是 普通的文件,都是一个目录项对象。如:在路径 /home/source/test.c 中,目录 /homesource 和文件 test.c都对应一个目录项对象。不同于前面的两个对象,目录项对象没有对应的磁盘数据结构,VFS 在遍历路径名的过程中现场将它们逐个地解析成目录项对象。

4、文件对象

文件对象是已打开的文件在内存中的表示,主要用于建立进程和磁盘上的文件的对应关系。它由 sys_open() 现场创建,由 sys_close() 销毁。文件对象和物理文件的关系有点像进程和程序的关系一样。

当我们站在用户空间来看待 VFS,我们像是只需与文件对象打交道,而无须关心超级块,索引节点或目录项。因为多个进程可以同时打开和操作 同一个文件,所以同一个文件也可能存在多个对应的文件对象。

文件对象仅仅在进程观点上代表已经打开的文件,它 反过来指向目录项对象(反过来指向索引节点)。一个文件对应的文件对象可能不是惟一的,但是其对应的索引节点和 目录项对象无疑是惟一的。


【文章福利】小编推荐自己的Linux内核技术交流群:【749907784】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)     


正篇

经过基础知识点的介绍后,我们开始来探究,当我们通 open() 尝试去打开一个文件的时候,Linux 内部是如何找到对应的存储在硬件上的该文件的数据。

(图2)

(图3)

首先我们来看看上面这两张图,files_struct 主要就是一个 file 指针数组,我们通常说的文件描述符是一个整数,而这个整数正好可以作为下标,从而从 files_struct 中获得 file 结构。

task_struct 为进程描述符,代表的是打开文件的这么一个动作,这里我想表达的知识点:当文件第一次被打开时(打开成功),会建立起如上图所示的联系,返回 fd 文件描述符就这样和底层的存储结构联系在了一起,fd 作为文件描述符,文件作为数据的载体,我们可以将它们理解为密码和保险柜之间的关系,第一打开文件就是相当初始化时设置密码(建立起了密码和保险柜的联系),当我们以后再需要拿取保险柜中的东西时,只需要通过第一次设置的密码就可以对保险柜进程操作。

内核中,对应于每个进程都有一个文件描述符表,表示这个进程打开的所有文件。文件描述表中每一项都是一个指针,指向一个用于描述打开的文件的数据块 ——— file 对象,file 对象中描述了文件的打开模式,读写位置等重要信息,当进程打开一个文件时,内核就会创建一个新的 file 对象。

需要注意的是,file 对象不是专属于某个进程的,不同进程的文件描述符表中的指针可以指向相同的 file 对象,从而共享这个打开的文件。 file 对象有引用计数,记录了引用这个对象的文件描述符个数,只有当引用计数为0时,内核才销毁 file 对象,因此某个进程关闭文件,不影响与之共享同 一个 file 对象的进程.

下面我们来分析具体的代码。

应用层:

应用程序在操作任何一个文件之前,必须先调用 open() 来打开该文件,即通知内核新建一个代表该文件的结构,并且返回该文件的描述符(一个整数),该描述符在进程内唯一。所用到函数为 open()

内核层:

当 open() 系统调用进入内核时候,最终调用的函数为:

该函数位于 fs/open.c 中,下面将会分析其具体的实现过程。

该函数主要调用 do_sys_open() 来完成打开工作,do_sys_open() 的代码分析如下。

(图4)

从代码和流程图的分析中我们知道了,fd 和 file 是如何建立联系 (file 对象中包含一个指针,指向 dentry 对象。dentry 对象代表一个独立的文件路径,如果一个文件路径被打开多次,那么会建立多个 file 对象,但它们都指向同一个 dentry 对象。dentry 对象中又包含一个指向 inode 对象的指针。inode 对象代表一个独立文件。因为存在硬链接与符号链接,因此不同的 dentry 对象可以指向相同的 inode 对象。inode 对象包含了最终对文件进行操作所需的所有信息,如文件系统类型、文件的操作方法、文件的权限、访问日期等)。

那我们反向思考一下,现在我们已经得到 fd,如何找到对应 file, 在当前进程中我们保留着文件描述符,文件描述符中(files_structs),文件描述符中又保留着文件描述表(fatable),通过文件描述符表中 file 类型的指针数组对应的 fd 的项,我们可以找到 file

这篇文章到这里就算结束了,还留有一个工作没有完成,如 do_filp_open(dfd, tmp, flags, mode) 是如何得到 file?,这是一个很复杂的过程,以后有空,笔者会尝试着去分析。


原文作者:Linux内核那些事




一文读懂|Linux 虚拟文件系统(VFS)的评论 (共 条)

分享到微博请遵守国家法律