linux 虚拟文件系统
概述
在linux中,一切皆文件。我们平时见到的各种类型的文件存储诸如ext2、ext3、fat32等等,对于上层的用户进程来说是一种 统一的视图,上层应用程序无需关心底层文件系统的具体实现,因为操作系统在内核层面上做了统一:对上层的用户进程提供了 统一的接口。类比我们学过的设计模式,这个就有点像是代理了,也即是真正的实现有很多种,不过对外提供统一的接口。而在 内核中完成这部分工作的就是虚拟文件系统了。如下图所示,虚拟文件系统在整个系统中的层级:

详解
接下来我们将依次从磁盘存储结构、基本概念、数据结构、以及针对文件的操作入手来详细的了解一下虚拟文件系统,在此之前 需要强调一下不管是内存还是磁盘,其均是通过块来划分存储空间的,使用的时候也是以块为基本单位进行使用的。
基本概念
超级块
超级块又叫做super block,通常可以称之为文件卷控制块,是存储了文件系统控制信息的结构,描述了文件系统的状态、 类型、大小、索引节点、区块数、空余块等信息,存放于磁盘特定的扇区。文件卷控制块的特点是每个文件系统只有一个。
索引节点
索引节点又叫做inode,通常也可以称之为文件控制块,其中包含了文件的详细信息,诸如文件的访问权限、拥有者、大小 数据块位置等信息。对比与超级块,这里的索引节点是每个文件一个。
目录&&文件
文件是一组在逻辑上连续的信息项,包括我们常见的文件、套件字、设备等都是文件的一种, 而目录好比一个文件夹,在linux操作系统中,目录是被当作一种特殊的文件的,用于文件的操作同样可以用在目录上, 要知道在linux系统中,一切皆文件!
目录项
在文件路径中,路径中的每一项都是一个目录项,如我们常见的/home/xyz/z.txt中,/、home、xyz、z.txt均由一个目录项 来代替,区别于上面的内容,目录项表示的是内存空间中的一种数据结构。
文件系统分析
上面介绍了一些基本的概念后,我们来看一下这些基本的概念在实际的物理存储设备上的分布及使用情况:

如上图所示,索引块并非只有一个,而是一个数组,数组中的每一项和一个文件是对应起来的(也即是每一个文件对应了一个索引节点,这里的 文件包含了真实的文件以及目录),其中记录了文件的访问属性、所有者、创建修改时间以及大小等内容,但是并不包含文件的名称。
数据块中记录了文件的名称和对应的节点号码,当然这里的数据块其实对应了文件、目录。上图中介绍了文件系统中磁盘块的具体结构, 接下来通过一个例子来演示一下读取一个文件/etc/a.txt的过程:

如上,文件系统中数据块中包含的内容即是表中所示的内容,我们通过索引节点可以查到对应的目录项的(也即是数据节点), 接下来通过数据节点中记录的信息,继续递归查找,最终找到文件,并获取文件的内容。可以看到文件系统中大量的使用了这种 hashmap的数据结构。
注册、安装文件系统
注册
内核中维护了一个file_systems的链表,注册的过程相当于将被注册的文件系统类型进行初始化,并挂载到这个全局的链表中。 对应的函数为registre_filesystem(struct file_system_type *fs),这里的file_system_type对应的结构如下:
1 | struct file_system_type { |
在注册完文件系统之后,file_system链表中就包含了上述的结构体,其中比较重要的应该就是get_sb方法了,这个 方法会在接下来的安装的过程中使用,直接从待安装系统中读取超级块,一旦超级块被读入内存,这个文件系统就变的可用了。
安装
前面我们有提到过,虚拟文件系统提供了代理的作用(或者类似于java里面的SPI的机制),那么如何将真实的文件系统和VFS 进行关联呢?这就涉及到文件系统的注册了,具体的过程是:
用户态的进程调用mount()命令指定安装点(vfsmount)、安装的设备、安装的类型(此处名词不甚理解),mount系统调用在内核中
的实现为sys_mount,该函数真实调用的是do_mount,获取安装点的目录项对象,然后调用do_add_mount方法,该函数首先创建一个
新的安装点,如:”/“、”/dev”,最后调用graft_tree的方法将节点作为叶子与根目录树挂载起来,安装点的数据结构如下:
1 | struct vfsmount { |
我们以mount一个设/dev/hdb到/home目录为例,其mount的过程可以大致分为两个阶段:
- 查找目录:我们会根据要mount的路径的名称,来找到对应的目录项,也即是dentry(或者说mountpoint:挂载点)
- 增加vfsmount:该过程首先会根据上面注册进来的file_system对象的get_super来创建该文件系统对应的超级块, 接下来就是创建好一个vfsmount对象,并将其和上一步骤中找到的目录项进行关联起来。
文件类型、挂载点、超级块之间的关系
前面简要的说明了文件系统的注册及挂载,接下来我们来展示一下上面提到的一些概念及其之间的关系:

如上图所示,在文件系统类型链表中,每一个节点代表一个文件系统类型,是唯一的(这意味着,如果同一种类型的文件系统如果 挂载了多个,依然是只会存在一个文件系统类型,毕竟嘛,它是类型的概念而并非实例的概念),节点之间通过next指针相连, 而super_block可以理解为文件系统的实例,是用来管理当前注册的文件系统的(这就有点类似于面形对象编程语言中的类和对象的关系了)。 同一种类型的多个super_block则是通过s_instance组成了一个双向的链表。
每mount一个文件系统实例都会有一个mount点和超级块与其对应,mount节点是通过mnt_list链接起来的,超级块是通过super_list链接起来的, mount节点结构中有一个mnt_sb指向该节点上挂载的文件系统的管理对象,也即是超级块。
文件操作
进程访问文件系统
提起进程,就不得不提起进程控制块PCB,其中包含了:进程状态,进程调度,进程标识符,进程通信,进程链接,时间和定时器,文件系统, 虚拟内存信息以及页面管理信息,对称多处理机信息,上下文信息等,其中包含了一个重要的结构体:task_struct,该结构体 中存放了进程的各种属性信息,保存了进程和其打开的文件之间的映射关系:

当用户进程打开文件的时候,内核将会返回一个fd,用于实际的标示该文件,实际上对应了上图中fd执行的数组的下标,数组中对应的元素存放的是文件对象信息,文件对象中的f_dentry,指向了目录项, 目录项中的d_inode则指向了真实的索引节点,索引节点和文件则是一一对应的,最终会指向数据节点,同时inode中也会记录该文件对应的操作函数。
文件系统类型
// TODO
小结
虚拟文件系统的好处是,统一了用户程序的视图,似的
// 文件系统和对应的内存管理系统之间的关系