文件系统—虚拟文件系统(一)

为了保证Linux的开放性,设计者必须考虑如何使Linux除了支持Ext2文件系统外,还能支持其它各种不同的文件系统,例如日志文件系统、集群文件系统以及加密文件系统等。因此,就必须把各种不同文件系统的操作和管理纳入到一个统一的框架中,使得用户程序通过同一个文件系界面,即同一组系统调用,能对各种不同的文件系统和文件操作。用户程序不必关心各种不同文件系统的实现细节,而使用系统提供统一、抽象、虚拟的文件系统界面。这种统一的框架就是虚拟文件系统转换,一般简称虚拟文件系统(VFS)。

一、虚拟文件系统引入

Linux最初采用MINIX文件系统,但MINIX是一种教学用操作系统,其文件系统大小限制在64MB,文件名长度也限制在14个Byte。Linux经过一段时间的改进和发展,尤其吸取UNIX文件系统多年改进所积累的经验,最后形成Ext2文件系统。可以说,Ext2文件系统就是Linux文件系统。

虚拟文件系统所提供的抽象界面主要由一组标准的、抽象的操作构成,例如读、写、lseek()等,这些函数以系统调用的形式供用户程序调用。用户程序调用这些系统调用函数时,无须关心所操作的文件属于哪个文件系统,这个文件系统是如何设计和实现的。

在Linux内核中,VFS与具体的文件系统关系如图8.4所示。

Linux的目录建立了一棵根目录为/的树。根目录包含在根文件系统中,Linux中,这个根文件系统类型通常是Ext2类型的。其他所有的文件系统都可以被挂载到根文件系统的子目录中。例如,通过mount命令,将DOS格式的磁盘分区(即FAT文件系统)挂载到Linux系统中,就可以访问DOS的文件。

例如,用户输入以下shell命令:

$ cp /mnt/dos/TEST /tmp/test

其中/mnt/dos是DOS磁盘的一个挂载点, /tmp是一个标准的Ext2文件系统的目录。如图8.4所示,VFS是用户空间的应用程序与具体文件系统之间的抽象层。因此,cp程序不需要知道/mnt/dos/TEST和/tmp/test是哪种文件系统类型。相反,cp程序通过系统调用直接与VFS交互。cp所执行的代码片段如下:

为深入理解图8.4,结合上面代码片段,说明内核如何把read()转换为对DOS文件系统的一个调用。用户应用程序对read()的调用引起内核调用sys_read(),这完全与其他系统调用类似。

文件在内核中由一个file数据结构表示(include/linux/fs.h):

file结构体中含有一个f_op的成员,其类型为file_operations结构。file_operations结构体中包含指向各种函数的指针,例如:

备注:ssize_t实际为无符号整型。

每种文件系统都有自己的file_operations结构,该结构中的域几乎全是函数指针。因此,当应用程序调用read()系统调用时,就会陷入内核而调用sys_read(),而sys_read()调用vfs_read(),如下:

从代码看出,通过file结构中的指针f_op就会调用DOS文件系统的read():

file->f_op->read(file, buf, count, pos);

类似,write()操作也会引发一个与输出文件相关的Ext2写函数(而不是DOS文件系统的写函数)执行。

不同的文件系统通过不同的程序来实现各种功能,但是,与VFS之间的界面则是有明确定义的。这个界面就是file_operations结构。

2、VFS中对象的演绎

虚拟文件系统在磁盘或其他存储介质上没有对应的存储信息。那么,一个虚无的文件系统到底是怎样形成的?尽管Linux支持多达几十种文件系统,但这些真实的文件系统并不是一下子都挂在系统中的,实际上是按需被挂载的。这个虚的VFS的信息都来源于实的文件系统,所以VFS必须承载各种文件系统的共有属性。另外,这些实的文件系统只有安装到系统中,VFS才予以认可,VFS只管理挂载到系统中的实际文件系统。

VFS承担管家的角色,那么分析VFS要管理哪些对象。Linux在文件系统设计中,吸取UNIX的设计思想。UNIX在文件系统的设计中抽象出四个概念:文件、目录项、索引结点和超级块

