《Linux内核设计与实现》第十三章——虚拟文件系统概述
20135211
一、通用文件系统接口
- 之所以可以使用这种通用接口(VF)对所有类型的文件系统进行操作,是因为内核在它的底层文件系统接口上建立了一个抽象层。
- 该抽象层使Linux 能够支持各种文件系统。为了支持多文件系统, VFS 提供了一个通用文件系统模型,该模型囊括了任何文件系统的常用功能集和行为。
- 同时实际文件系统也将自身的诸如“如何打开文件”,“目录是什么”等概念在形式上与VFS 的定义保持一致。
二、Unix文件系统
Unix 使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点
- 文件其实可以做一个有序字节串,字节串中第一个字节是文件的头,最后一个字节是文件的
尾; - 文件通过目录组织起来。文件目录好比一个文件夹,用来容纳相关文件。因为目录也可以包
含其他目录,即子目录,所以目录可以层层嵌套,形成文件路径;由于VFS 把自录当作文件对待,所以可以对目录执行和文件相同的操作; - Unix 系统将文件的相关信息和文件本身这两个概念加以区分,例如访问控制权限、大小、
拥有者、创建时间等信息,这些被称作文件的元数据(也就是说,文件的相关数据),被存储在一个单独的数据结构中,该结构被称为索引节点( inode);
三、VFS对象及其数据结构
1. VFS采用的是面向对象的设计思路,使用一组数据结构来代表通用文件对象。包括:
- 超级块对象,它代表一个具体的已安装文件系统。
- 索引节点对象,它代表一个具体文件。
- 目录项对象,它代表一个目录项,是路径的一个组成部分。
- 文件对象,它代表由进程打开的文件。
2. 每个主要对象中都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的
方法,操作对象作为一个结构体指针来实现,此结构体中包含指向操作其父对象的函数指针。(其实,对于其中许多方法来说,可以继承使用VFS 提供的通用函数,如果通用函数提供的基本功能无法满足需要,那么就必须使用实际文件系统的独有方法填充这些函数指针,使其指向文件系统实例)
四、超级块对象
- 概述:
- 各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块
- 创建、管理和撤销超级块对象的代码位于文件fs/super.c 中。
- 超级块对象通过函数alloc_super()创建并初始化。在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中。
- 操作:
- 超级块对象中最重要的一个域是s_op,它指向超级块的操作函数表。定义在文件<linux/fs.h>中
- 当文件系统需要对其超级块执行操作时,首先要在超级块对象中寻找需要的操作方法;
- 由于在C 语言中无法直接得到操作函数的父对象,所以必须将父对象以参数形式传给操作函数;
五、索引结点对象
- 索引节点对象包含了内核在操作文件或目录时需要的全部信息;
- 一个索引节点代表文件系统中(但是索引节点仅当文件被访问时,才在内存中创建〉的一个
文件,它也可以是设备或管道这样的特殊文件; - 操作:
- 和超级块操作一样,索引节点对象中的inode_operations 项也非常重要,因为它描述了VFS用以操作索引节点对象的所有方搓,这些方能由文件系统实现;
- 与超级块类似, 对索引节点的操作调用方式如下:i ->i _op->truncate(i)。其中,i指向给定的索引节点, truncate()函数是由索引节点i 所在的文件系统定义的;
六、目录项对象
-
为了方便查找操作, VFS 引入了目录项的概念。每个den世y 代表路径中的一个特定部分;
-
目录项也可包括安装点。在路径/mmn/cdrom/foo 中,构成元素/、mmn、foo都属于
目录项对象。VFS 在执行目录操作时〈如果需要的话)会现场创建目录项对象; -
目录项对象由den町结构体表示,定义在文件<linux/dcacbe.h>中;
-
目录项对象有三种有效状态:被使用、未被使用和负状态。
- 被使用状态:一个被使用的目录项对应一个有效的索引节点(即d_inode 指向相应的索引节点〉并且表明该对象存在一个或多个使用者(即d_count 为正值〉
- 未被使用状态:一个未被使用的目录项对应一个有效的索引节点( d_inode 指向一个索引节点但是应指明VFS 当前并未使用它( d_count )。该目录项对象仍然指向一个有效对象,而且被保留在缓存中以便需要时再使用它;
- 一个负状态的目录项没有对应的有效索引节点( d_inode 为NULL ),因为索引节点已被删除了,或路径不再正确。
-
目录项缓存
目的:如果VFS 层遍历路径名中所有的元素并将它们逐个地解析成目录项对象,还要到达最深层目录,将是一件非常费力的工作,会浪费大量的时间。所以内核将目录项对象缓存在目录项缓存(简称dcache )中。 -
目录项操作
- dentry _operation 结构体指明了VFS 操作目录项的所有方法。该结构定义在文件<lin皿/dcache.h>中:
-
- int d_ revalidate(struct dentry *dentry,struct namidata•);该函数判断目录对象是否有效• VFS 准备从dcache 中使用一个目录项肘,会调用该函数。(大部分文件系统将该方法置NULL,因为它们认为dcache 中的目录项对象总是有效的。)
- int d hash(struct dentry dentry,struct qstr *name);该函数为目录项生成散列值,当目录项需要加入到散列表中肘, VFS 调用该函数。
- int d_compare(struct dentry •dentry,struct qstr *namel,struct qstr *name2);VFS 调用该函数来比较namel 和name2 这两个文件名
- int d_dele t e (struct dentry *dentry);当目录项对象的d_count 计数值等于0 时, VFS 调用该函数。注意使用该函数需要加dcache_lock 锁和目录项的d_lock
- void d_rel ease(struct dentry *dentry);当目录项对象将要被释放时, VFS 调用该函数,默认情况下,它什么也不傲
- void d_ iput(struct dentry *dentry,struct inode *inode);当一个目录项对象丢失了其相关的索引节点时(也就是说磁盘索引节庚、被删除了), VFS 调用该函数
七、文件对象
- 文件对象包含如访问模式, 当前偏移等信息;文件操作常见的系统调用read()、write()等也很类似;
- 文件对象仅仅在进程观点上代表已打开文件,它反过来指向目录项对象〈反过来指向索
引节点〉,其实只有目录项对象才表示巳打开的实际文件。虽然一个文件对应的文件对象不是唯
一的,但对应的索引节点和目录项对象无疑是唯一的; - 文件操作——具体的文件系统可以为每一种操作做专门的实现,或者如果存在通用操作,也可以使用通用操作
八、文件系统相关的数据结构
- 除了以上几种VFS 基础对象外,内核还使用了另外一些标准数据结构来管理文件系统的其他相关数据。第一个对象是file_system_ type, 用来描述各种特定文件系统类型;第二个结构体是vfsmount,用来描述一个安装文件系统的实例。
- file_system_ type 结构体被定义在<linux/fs.h>中
- get_sb()函数从磁盘上读取超级块,并且在文件系统被安装时,在内存中组装超级块对象。剩余的函数描述文件系统的属性。
- 每种文件系统,不管有多少个实例安装到系统中,还是根本就设有安装到系统中,都只有一个file_system_ type 结构。
- vfsmount 结构被定义在<linux/mount.h>中
- 理清文件系统和所有其他安装点阔的关系,是维护所有安装点链表中最复杂的工作。vfsmount 结构体中维护的各种链表就是为了能够跟踪这些关联信息。
九、和进程相关的数据结构
以下三个数据结构将VFS 层和系统的进程紧密联系在一起
- file_ struct 结构体定义在文件<linux/fdtable.h>中。该结构体由进程描述符中的files 目录项指向。所有与单个进程(per-process)的相关的信息〈如打开的文件及文件描述符〉都包含在其中;
- fs_struct. 该结构由进程描述符的fs域指向。它包含文件系统和进程相关的信息,定义在文件<linux/fs_ struct.h>中;
- 是namespace 结构体=定义在文件<linux/mmt_namespace.h>中,由进程描述符中的mmt_namespace 域指向。2 .4 版内核以后,单进程命名空间被加入到内核中,它使得每一个进程在系统中都看到唯一的安装文件系统一一不仅是唯一的根目录,而且是唯一的文件系统层次结构。
十、总结
Linux支持多种类的操作系统,正是因为vfs的存在,才使得Linux文件系统变得更加简单