分类: LINUX
2009-04-14 16:25:00
虚拟文件系统(有时也称作虚拟文件交换,更常见的是简称VFS)作为内核子系统,为用户空间程序提供了文件系统相关的接口。系统中所有文件系统不但依赖VFS共存,而且也依靠VFS系统协同工作。通过虚拟文件系统,程序可以利用标准的UNIX文件系统调用对不同介质上的不同文件系统进行读写操作。
12.1 通用文件系统接口
VFS使得用户可以直接使用open()、read()和write()这样的系统调用而无需考虑具体文件系统和实际物理介质。现在听起来这并没什么新奇的——我们早就认为这是理所当然的——但是,使得这些同通用的系统调用可以跨所有介质和文件系统执行,绝非是微不足道的成绩。更为了不起的是,系统调用可以在这些不同的文件系统和介质之间执行——我们可以用标准的系统调用从一个文件系统拷贝或移动数据到另一个文件系统。正是由于包括Linux在内的现代操作系统引入了抽象层,通过虚拟接口访问文件系统,才使得这种协作性和通用性成为可能。
12.2 文件系统抽象层
之所以可以使用这种通用接口对所有类型的文件系统进行操作,是因为内核在它的底层文件系统接口上建立了一个抽象层。该抽象层使Linux能够支持各种文件系统。为了支持多文件系统,VFS提供了一个通用文件系统模型,改模型囊括了我们所能想到的文件系统的常用功能和行为。
VFS抽象层之所以能够衔接各种各样的文件系统,是因为它定义了所有文件系统都支持的基本的、概念上的接口和数据结构。同时实际文件系统也将自身的诸如“如何打开文件”,“目录是什么”等概念在形式上与VFS的定义保持一致。因为实际文件系统的代码在统一的接口和数据结构下隐藏了具体的实现细节,所以在VFS层和内核的其他部分看来,所有文件系统都是相同的。
内核通过抽象层能够方便、简单的支持各种类型的文件系统。实际文件系统通过编程提供VFS所期望的抽象接口和数据结构,这样,内核就可以毫不费力的和任何文件系统协同工作。实际上在内核中,除了文件系统本身外,其他部分并不需要了解文件系统的内部细节。比如一个简单的用户空间程序执行如下的操作:
write(f, &buf, len);
该代码将&buf指针指向的、长度为len字节的数据写入文件描述符f对应的文件的当前位置。这个用户调用首先被一个通用sys_write()处理,sys_write()函数要找到f所在的文件系统实际给出的是哪个写操作,然后再执行该操作。实际文件系统的写方法是文件系统实现的一部分,数据最终通过该操作写入介质。
12.3 UNIX文件系统
UNIX使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点(mount point)。
从本质上讲,文件系统是特殊的数据分层存储结构,它包含文件、目录和相关的控制信息。文件系统的通用操作包含创建、删除和安装等等。在UNIX中,文件系统被安装在一个特定的安装点上,该安装点在全局层次结构中被称作命名空间,所有的已安装文件系统都作为根文件系统树的枝叶出现在系统中。
文件其实可以看作是一个有序字节串,字节串中第一个字节是文件的头,最后一个字节是文件的尾。文件通过目录组织起来。文件目录好比一个文件夹,用来容纳相关文件。在UNIX中,目录属于普通文件,它列出包含在其中的所有文件。由于VFS把目录当作文件对待,所以可以对目录执行和文件相同的操作。
UNIX系统将文件的相关信息(如访问控制权限、大小、拥有者、创建时间等信息)和文件本身这两个概念加以区分,例如访问控制权限、大小、拥有者、创建时间等等信息。文件相关信息,有时被称作文件的元数据,被存储在看一个单独的数据结构中,该结构被称作索引节点(inode)。
所有这些信息都和文件系统的控制信息密切交融,文件系统的控制信息存储在超级块中,超级块是一种包含文件系统信息的数据结构。有时,把这些收集起来的信息称为文件系统数据元,它集单独文件信息和文件系统的信息于一身。一直以来,Unix文件系统在它们物理磁盘布局中也是按照上述概念实现的。比如说在磁盘上,文件信息按照索引节点形式存储在单独的块中,控制信息被集中存储在磁盘的超级块中。
12.4 VFS对象及其数据结构
VFS其实采用的是面向对象的设计思路,使用一族数据结构来代表通用文件对象。这些数据结构表现的就像是对象。因为内核纯粹使用C代码实现,没有直接利用面向对象的语义,所以内核中的数据结构都使用C结构体实现,但这些数据结构包含数据的同时也包含操作这些数据的函数指针,其中的操作函数由具体的文件系统实现。
VFS中有四个主要的对象类型,它们分别是:
(1)超级块对象,它代表一个已安装文件系统。
(2)索引节点对象,它代表一个文件。
(3)目录项对象,它代表一个目录项(注意不是目录,目录是一个文件),是路径的一部分。
(4)文件对象,它代表由进程打开的文件。
每个主要对象中都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法。
(1)super_operations对象,其中包括内核针对特定文件系统所能调用的方法,比如read_inode()和sync_fs()等方法。
(2)inode_operations对象,其中包括内核针对特定文件所能调用的方法,比如creat()和link()等方法。
(3)dentry_operations对象,其中包括内核针对特定目录所能调用的方法,比如d_compare()和d_delete()等方法。
(4)file对象,其中包括进程对已打开文件所能调用的方法,比如read()和write()等方法。
操作对象作为一个指针结构体被实现,此结构体中包含了指向操作其父对象的函数指针。
12.5 超级块对象
超级块对象由super_block结构体表示,定义在文件
超级块操作
超级块对象中最重要的一个域是s_op,它指向超级块的操作函数表。超级块操作函数表由super_operations结构体表示,定义在文件
struct super_operations {
struct inode *(* alloc_inode)(struct super_block *sb);
void ( *destroy_inode)(struct inode *);
................................
};
该结构体中的每一项都是一个指向超级块操作函数的指针,超级块操作函数执行文件系统和索引节点的低层操作。当文件系统需要对其超级模块执行操作时,首先要在超级块对象中寻找需要的操作方法。比如,如果一个文件系统要写自己的超级块,需要调用:
sb->s_op->write_super(sb);
这里的sb是指向文件系统超级块的指针,沿着该指针进入超级块操作函数表,并从表中取得希望得到的write_super()函数,该函数执行写入超级块的实际操作。
下面对super_operation中超级块的操作函数一一说明:
(1)struct inode * alloc_inode(struct inode *inode) 在给定的超级块下创建并初始化一个新的索引节点对象
(2)void destroy_inode(struct inode *inode) 该函数用于释放给定的索引节点。
(3)void read_inode(struct inode *inode) 该函数以inode->i_ino为索引,从磁盘上读取索引节点,并填充内存中对应的索引节点结构的剩余部分。
(4) void dirty_inode(struct inode *inode) VFS在索引节点脏(被修改)时会调用此函数。
(5)void write_inode(struct inode *inode, int wait) 该函数用于将给定的索引节点写入磁盘。wait参数指明写操作是否需要同步。
(6)void put_inode(struct inode *inode) 该函数用于释放给定索引节点。
(7)void delete_inode(struct inode *inode) 该函数用于从硬盘上删除给定的索引节点。
(8)void put_super(struct super_block *sb) 该函数在卸载文件系统时由VFS调用,释放超级块。
(9)void write_super(struct super_block *sb) 该函数用给定的超级块更细磁盘上的超级块。
(10)int sync_fs(struct super_block *sb, int wait) 该函数使文件系统的数据元与磁盘上的文件系统同步。wait参数指定操作是否同步。
(11)void write_super_lockfs(struct super_block *sb) 该函数首先禁止对文件系统做改变,在使用给定的超级块更新磁盘上的超级块。
(12)void unlock(struct super_block *sb) 该函数对文件系统解除锁定,是(11)的逆操作。
(13)int statfs(struct super_block *sb, int *flags, char *data) VFS通过调用该函数获取文件系统状态。指定文件系统相关的统计信息将放置在statfs中。
(14)int remount_fs(struct super_block *sb, int *flags, char *data) 当指定新的安装选项重新安装文件系统时,VFS会调用该函数。
(15)void clear_inode(struct inode *) VFS调用该函数释放索引节点,并清空包含相关数据的所有页面。
(16)void umount_begin(struct super_block *sb) VFS调用该函数中断安装操作。该函数被网络文件系统使用,如NFS。
所有以上函数都是由VFS在进程上下文中调用。
12.6 索引节点对象
索引节点对象包含了内核在操作文件或目录时需要的全部信息。索引节点对象定义在文件
索引节点操作
和超级模块操作一样,索引节点对象中的inode_operations项也非常重要,因为它描述了VFS用以操作索引节点对象的所有方法——这些方法由文件系统实现。
12.7 目录项对象
VFS把目录当作文件对待,所以在路径/bin/vi中,bin和vi都属于文件——bin是目录文件,vi是普通文件,路径中的每个组成部分都由一个索引节点对象表示。为了方便查找操作,VFS引入了目录项的概念。每个dentry代表路径中的一个特定部分。对前一个例子来说,/、bin和vi都属于目录项对象。前两个是目录,后一个是普通文件。必须明确一点:在路径中,包括普通文件在内,每一个部分都是目录项对象。也就是说,一个文件并不一定就只和一个对象相对应,在不同的情境下,它对应不同的对象。
不同于前面的两个对象,目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它。而且由于目录项对象并非真正保存在磁盘上,所以目录项结构体没有是否被修改的标志(也就是是否为脏,是否需要写回磁盘的标志)。
目录项对象有三种状态:被使用、未被使用和负状态。
一个被使用的目录项对应一个有效的索引节点(即d_inode指向相应的索引节点)并且表明该对象存在一个或多个使用者(即d_count为正值)。
一个未被使用的目录项对应一个有效的索引节点,但是VFS当前并未使用它(d_count为0)。该目录项对象仍然指向一个有效对象,而且被保留在缓存中以便需要时再使用它。
一个负状态的目录项没有对应的有效节点,索引节点已经被删除或路径不再正确了,但是目录项仍然保留着,以便快速解析以后的路径查询。
12.8 文件对象
文件对象表示进程已打开的文件。如果我们站在用户空间来看待VFS,文件对象会首先进入我们的视野。文件对象是已打开的文件在内存中的表示。该对象由相应的open()系统调用创建,由close()系统调用销毁,所有的这些文件相关的调用实际上都是文件操作表中定义的方法。因为多个进程可以同时打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅在进程观点上代表已打开文件,它反过来指向目录项对象(反过来指向索引节点),其实只有目录项对象才表示已打开的实际文件。虽然一个文件对应的文件对象不是唯一的,但对应的索引节点和目录项无疑是唯一的。
文件对象由file结构体表示,定义在文件
文件操作
和VFS的其他对象一样,文件操作表在文件对象中也很重要。跟file结构体相关的操作与系统调用很类似,这些操作是标准UNIX系统调用的基础。文件对象的操作由file_operations结构体表示,定义在
具体的文件系统可以为每一种操作做专门的实现,如果存在通用操作,也可以使用通用操作。一般在基于Unix的文件系统上,这些通用操作效果都不错。并不要求实际文件系统实现文件操作中的所有方法——但是不实现最基础的那些操作显然是很不明智的——对于不感兴趣的操作完全可以简单的将该函数指针置为NULL。
12.9 和文件系统相关的数据结构
除了以上几种VFS基础对象外,内核还使用了另外一些标准数据结构来管理文件系统的其他相关数据。第一个结构体是file_system_type,用来描述各种特定文件系统类型,比如ext3或XFS。第二个结构体是vfsmount,用来描述一个安装文件系统的实例。因为Linux支持多种不同的文件系统,所以内核必须由一个特殊的结构来描述每种文件系统的功能和行为。
struct file_system_type {
const char *name; /* 文件系统的名字 */
struct subsystem subsys; /* sysfs子系统对象 */
int fs_flags; /* 文件系统类型标志 */
struct super_block *(* get_sb)(strcut file_system_type *, int, char *, void *);
void (*kill_sb) (struct super_block *);
struct module *owner; /* 文件系统模块 */
struct file_system_type *next; /* 链表中下一个文件系统类型 */
struct list_head fs_supers; /* 超级块对象链表 */
}; get_sb()函数从磁盘上读取超级块,并且在文件系统被安装时,在内存中组装超级块对象。
每个文件系统,不管有多少实例被安装到系统中,还是根本就没有安装到系统中,都只有一个file_system_type结构代表着它。更有趣的是,当文件系统被实际安装时,将有一个vfsmount结构体在安装点被创建。该结构体用来代表文件系统的实例——换句话说,代表一个安装点。
12.10 和进程相关的数据结构
系统中的每一个进程都有自己的一组打开的文件,像根文件系统、当前工作目录、安安装点等等。有三个数据结构将VFS和系统的进程紧密联系在一起,它们分别是:file_struct、fs_struct和namespace结构体。
file_struct结构体定义在文件
和进程相关的第二个数据结构是fs_struct。该结构由进程描述符的fs域指向。它包含文件系统和进程相关的信息,定义在文件
第三个也最后一个相关结构体是namespace,它定义在
注:此文转载出处为http://hi.baidu.com/to_qgq/blog/item/be46b216da006c1b972b432d.html
和linux内核设计与实现一书。