从本质上讲,文件系统是特殊的数据分层存储结构,它包含文件、目录和相关的控制信息。文件系统的典型操作有创建、删除和挂载等。如之前所述的,一个文件系统被挂载在根文件系统的某个枝叶上,这个枝叶就是挂载点,挂载点在全局的层次结构中具有独立的命名空间。

如何给文件一个明确的定义?其实可以把文件看作是一个有序字节串,字节串中第一个字节是文件的头,最后一个字节是文件的尾。典型的文件操作如读、写、创建和删除等。

文件系统通过目录来组织文件。文件目录就是一个文件夹,用来容纳文件的。因为目录可以包含子目录,所以目录可以逐层嵌套,形成文件路径。路径中的每一部分被称作目录项,例如/home/test/myfile是文件路径的一个例子,其中根目录是/,home,test,和文件myfile都是目录项。在UNIX中,目录属于普通文件,所以对目录和文件的可以实施相同操作。

文件系统使用索引结点(Index node)来描述文件的属性(例如文件名、访问控制权限、大小、拥有者、创建时间等信息),为什么不叫文件控制块而叫索引结点,关键是有一个索引号的属性可以唯一地标识文件。

以上描述了文件、目录项、索引结点,还有一个超级块对象,超级块是一种包含文件系统信息的数据结构。

通过上面的描述,总结出VFS中以下4个主要对象。

超级块对象:描述已挂载文件系统。

索引结点对象:描述一个文件。

目录项对象:描述一个目录项,是路径的组成部分。

文件对象:描述由进程打开的文件。

备注:VFS将目录作为一个文件来处理,不存在目录对象。目录项不同于目录,但目录却和文件相同。

3、VFS的超级块

超级块用来描述整个文件系统的信息。对每个具体的文件系统,都有各自的超级块,如Ext2超级块和Ext3超级块,它们存放在磁盘上。当内核在对一个文件系统进行初始化和注册时在内存为其分配一个超级块,这就是VFS超级块。VFS超级块是各种具体文件系统在挂载时建立的,并在这些文件系统卸载时被自动删除,可见,VFS超级块只存在于内存中。

1)超级块数据结构

VFS超级块的数据结构为super_block,该结构及其主要域的含义如下:

extern struct list_head super_blocks;

所有超级块对象以双向循环链表链接在一起,链表中第一个元素用super_blocks变量表示。

s_list:存放指向链表相邻元素的指针

sb_lock:自旋锁保护链表免受多处理器上的同时访问

s_fs_info:指向具体文件系统的超级块,例如,超级块对象指的是Ext2文件系统,这个成员就指向ext2_sb_info结构,该结构包括与磁盘分配位图等相关数据,不包含与VFS的通用文件模型相关的数据。

通常,为了提高效率,由s_fs_info域所指向的数据被复制到内存。任何磁盘文件系统都需要访问和更改自己的磁盘分配位图,以便分配和释放磁盘块。VFS允许这些文件系统直接对内存超级块的s_fs_info域进行操作,而无须访问磁盘。

但这种方法带来一个新问题:有可能VFS超级块最终不再与磁盘上相应的超级块同步。因此,引入一个s_dirt标志来表示超级块是否是脏的,磁盘上的数据是否必须要更新。缺少同步机制还导致一个问题:当机器的电源突然断开用户来不及正常关闭系统时,出现文件系统崩溃。Linux通过周期性地将所有脏的超级块写会磁盘来减少这个问题带来的危害。

与超级块关联的方法是超级块操作函数集。这些操作函数集由super_operations来描述的,其主要函数如下:

每一种文件系统都有自己的super_operations操作实例。其主要函数功能描述如下:

write_super:将超级块的信息写回磁盘

put_super:释放超级块对象

write_inode:把inode写回磁盘

4、VFS的索引结点inode

文件系统处理文件所需要的所有信息(文件属性)都放在索引结点的数据结构中。文件名可以随时更改,但是索引结点对文件是唯一的,并且随文件的存在而存在。说明:具体文件系统的索引结点存放在磁盘上,是一种静态结构,要使用它,必须调入内存,填写VFS的索引结点,因此,也称VFS索引结点是动态结点。

