记录一下关于VFS文件系统的一些基础学习,和一些理解,如有错误望指正!
参考资料:
《深入理解Linux内核》(第三版)
源码:Linux-4.19.65
一些概念、数据结构
VFS
虚拟文件系统(Virtual Filesystem),是一个内核软件层,用来处理与Unix标准文件系统相关的所有系统调用。其健壮性表现在能为各种文件系统提供一个通用的接口。VFS是用户的应用程序与文件系统实现之间的抽象层,应用程序不需要知道操作的文件是什么文件系统类型,直接与VFS交互,VFS再与不同的文件系统交互。
VFS引入了一个通用的文件模型(common file model),这个模型能够表示所有支持的文件系统。而要实现每个具体的文件系统,必须将其物理组织结构转换为虚拟文件系统的通用文件模型。
下图所示的体系结构显示了用户空间和内核中与文件系统相关的组件之间的关系:
file_system_type
可以使用一组注册函数在 Linux 中动态地添加或删除文件系统。内核保存当前支持的文件系统的列表,可以通过 /proc 文件系统在用户空间中查看这个列表。这个虚拟文件还显示当前与这些文件系统相关联的设备。在 Linux 中添加新文件系统的方法是调用
register_filesystem()
。这个函数的参数定义一个文件系统结构(file_system_type
)的引用,这个结构定义文件系统的名称、一组属性和两个超级块函数。也可以注销文件系统。在注册新的文件系统时,会把这个文件系统和它的相关信息添加到 file_systems 列表中(见图 2 和 linux/include/linux/mount.h)。这个列表定义可以支持的文件系统。在命令行上输入
cat /proc/filesystems
,就可以查看这个列表。from https://www.ibm.com/developerworks/cn/linux/l-linux-filesystem/index.html
源码中对file_system_type
结构的定义如下:
1 | /* include/linux/fs.h */ |
vfsmount
这个结构提供当前挂装的文件系统 。
1 | /* /include/linux/mount.h */ |
super_block 超级块
存放已安装文件系统的有关信息。 它包含管理文件系统所需的信息,包括文件系统名称(比如 ext2)、文件系统的大小和状态、块设备的引用和元数据信息(比如空闲列表等等)。超级块通常存储在存储媒体上,但是如果超级块不存在,也可以实时创建它。可以在/include/linux/fs.h 中找到超级块结构 。
1 | /* /include/linux/fs.h */ |
super_operations
super_block中的一个重要字段是const struct super_operations *s_op
,这个结构定义一组用来管理这个文件系统中inode的函数,其中实现了一些高级操作,比如删除文件或安装磁盘。
每个具体的文件系统都可以定义自己的超级块操作。当VFS需要调用其中一个操作时,比如write_inode()
,它执行下列操作:
1 | sb->s_op->write_inode(); |
super_operations
结构的定义如下:
1 | /* /include/linux/fs.h */ |
inode 索引节点
inode(索引节点对象)表示文件系统中的一个对象,它具有惟一标识符。各个文件系统提供将文件名映射为惟一 inode 标识符和 inode 引用的方法 ,文件名可以随时更改,但是索引节点对文件是唯一的,并且随文件的存在而存在。
inode
结构的定义如下:
1 | /* /include/linux/fs.h */ |
inode_operations
inode_operations
定义直接在 inode 上执行的操作
1 | /* /include/linux/fs.h */ |
file_operations
file_operations
定义与文件和目录相关的方法(标准系统调用)。
1 | /* /include/linux.h */ |
file 文件对象
存放打开文件与进程之间进行交互的有关信息。当进程打开一个文件时,会创建一个file
文件对象,它描述进程怎样与一个打开的文件进行交互。
严格来讲,这个结构体不仅仅和文件系统相关,它也和进程相关联。对于虚拟文件系统而言,它的重要性一般,而相反,这个结构体对进程管理更重要,它记录了进程打开文件的上下文。该结构体,浮于表面,更贴近用户,更贴近进程。
1 | /* /include/linux/fs.h */ |
文件指针
有一个重要信息是文件指针loff_t f_pos
,即文件中当前的位置,下一个操作将在该位置发生。由于几个进程可能同时访问同一文件,因此文件指针必须存放在文件对象而不是索引节点对象中。
file结构体中的file_operations
每个文件系统都有其自己的文件操作集合,执行诸如读写文件这样的操作。当进程打开一个文件时,VFS会用对应的inode->i_fop
中的file_operations结构体
来初始化新文件对象的file->fop
,使得对文件操作的后续调用能够使用这些函数。如果需要VFS也可以通过修改这个字段而修改文件操作的集合。
1 | /* /include/linux/fs.h */ |
path
file
结构体中的struct path f_path
字段,记录了这个文件对象对应的目录项(dentry)和文件系统(vfsmount)
1 | /* /include/linux/path.h */ |
dentry 目录项对象
dentry是目录项缓存,是一个存放在内存里的缩略版的磁盘文件系统目录树结构,他是directory entry的缩写。它存放目录项(也就是文件的特定名称)与对应文件进行链接的有关信息。一旦目录项被读入内存,VFS就把它转换成基于dentry
结构的一个目录项对象。对于进程查找的路径名中的每个分量,内核都为其创建一个目录项对象;目录对象将每个分量与其对应的索引节点相联系。例如在查找路径名/tmp/test
时,内核为根目录/
创建一个目录对象,为根目录下的tmp
项创建一个第二级目录项对象,对/tmp
目录下的test
项创建一个第三级目录项对象。
目录项对象存放在名为dentry_cache
的slab分配器高速缓存中。因此,目录项对象的创建和删除是通过kmem_cache_alloc()
和kmem_cache_free()
实现的。
1 | /* /include/linux/dcache.h */ |
inode dentry file 对象之间的关系
下图是一个简单的实例。三个不同的进程打开同一个文件,其中两个进程使用同一个硬链接,还有一个进程使用另一个硬链接,但是都是指向同一个文件的。在这种情况下,其中的每个进程都使用自己的文件对象,但只需要两个目录项对象,每个硬链接对应一个目录项对象。这两个目录项对象都指向同一个索引节点对象,该索引节点对象标识超级块对象。
与进程相关的文件
fs_struct
文件描述符task_struct
中有一个字段struct fs_struct *fs
,它记录了进程当前的工作目录和根目录等信息,这是内核用来表示进程与文件系统相互作用所必须维护的数据。
1 | /* /include/linux/fs_struct.h */ |
files_struct
文件描述符task_struct
中有一个字段struct files_struct *files
,它记录了进程当前打开的文件的信息。
1 | /* /include/linux/fdtable.h */ |
fdtable
中的fd
是一个指针,指向一个数组, 数组中的每一个元素都是一个struct file
类型的指针 ,对于在fd数组中有相应对象指针的文件来说,数组的索引下标就是文件描述符(file descriptor)。通常,数组的第一个元素(索引为0)是进程的标准输入文件,数组的第二个元素(索引为1)是进程的标准输出文件,数组的第三个元素(索引为2)是进程的标准错误文件。
请注意,借助于dup()、dup2()和fcntl()系统调用,两个文件描述符可以指向同一个打开的文件,也就是说,数组的两个元素可以指向同一个文件对象。例如,当用户将标准错误文件重定向到标准输出文件上时。