20135323符运锦----第十三章知识点总结

第十三章 虚拟文件系统

虚拟文件系统〈有时也称作虚拟文件交换,更常见的是简称VFS)作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口。系统中所有文件系统不但依赖VFS 共存,而且也依靠VFS 系统协同工作.通过虚拟文件系统,程序可以利用标准的Uinx 系统调用对不同的文件系统,甚至不同介质上的文件系统进行读写操作

13.1 通用文件系统接口

VFS 使得用户可以直接使用openO 、read() 和write。这样的系统调用而无须考虑具体文件系统和实际物理介质。现在昕起来这并没什么新奇的〈我们早就认为这是理所当然的〉,但是,使得这些通用的系统调用可以跨越各种文件系统和不同介质执行j 绝非是微不足道的成绩.更了不起的是,系统调用可以在这些不同的文件系统和介质之间执行-一我们可以使用标准的系统调用从一个文件系统拷贝或移动数据到另一个文件系统。老式的操作系统(比如DOS )是无力完成上述工作的,任何对非本地文件系统的访问都必须依靠特殊工具才能完成。

13.2 文件系统抽象层

①为了支持多文件系统, VFS 提供了一个通用文件系统模型,该模型囊括了
任何文件系统的常用功能集和行为。

②VFS 抽象层之所以能衔接各种各样的文件系统,是因为它定义了所有文件系统都支持的、基本的、概念上的接口和数据结构。同时实际文件系统也将自身的诸如“如何打开文件”,“目录是什么”等概念在形式上与VFS 的定义保持一致。因为实际文件系统的代码在统一的接口和数据结构下隐藏了具体的实现细节,所以在VFS 层和内核的其他部分看来,所有文件系统都是相同的,它们都支持像文件和目录这样的概念, 同时也支持像创建文件和删文件这样的操作。

③描述了从用户空间的write()调用到数据被写入磁盘介质的整个流程。一方面, 系统调用是通用VFS 接口,提供给用户空间的前端;另一方面,系统调用是具体文件系统的后端,处理实现细节。接下来的小节中我们会具体看到VFS 抽象模型以及它提供的接口。

13.3 Unix 文件系统

①Unix 使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点。

②从本质上讲文件系统是特殊的数据分层存储结构,它包含文件、目录和相关的控制信息。文件系统的通用操作包含创建、删除和安装等。在Unix中,文件系统被安装在一个特定的安装点上,该安装点在全局层次结构中被称作命名空间,所有的已安装文件系统都作为根文件系统树的枝叶出现在系统中。与这种单一、统一的树形成鲜明对照的就是DOS 和Windows 的表现,它们将文件的命名空间分类为驱动字母

③文件其实可以做一个有序字节串,字节串中第一个字节是文件的头,最后一个字节是文件的尾。每一个文件为了便子系统和用户识别,都被分配了一个便于理解的名字。典型的文件操作有读、写、创建和删除等。

④文件通过目录组织起来。文件目录好比一个文件夹,用来容纳相关文件。因为目录也可以包含其他目录,即子目录,所以目录可以层层嵌套,形成文件路径。路径中的每一部分都被称作目录条目。"ome/wolfman/butter'’是文件路径的一个例子一一根目录/,目录home, wolfman 和文件butter 都是目录条目,它们统称为目录项。在Unix 中,目录属于普通文件,色列出包含在其中的所有文件。由于VFS 把自录当作文件对待,所以可以对目录执行和文件相同的操作。

⑤一直以来, Unix 文件系统在它们物理磁盘布局中也是按照上述概念实现的。比如在磁盘上,文件(目录也属于文件)信息按照索引节点形式存储在单独的块中:控制信息被集中存储在磁盘的超级块中,等等。Unix 中文件的概念从物理上被映射到存储介质。Linux 的VFS 的设计目标就是要保证能与支持和实现了这些概念的文件系统协同工作。

13.4 VFS 对象及其数据结构

VFS 其实采用的是面向对象@的设计思路,使用一组数据结构来代表通用文件对象。这些数据结构类似于对象。因为内核纯粹使用C 代码实现,没有直接利用面向对象的语言,所以内核中的数据结构都使用C 语言的结构体实现,而这些结构体包含数据的同时也包含操作这些数据的函数指针,其中的操作函数由具体文件系统实现。VFS 中有四个主要的对象类型,它们分别是:

·超级块对象,它代表一个具体的已安装文件系统。
·索引节点对象,它代表一个具体文件。
·目录项对象,它代表一个目录项,是路径的一个组成部分。
·文件对象,它代表由进程打开的文件。

