LKD-虚拟文件系统
虚拟文件系统
Unix使用了四种传统的文件系统相关抽象概念:文件、目录项、索引节点和安装点(挂载点)。
VFS有四类主要的对象类型,分别是:
- 超级块对象,代表一个具体的已安装文件系统;
- 索引节点对象,代表一个具体文件;
- 目录项对象,代表一个目录项,是路径的一个组成部分;
- 文件对象,代表由进程打开的文件。
VFS相应的为这些对象提供了一些操作接口,具体的文件系统都需要实现这些接口,VFS是面向对象思想在linux中的一个重要体现。
- super_operations,包括内核针对特定文件系统能调用的方法;
- inode_operations,包括内核对特定文件所能调用的方法;
- dentry_operations,包括内核针对特定目录所能调用的方法;
- file_operations,包括进程针对已打开文件所能调用的方法。
注意我们接下来讨论的超级块对象,inode对象等都在内存中,分别对应着磁盘中的超级块和inode,即超级块和inode实际上在磁盘中,内存中的对象只是它们的副本。
超级块对象对应磁盘特定扇区中的超级块,在文件系统安装时,超级块对象调用alloc_super从磁盘读取超级块,将相关信息添加到内存的超级块对象中。超级块操作由s_op指针指定,s_op中都是函数指针,具体实现超级块的操作,有些没有的操作函数指针指向NULL。
索引节点对象(struct inode)包含了内核操作普通文件或者目录的全部信息。注意有些文件系统中没有在磁盘上inode(磁盘上),但是内存中必须有struct inode以供内核使用,只有当文件被访问时,内存中的索引节点才被创建。不仅可以表示普通文件或者目录,还可以表示一些特殊文件,例如管道文件、字符设备文件或者块设备文件。索引节点的操作也类似的由i_op指针指定,i_op指向一个inode_operations结构,里面包含了针对索引节点的一些操作。
为了方便查找以及解析路径,避免使用耗时且繁琐的字符串匹配,文件系统引入了目录项这一概念(dentry),dentry在磁盘上没有对应的位置,是VFS根据字符串路径名现场创建的,所以dentry并不区分是否被修改。目录项分为3个状态,分别是使用,未使用以及负状态。使用状态表明此时dentry中的d_inode指向一个有效的inode,且使用计数(d_count)>0,未使用表明d_count=0但是指向的inode是有效的,负状态表示此时inode为NULL,表示inode已经被删除了或者路径被修改了。这三种状态的目录项理想状况下都需要被缓存,以便快速查找路径,但是实际使用中,负状态的目录项因很少使用,所以可以撤销。dentry对象释放后也可以被缓存到slab中。
如果VFS遍历整个文件路径并以此解析为dentry,这是一个非常耗时的工作,因此为了速度考量,使用目录项缓存dcache。(问题目录项缓存中的被使用的单向链表是怎么组织的?一个inode可能会有多个链接,所以也就有多个dentry,也就是说单向链表中保存的是inode的指针?)
目录项缓存主要包括以下三个部分:
- 被使用的单向链表;
- 未被使用和负状态的双向链表;
- 哈希表和对应的哈希函数。
在这里我们考虑一个具体的例子,比如/home/xinze/testdir
这个路径,首先对整个路径字符串做d_hash(这个d_hash与dentry里的函数成员不同),返回一个hash值,根据这个hash值查表d_lookup,如果存在这个hash值,那么就取出对应的dentry,找到指向的inode,如果不存在,那么VFS为每个路径分量解析路径,随后将使用到的dentry放入dcache中。VFS对dentry的操作也保存在dentry_operations中。
文件对象在进程角度上表示进程已打开的文件,但是在实际上只有dentry和inode可以代表文件,所以可以看到在老版本的代码中,file中有一个f_dentry指针,指向一个dentry,进而指向对应文件inode,而在新版本的内核中(5.1.0以后),file中直接使用f_inode成员指向对应文件的inode。同样,因为文件对象在磁盘中并没有对应的部分(inode中维护了文件所占有的磁盘块),所以也无所谓file struct是否是脏的,f_inode指向inode,inode会记录文件是否是脏的。
文件操作也是很重要的一个内容,其中比较重要的是ioctl相关,这里贴一下LKD中的简要解释:
ioctl:
用来给设备发命令参数对,当文件是一个打开的设备节点时,可以通过它进行设备操作,用系统调用
ioctl调用,调用者必须持有大内核锁。
unlocked_ioctl:
与ioctl类似,只是不需要调用者持有大内核锁。
compat_ioctl:
为了安全考虑,被设计成在64位体系结构上对32位也是安全的。和unlocked_ioctl类似,compat_ioctl也不必持有大内核锁。
除了这四种VFS中主要的对象,还涉及一些其他的数据结构。与文件系统相关的数据结构比如file_system_type,每种文件系统对应一个file_system_type。每个文件系统实例对应一个vfsmount,在挂载(安装)时被建立。
而与进程相关的数据结构包括files_struct,fs_struct和namespace结构体。files_struct维护了进程打开的文件以及文件描述符。namespace结构体为每个进程指明了唯一的文件系统命名空间。