文章目录
- 一、简介
- 二、进程读写文件示例
- 三、VFS高速缓存
- 参考资料
一、简介
虚拟文件系统(Virtual File System,简称 VFS)是内核中的软件层,为用户空间程序提供文件系统接口。它还在内核中提供了一个抽象层,允许不同的文件系统实现共存。
VFS 系统调用,如 open()、stat()、read()、write()、chmod() 等,在进程上下文中被调用。
VFS 是内核中的一个重要组件,提供了统一的接口,使用户空间程序能够与各种不同的文件系统进行交互,而无需了解底层文件系统实现的具体细节。
当用户空间程序调用诸如 open()、stat()、read()、write()、chmod() 等系统调用时,这些调用是在用户进程的上下文中发起的。VFS 拦截这些系统调用,并将其转换为特定于所访问的文件系统的操作。它负责将请求路由到适当的文件系统驱动程序上,然后驱动程序在实际的文件系统上执行请求的操作。
VFS承载着各种文件系统的共有属性。
VFS只管理挂载到系统中的实际文件系统。
VFS给不同的文件系统提供一个通用的文件模块(比如系统调用sleek())。
通过提供一个通用的接口来与不同的文件系统交互,VFS 使应用程序能够访问和操作文件,而无需了解底层文件系统的类型或实现细节。这种抽象层简化了与文件系统相关的应用程序的开发,提供了更大的灵活性和可移植性。
VFS支持三种类型的文件系统:
(1)磁盘文件系统:ext 文件系统家族、XFS(eXtended File System)、Btrfs(B-Tree File System)等。
(2)网络文件系统:NFS(Network File System)等
(3)基于内存的特殊文件系统:procfs、sysyfs、tmpfs、ramfs等
如下图所示:
VFS中主要有四个类型对象:
struct super_block:超级块对象表示一个挂载的文件系统实例。每个挂载的文件系统都有一个对应的超级块对象,它包含了文件系统的元数据信息,如文件系统类型、挂载选项、设备信息等。超级块对象是文件系统在内核中的核心表示。
请参考:Linux文件系统 struct super_block 结构体解析
struct inode:索引节点对象表示文件或目录。每个文件或目录在文件系统中都有一个对应的索引节点对象。索引节点对象包含了文件或目录的元数据,如文件类型、访问权限、大小、指向数据块的指针等。它是文件系统中的关键概念,用于表示和管理文件和目录的属性和状态。
请参考:Linux文件系统 struct inode 结构体解析
struct file:文件对象表示一个打开的文件。当应用程序通过系统调用打开文件时,内核会创建一个文件对象来跟踪该文件的状态和位置信息。文件对象包含了文件的读写位置、访问模式、引用计数等。它允许内核管理打开的文件,并提供对文件的读写操作。
请参考:Linux文件系统 struct file 结构体解析
struct dentry:目录项对象表示文件系统中的目录项(即文件名)。每个文件或目录在文件系统中都有一个对应的目录项对象,它包含了文件名和与之关联的索引节点对象。目录项对象用于在文件系统中进行路径解析和文件查找操作。
请参考:Linux文件系统 struct dentry 结构体解析
其中super block对象和inode对象在存储介质中都是有实际映射的,即存储介质中也存在超级块和inode。但是由于不同类型的文件系统差异,超级块和inode的结构不尽相同。
如下图所示:
这是ext/2/3/4系列文件系统的格式,可以看到其磁盘数据结构就有super block结构体和inode结构体的描述。
而dentry对象和file对象没有对应的磁盘数据结构,比如VFS根据字符串形式的路径名在操作文件时在内存中创建dentry对象。
dentry对象和file对象这两种并非真正保存在磁盘上,所以这两个对象结构体没有是否被修改的标志,即是否为脏,是否需要写回磁盘的标志。
比如一个打开的文件file内容被修改,虽然file对象没有是否被修改的标志,但是其对应的inode对象有是否被修改的标志:
struct file {
struct path f_path;
}
struct path {
struct dentry *dentry;
} __randomize_layout;
struct dentry {
struct inode *d_inode;
}
二、进程读写文件示例
进程要想往文件系统里面读写数据,需要很多层的组件一起合作。
(1)在应用层,进程在进行文件读写操作时,可通过系统调用如 sys_open、sys_read、sys_write 等。
(2)在内核,每个进程都需要为打开的文件,维护一定的数据结构。
(3)在内核,整个系统打开的文件,也需要维护一定的数据结构。
(4)Linux 可以支持多达数十种不同的文件系统。它们的实现各不相同,因此 Linux 内核向用户空间提供了虚拟文件系统这个统一的接口,来对文件系统进行操作。它提供了常见的文件系统对象模型,例如 inode、directory entry、mount 等,以及操作这些对象的方法,例如 inode operations、directory operations、file operations 等。
(5)然后就是对接的是真正的文件系统,比如ext4 文件系统。
(6)为了读写 ext4 文件系统,要通过块设备 I/O 层,也即 BIO 层。这是文件系统层和块设备驱动的接口。
(7)为了加快块设备的读写效率,我们还有一个缓存层。
(8)最下层是块设备驱动程序。
如下图所示:
系统调用read的流程如下所示:
-->read()
-->sys_read()
-->vfs_read()
-->file->f_op->read_iter()
-->ext4_file_read_iter()
-->generic_file_read_iter()
大部分文件系统(比如ext2/3/4,xfs,nfs等)的读取过程,都是将read_iter置为generic_file_read_iter来实现的。或者在read_iter中做一些简单的处理,然后再调用generic_file_read_iter。
对大部分文件系统来说,其实读取文件数据的流程相差无几。所以VFS提供了一些通用的文件操作函数。
而inode->i_fop是在初始化inode时,在ext4_iget中赋值为&ext4_file_operations,因此:
file->f_op->read_iter() = ext4_file_operations.ext4_file_read_iter()
const struct file_operations ext4_file_operations = {
.read_iter = ext4_file_read_iter,
}
-->ext4_iget()
-->__ext4_iget()
{
struct inode *inode;
inode->i_fop = &ext4_file_operations;
}
图片来自:极客时间趣谈操作系统
图片来自:极客时间趣谈操作系统
三、VFS高速缓存
在虚拟文件系统(VFS)中,提供了三种高速缓存机制来提高文件系统的性能和效率。这些高速缓存机制包括:
(1)索引节点高速缓存(Inode Cache):
索引节点高速缓存是用于缓存文件系统中的索引节点对象(struct inode)的数据结构。
当应用程序访问文件时,内核会首先尝试从索引节点高速缓存中查找对应的索引节点对象,以避免频繁的磁盘访问和索引节点的重复创建。
索引节点高速缓存提高了文件系统的访问速度,减少了文件系统操作的开销。
(2)目录项高速缓存(Dentry Cache):
目录项高速缓存是用于缓存文件系统中的目录项对象(struct dentry)的数据结构。
目录项高速缓存存储了文件名和与之关联的索引节点对象的映射关系。
当应用程序通过路径访问文件时,内核会首先尝试从目录项高速缓存中查找对应的目录项对象,以加速路径解析和文件查找的过程。
目录项高速缓存提高了文件系统的路径解析速度和文件查找效率。
(3)页高速缓存(Page Cache):
页高速缓存用于缓存文件系统中的数据页(或称为文件页)。
当应用程序读取或写入文件时,内核会将文件的数据读取到页高速缓存中进行缓存,以便后续的读取和写入操作能够更快地访问数据。
页高速缓存提高了文件的读取和写入性能,减少了频繁的磁盘访问。
如下图所示:
图片来自于:极客时间Linux性能优化实战
这些高速缓存机制能够显著提升文件系统的性能,减少了与磁盘的交互次数,加快了文件的访问速度。通过缓存常用的索引节点、目录项和数据页,VFS 能够更高效地响应应用程序的文件操作请求,提供更好的用户体验。同时,这些高速缓存机制还遵循一定的缓存策略和管理机制,比如最近最少使用(LRU)等策略,以保证缓存的一致性和正确性。
需要注意的是,缓存机制引入了内存使用和性能之间的权衡。较大的缓存可以通过减少磁盘访问来提高性能,但也会消耗更多的内存资源。因此,可以根据系统的具体需求和可用资源来调整缓存行为和大小。
参考资料
https://zhuanlan.zhihu.com/p/268375848
https://www.thomas-krenn.com/en/wikiEN/images/8/83/Linux-storage-stack-diagram_v6.2.png
极客时间趣谈操作系统
极客时间Linux性能优化实战