每个主要对象中都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法:

• super_o严rations 对象,其中包括内核针对特定文件系统所能调用的方站,比如write_in ode()和sync_fs()等方法。
• inode _operations 对象,其中包括内核针对特定文件所能调用的方搓,比如create()和link()等方法.
•den一operations 对象,其中包括内核针对特定目录所能调用的方棒
• file_ operations 对象,其中包括进程针对已打开文件所能调用的方棒,比如read()和write()等方面。

VFS 使用了大量结构体对象,它所包括的对象远远多于上面提到的这几种主要对象。比如每个注册的文件系统都由file_system_type 结构体来表示,它描述了文件系统及其性能:另外,每一个安装点也都用vfsmount 结构体表示,它包含的是安装点的相关信息,如位置和安装标志等。

13.5 超级块对象

①各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块〈所以称为超级块对象).

②创建、管理和撤销超级块对象的代码位于文件fs/super.c 中。超级块对象通过alloc_super()函数创建并初始化。在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中。

13.6 超级块操作

超级块对象中最重要的一个域是s_op,它指向超级块的操作函数衰。超级块操作函数表由superoperations 结构体表示,定义在文件<linux/fs.h>中,其形式如下:

该结构体中的每一项都是一个指向超级块操作函数的指针,超级块操作函数执行文件系统和索引节点的低层操作。
在这个调用中, sb 是指向文件系统超级块的指针,捂着该指针进入超级块操作函数表s_op,并从表中取得希望得到的write_super()函数,该函数执行写入超级块的实际操作.注意,尽管write_ super()方法来自超级块,但是在调用时,还是要把超级块作为参数传递给它,这是因为C语言中缺少对面向对象的支持,而在C++中,使用如下的调用就足够了:

sb.write_ super();

下面给出super_operation 中,超级块操作函数的用

• struct inode •alloc_inode(struct super_bl。Ck *Sb)

在给定的超级块下创建和初始化一个新的索引节点对象。

• void destroy_inode(struct inode • inode)

用于释放给定的索引节点。

• void dirty_inode(struct inode •inode)

VFS 在索引节点脏(被修改)时会调用此函数。日志文件系统(如ext3 和ext4 )执行该函数进行日志更新。

• void write inode(struct inode •inode,int wait)

用于将给定的索引节点写入磁盘。wait 参数指明写操作是否需要同步。

• void drop_inode(struct inode •inode)

在最后一个指向索引节点的引用被释放后, VFS 会调用该函数。VFS 只需要简单地删除这个索引节点后,普通Unix 文件系统就不会定义这个函数了。

• void delete inode(struct inode *inode)

用于从磁盘上删除给定的索引节点。

• void put_super (struct super_block *sb)

在卸载文件系统时由VFS 调用,用来释放超级块。调用者必须一直持有s_lock

• void write_super( struct super_block *sb)

用给定的超级块更新磁盘上的超级块。VFS 通过该函数对内存中的超级块和磁盘中的超级块进行间步。调用者必须一直持有s_lock

• int sync_fs(struct super_block *sb, int wait)

使文件系统的数据元与磁盘上的文件系统同步。wait 参数指定操作是否同步.

• void write_super_lockfs(struct super_block *sb)

13.7 索引节点对象

