虚拟文件系统(VFS)是linux的内核软件层,它能够为各种文件系统提供通用的接口,例如linux,unix,windows系统。它是一个位于应用程序和具体文件之间的中间层。VFS引入了一个通用文件模型,它能够表示所有支持的文件系统,它包含有超级块对象、索引节点对象、文件对象、目录项对象。
超级块对象
super_block
存放已安装文件系统的有关信息,对基于磁盘的文件系统,它通常对应于存放在磁盘上的文件系统控制块。对于一个特定的文件系统,超级块的格式是固定的。每一个文件系统都对应一个超级块,它们都会链接到一个超级块链表,而文件系统中的每个文件在打开时都需要在内存分配一个inode
结构它们都要链接到超级块。
索引节点对象
文件系统处理文件所需要的所有信息,都放在索引节点的数据结构中。对于每个文件来说,索引节点对文件是唯一的,其数据结构是inode。要访问一个文件时,一定要通过它的索引才知道它是什么类型的文件(inode有一个union
包含了这个信息)。inode
包含了文件的各种信息。它还包含一个dentry
结构的队列,可以通过它找到与这个文件相联系的所有dentry结构,指向超级块的指针等。每个索引节点对象也包含在文件系统的双向循环链表中,这个链表的头保存在超级块对象中。同时,inodes也存放在一个散列表中,用来加快对索引对象的搜索。
文件对象
文件对象用来描述一个进程怎样与一个打开的文件进行交互,它是在文件被打开的时候创建的,由一个file
数据结构来描述。其存放的主要信息是文件指针,即文件中当前的位置。使用中的文件对象,包含在超级块所确立的链表中。其中的f_list
字段包含了前一个/后一个对象的指针。
目录项对象
VFS
把每个目录看作由若干子目录和文件组成的一个普通文件。在目录项被读入内存后,VFS
就用一个dentry
结构来表示它。对于进程查找路径中的每一个分量,都为其创建一个目录项分量。每个分量都和其对应的索引节点相联系。由于目录项对象可能会经常使用,因此linux使用目录项高速缓存,它包含一个不同状态的目录项对象集合,以及一个用于快速的散列表。
文件操作函数的调用
由于每个文件系统都有其自身的文件操作集合,VFS将inode从磁盘装入内存时,会把文件操作的指针存放在数据结构file_operation中(定义在fs.h中),而该结构的地址存放在索引节点对象的i_fop字段中。进程打开文件时,VFS就用存放在索引节点中的地址初始化新文件对象的f_op字段,使得后续操作能够继续调用这些文件操作函数。f_op是指向文件操作表的指针。事实上除了file_operations
,还有dentry_operations
和inode_operations
,super_operations
,但它们通常只在打开文件的过程中使用,不像file_operations
结构中那些函数常用。
进程与文件
已打开的文件,是属于进程的一项“财产”,归具体的进程所有。在进程的task_struct中,包含两个数据结构。
1 |
struct fs_struct *fs; |
其中,fs_struct
是关于文件系统的信息,它反映的主要是带有全局性的,对具体进程而言的信息,与具体打开的文件关系不大。fs_struct
里面包含有一个指针pwd
,它总是指向进程的“当前工作目录”,每当进程进入不同目录时,内核就使进程的pwd
指向这个目录在内存中的dentry
,而root
指针则指向进程的根目录(还有一个altroot
,指向“替换跟目录”)。files_struct
是关于已打开文件的信息,它的主体是一个file
结构数组,每打开一个文件后,进程就通过一个fid
来访问这个文件,这个fid
实际上就是它在file
数组中的下标。
文件系统的安装和拆卸
每当将一个存储设备安装到现有文件系统中的某个节点时,内核都要为之建立一个vfsmount
结构,它既包含了该设备的信息,又包含了有关安装点的信息。因此fs_struct
中的pwdmnt
总是指向一个vfsmount
结构。
与传统的Unix
内核不同,linuc
允许同一个文件系统被安装许多次。对于每个安装操作,内核通过一个vfsmount
数据结构来保存安装点和安装标志等信息。这个vfsmount
数据结构保存在几个双向循环链表中:父文件系统vfsmount
描述符的地址和安装点dentry
的地址索引散列表;属于每一命名空间的已安装文件系统描述符形成的双向循环链表;每一个已安装文件系统已安装的文件形成等双向循环链表。
安装普通文件系统
mount
系统调用用来安装一个普通文件系统。它的服务例程sys_mount()
向下调用了do_mount
,并最后由do_kern_mount()
函数完成了安装操作的核心。do_kern_mount
首先会查找对应的文件系统类型,然后分配一个新的已安装文件系统的描述符mnt
,并初始化一个新的超级块(如果get_sb
能够在链表中找到对应的超级块对象,说明这个设备被安装了多次,直接返回这个已有超级块的地址),以及mnt
中的一些字段。最后,它会把这个描述符插入到合适的链表中去。在完成这些工作后,该函数将mnt
返回。
安装根文件系统
根文件系统的安装与普通文件系统安装是不同的,它是系统初始化的关键部分。它分为两个部分,首先是安装特殊rootfs
文件系统,它提供一个作为初始安装点的空目录;随后内核在空目录上安装实际根文件系统。为什么要先安装rootfs
?因为它允许内核轻易地改变实际根文件系统,而内核会逐个安装卸载许多个根文件系统。
在rootfs
安装阶段,do_kern_mount
会调用rootfs_get_sb
,为其分配特殊的超级块。随后为进程0分配namespace,将其根目录和当前工作目录设置为根文件系统。
在实际安装阶段,内核调用mount_root
函数,在rootfs
初始根文件系统中创建设备文件/dev/root
。调用sys_chdr("root")
改变进程的当前目录,然后移动rootfs
上的安装文件系统和安装点。而rootfs也并没有被卸载,只是隐藏在根目录下而已。
卸载文件系统
unmount
系统调用由来卸载一个文件系统。相应的sys_unmount
例程对文件名和一组标志进行处理。首先需要对安装点路径名进行查找,随后调用do_unount
,根据标志位进行相应的处理。unmount_tree
会卸载文件系统(及其所有子文件系统)。最后,内核会减少相应文件系统根目录的目录项对象和已安装文件系统描述符的引用计数器值。
路径名查找
进程识别一个文件时,需要分析路径名,并且把它拆分成一个文件名序列。首先需要区分这个路径是相对路径还是绝对路径。这个可以通过路径名的第一个字符是否是“/”
来确定。绝对路径从进程的根目录开始搜索,否则从进程的当前目录开始搜索。
在这个过程中,内核会检查依次检查序列中的每一项,找到与它匹配的目录项,以获得相应的索引节点;再读取这个索引节点的目录文件,继续检查下去。但这个过程没有想象的那么简单:每个目录的访问权都需要检查,文件名可能是与任意一个路径名对应的符号链接,符号链接会包括循环,查找可能会延伸到其他的文件系统。
路径名查找接受一个文件名指针,一个标志,以及一个nameidata
数据结构的地址,这个nameidata
存放了查找操作的结果。nameidata
中的dentry
和mnt
分别指向所解析的最后一个路径分量的目录项对象和已安装文件系统对象。
文件打开/读写
文件打开和关闭,核心是对一个fd
进行操作。这个fd
也就是指向文件对象的指针数组task_struct->files->fd
中分配给新文件的索引。创建一个新的文件对象,然后将它放在fd数组中。
文件读写是对于文件自身来说的,因此其相关的信息存储在inode
中。linux
中,对于文件的读写,实际上是对缓冲区直接进行的,而不是直接在文件上操作。对文件的操作由内核中的线程kflushd
来完成,它们相当于一道流水线上的两道工序。
The link of this page is https://blog.nooa.tech/articles/a4b4101b/ . Welcome to reproduce it!