VFS索引结点结构体的主要成员如下:

下面进一步描述inode结构体。

1)在同一个文件系统中,每个索引结点号都是唯一的,内核可根据索引结点号的哈希值查找其inode。

2)inode中的设备号i_rdev。如果索引结点所代表的不是常规文件,而是某个设备,就有个设备号,这就是i_rdev。

3)每个VFS索引结点都会复制磁盘索引结点包含的一些数据,比如文件占用的磁盘块数。如果i_state成员的值等于I_DIRTY,该索引结点就是脏的,对应的磁盘索引结点必须更新。每个索引结点对象总是出现在下列循环双向链表的某个链表中:未用索引结点链表、正在使用索引结点链表和脏索引结点链表。这三个链表都是通过索引结点的i_list成员链接在一起的。

4)属于正在使用或脏链表的索引结点对象也同时放在一个哈希表中。哈希表加快了对索引结点对象的搜素,前提是内核要知道索引结点号及对应文件所在文件系统的超级块对象的地址。

与索引结点关联的方法叫做索引结点操作函数集,由inode_operations结构体来描述:

其中主要函数功能如下:

create:创建一个新的磁盘索引结点

lookup:查找一个索引结点所在的目录

link:创建一个新的硬链接

unlink:删除一个硬链接

symlink:为符合链接创建一个新的索引结点

mkdir:为目录项创建一个新的索引结点

对于不同的文件系统,其每个函数的具体实现不同,也不是每个函数都必须实现,没有实现的函数对应设置为NULL。

5、目录项对象

每个文件有一个索引结点inode数据结构和一个目录项dentry数据结构。dentry结构体中有一个指向inode结构体的指针d_inode。既然inode结构和dentry结构都是对文件属性的描述,为什么不把这两个结构合二为一呢?这是因为二者描述的目标不同,dentry结构表示逻辑意义上的文件,所描述的是文件逻辑上的属性,因此,目录项对象在磁盘上没有对应的映像;而inode结构表示物理意义上的文件,记录的是物理上的属性,对于一个具体的文件系统,如Ext2,Ext2 inode结构在磁盘上有对应的映像。一个索引结点对象可能对应多个目录项对象

dentry结构体(include/linux/dcache.h)的主要成员为:

一个有效的dentry结构必定有一个inode结构,因为一个目录项要么代表一个文件,要么代表一个目录,目录也是一个文件。只要dentry结构是有效的,则其指针d_inode必定指向一个inode结构。反过来则不然,一个inode结构可能对应着不止一个dentry结构,也就是说,一个文件可以有不止一个文件名或路径名。这是因为一个已经建立的文件可以被链接到其它文件名。所以在inode结构中有一个链表i_dentry,凡是代表着同一个文件的所有目录项都通过dentry结构中的d_alias成员挂入相应inode结构中的i_dentry链表。

在内核中有一个哈希表dentry_hashtable,是一个指向hlist_bl_head结构的指针。一旦在内存中建立起一个目录结点的dentry结构,该dentry结构就通过d_hash成员链入哈希表中的某个链表中。

内核中还有一个dentry_unused,凡是已经没有用户使用的dentry结构就通过d_lru成员挂入这个链表。

dentry结构中除了d_alias,d_hash,d_lru三个链表外,还有d_child及d_subdirs链表。当该目录结点有父目录时,则dentry结构通过d_child挂入其父结点的d_subdirs链表中,同时通过指针d_parent指向其父目录的dentry结构,而它自己的各个子目录的dentry结构则挂在其d_subdirs成员指向的链表中。

从上面描述看出,一个文件系统中所有目录项结构或组织为一个哈希表,或组织为一棵树,或按照某种需要组织为一个链表,这将为文件访问和文件路径搜索奠定基础。

对目录项操作的一组函数叫目录项操作函数集,由dentry_operations结构体来描述:

该结构体中函数主要功能描述如下。

d_revalidate:判断目录项是否有效

d_hash:生成一个哈希值

d_compare:比较两个文件名

d_delete:删除d_count域为0的目录项对象

d_release:释放一个目录项对象

d_iput:调用该方法丢弃目录项对应的索引结点

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值