①一个索引节点代表文件系统中(但是索引节点仅当文件被访问时,才在内存中创建〉的一个文件,它也可以是设备或管道这样的特殊文件。因此索引节点结构体中有一些和特殊文件相关的项,比如i_pipe 项就指向一个代表有名管道的数据结构, i_bdev 指向块设备结构体, i_cdev 指向字符设备结构体。

②有时,某些文件系统可能并不能完整地包含索引节点结构体要求的所有信息。

13.8 索引节点操作

和超级块操作一样,索引节点对象中的inode_operations 项也非常重要,因为它描述了VFS用以操作索引节点对象的所有方搓,这些方能由文件系统实现.与超级块类似, 对索引节点的操作调用方式如下:

i ->i _op->truncate(i)


下面这些接口由各种函数组成,在给定的节点上,可能由VFS 执行这些函数,也可能由具体的文件系统执行:

• int create(struct inode *dir,struct dentry *dentry, int mode)

VFS 通过系统调用“四.te()和open()来调用该函数,从而为dentry 对象创建一个新的索引节点.在创建时使用mode 指定的初始模式.

• struct dentry * lookup(struct inode 舍dir,struct dentry •dentry)

该函数用来检查给定的inode 所代表的文件是否允许特定的访问模式. 如果允许特定的访问模式, 返回零, 否则返回负值的错误码。多数文件系统都将此区被设置为NULL,使用VFS提供的通用方撞进行检查。这种检查操作仅仅比较索引节点对象中的访问模式位是否和给定的mask。比较复杂的系统(比如支持饰问控制链( ACLS )的文件系统),需要使用特殊的
permissionO 方曲。

13.9 目录项对象

①VFS 把自录当作文件对待,所以在路径/bin/vi 中, bin 和vi 都属于文件一一七m 是特殊的目录文件而vi 是一个普通文件,路径中的每个组成部分都由一个索引节点对象表示.虽然它们可以统一囱索引节点表示,但是VFS 经常需要执行目录相关的操作,比如路径名查找等。路径名查找需要解析路径中的每一个组成部分,不但要确保它有效,而且还需要再进一步寻找路径中的下一个部分。

②为了方便查找操作, VFS 引入了目录项的概念。每个den世y 代表路径中的一个特定部分。对前一个例子来说,人bin 和vi 都属于目录项对象。前两个是目录,最后一个是普通文件。必须明确一点: 在路径中〈包括普通文件在内),每一个部分都是目录项对象。解析一个路径并遍历其分量绝非简单的演练,它是辑时的、常规的字符事比较过程,执行艳时、代码繁琐.目录项对象的引入使得这个过程更加简单。

13.9.1 目景项状态

①目录项对象有三种有效状态:被使用、未被使用和负状态。

一个被使用的目录项对应一个有效的索引节点(即d_inode 指向相应的索引节点〉并且表明该对象存在一个或多个使用者(即d_count 为正值〉。一个目录项处于被使用状态,意味着它正被VFS 使用并且指向有效的数据, 因此不能被丢弃。
一个未被使用的目录项对应一个有效的索引节点( d_inode 指向一个索引节点儿但是应指明VFS 当前并未使用它( d_count 为的。该目录项对象仍然指向一个有效对象,而且被保留在缓存中以便需要时再使用它。由于该目录项不会过早地被撤销,所以以后再需要它肘,不必重新创建,与未缓存的目录项相比,这样使路径查找更迅速。但如果要回收内存的话,可以撤销未使用的目录项。
一个负状态的目录项@没有对应的有效索引节点( d_inode 为NULL ),因为索引节点已被删除了,或路径不再正确了,但是目录项仍然保留,以便快速解析以后的路径查询。比如, 一个守护进程不断地去试图打开并读取一个不存在的配置文件。open()系统调用不断地返回ENOENT,直到内核构建了这个路径、遍历磁盘上的目录结构体并检查这个文件的确不存在为让。即使这个失败的查找很浪费资源,但是将负状态缓存起来还是非常值得的。虽然负状态的目录项有些用处,但是如果有需要,可以撇销它,因为毕竟实际上很少用到它。

13.9.2 目录项缓存

①目录项缓存包括三个主要部分:

· “被使用的”目录项链表. i主链表通过索引节点对象中的i_den町项连接相关的索引节点,因为一个给定的索引节点可能有多个链接,所以就可能有多个目录项对象,因此用一个链表来连接它们.
· “最近被使用的”双向链表。该链表含有未被使用的和负状态的自录项对象.由于该链总是在头部插入目录项,所以链头节点的数据总比链尾的数据要新。当内核必须通过删除节点项回收内存肘,会从链尾删除节点项,因为尾部的节点最旧,所以它们在近期内再次被使用的可能性最小。
·散列表和相应的散列函数用来快速地将给定路径解析为相关目录项对象。

散列表囱数组den_hashtable 表示,其中每一个元素都是一个指向具有相同键值的目录项对象链袭的指针。数组的大小取决于系统中物理内存的大小。

②因为文件谕问呈现空间和时间的局部性,所以对目录项和索引节点进行缓存非常有益。文件访问有时间上的局部性,是因为程序可能会一次又一次地坊问相同的文件。因此,当一个文件被访问时,所缓存的相关目录项和索引节点不久被命中的概率较高。文件访问具有空间的局部性是因为程序可能在同一个目录下访问多个文件,因此一个文件对应的目录项缓存后极有可能被命中,因为相关的文件可能在下次又被使用。

13.10 目录项操作


下面给出函数的具体用法:

• int d_ revalidate(struct dentry *dentry ,struct nanidata•);

该函数判断目录对象是否有效• VFS 准备从dcache 中使用一个目录项肘,会调用该函数。大部分文件系统将该方撞置NULL,因为它们认为dcache 中的目录项对象总是有效的。

• int d hash(struct dent:i:y ’ dentry,struct qstr •name)

该函数为目录项生成散列值,当目录项需要加入到散列表中肘, VFS 调用该函数。

• int d_compare(struct dentry •dentry,

VFS 调用该函数来比较namel 和name2 这两个文件名。多数文件系统使用VFS 默认的操作,仅仅作字符串比较。对有些文件系统,比如FAT,简单的字符串比较不能满足其需要。因为FAT 文件系统不区分大小写,所以需要实现一种不区分大小写的字符串比较函数。注意使用该函数时需要加dcache_lock 锁。

• 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 调用该函数。默认情况下VFS 会调用iput()函数释放索引节点。如果文件系统重载了该函数,那么除了执行此文件系统特殊的工作外,还必须惆用iput()函数。

13.11 文件对象

①VFS 的最后一个主要对象是文件对象。文件对象表示进程已打开的文件。如果我们站在用户角度来看待VFS ,文件对象会首先进入我们的视野。进程直接处理的是文件,而不是超级块、索引节点或目录项。所以不必奇怪:文件对象包含我们非常熟悉的信息〈如访问模式, 当前偏移等〉,同样道理文件操作和我们非常熟悉的系统调用read()和write()等也很类似。

②类似于目录项对象,文件对象实际上没有对应的磁盘数据.所以在结构体中没有代表其对象是否为脏、是否需要写回磁盘的标志。文件对象通过f_den指针指向相关的目录项对象.目录项会指向相关的索引节点,索引节点会记录文件是否是脏的。

13.12 文件操作


具体的文件系统可以为每一种操作做专门的实现,或者如果存在通用操作,也可以使用通用操作。一般在基于Unix 的文件系统上,这些通用操作敖果都不错。并不要求实际文件系统实现文件操作函数表中的所有方捧一-M然不实现最基础的那些操作显然是很不明智的,对不感兴趣的操作完全可以简单地将该函数指针置为NULL

当绘出SETFL 命令时,这个函数用来检查传递给fcntl()系统调用的:flags 的有效性.与大多数VFS 操作一样, 文件系统不必实check_:flags(卜-目前, 只有在NFS 文件系统上实现了。这个函数能使文件系统限制无效的SETFL 标志,不进行限制的话,普通的fcntlO 函数能使标志生效。在NFS 文件系统中,不允许把O_APPEND 和O_DIRECT 相结合.

13.13 和文件系统相关的鼓据结构

①除了以上几种VFS 基础对象外,内核还使用了另外一些标准数据结构来管理文件系统的其他相关数据。第一个对象是file_system_ type , 用来描述各种特定文件系统类型,比如ext3 、ext4或UDF 。第二个结构体是vfsmount,用来描述一个安装文件系统的实例。

②get_sb()函数从磁盘上读取超级块,并且在文件系统被安装时,在内存中组装超级块对象。剩余的函数描述文件系统的属性。

13.14 和避程相关的数据结构

系统中的每一个进程都有自己的一组打开的文件,像报文件系统、当前工作目录、安装点等。有三个数据结构将VFS 层和系统的进程紧密联系在一起,它们分别是: file_ struct 、fs_struct和namespace 结构体.

fd_array 数组指针指向已打开的文件对象.因为NR_OPEN_DEFA叽T 等于BITS_PER_LONG,在64 位机器体系结构中这个宏的值为64 ,所以该数组可以容纳64 个文件对象。如果一进程所打开的文件豆、珠超过64 个,内核将分配一个新数组,并且将他指针指向它。所以对适当数量的文件对象的访问会执行得很快,因为它是对静态数组进行的操作:如果一个进程打开的
文件数量过多,那么内核就需要建立新数组.所以如果系统中有大量的进程都要打开超过64 个文件,为了优化性能,管理员可以适当增NR_OPEN_DEFAULT 的预定义值.

name space 结构体的使用方提却和前两种结构体完全不同,默认情况下,所有的进程共享同样的命名空间(也就是,它们都从相同的挂载表中看到同一个文件系统层次结构〉。只有在进行cloneO 操作时使用CLONE_NEWS 标志,才会给进程一个唯一的命名空间结构体的拷贝。因为大多数进程不提供这个标志,所有进程都继承其父进程的命名空间。因此,在大多数系统上只有一个命名空间,不过, CLONENEWS 标志可以使这一功能失效。

posted @ 2016-05-23 01:46  20135323符运锦  阅读(215)  评论(0编辑  收藏  举报