虚拟文件系统
虚拟文件系统
虚拟文件系统
是负责组织和管理文件系统
的,就像虚拟内存
用来实现管理内存系统
.而在计算机中因为存储介质的问题,所以通常存在多个文件系统
.
比如:硬盘,光盘,闪盘这些存储介质的不同,还有例如一些操作系统的不同,以及文件特性的不同,因此不同的文件系统在实现上会有所不同.
- 虚拟文件系统和文件系统类似,也有着自己的数据结构:
超级块
,inode
,目录项
等,也就如同文件系统去保存着相应文件的数据那样,有着相应的内存结构。而正是通过这种类似的结构,虚拟文件系统才得以提供给文件系统一种通用的接口供他们使用。
1.虚拟文件系统定义在文件系统的方法
当我们想要读取一个文件或者想要打开一个文件的时候,通常都需要依靠VFS为我们提供的文件接口,让他们得以读写打开及关闭.
例如:当我们使用C语言去打开一个文件,或者读写时
-
打开文件:
#include <stdio.h> int main() { FILE* file = fopen("file.txt", "r"); if (file == NULL) { perror("Error opening file"); return 1; } // 文件已成功打开,可以进行读取操作 // ... fclose(file); // 关闭文件 return 0; }
-
读取文件:
#include <stdio.h> int main() { FILE* file = fopen("file.txt", "r"); if (file == NULL) { perror("Error opening file"); return 1; } char buffer[100]; while (fgets(buffer, sizeof(buffer), file) != NULL) { // 对每一行数据进行处理 // ... } fclose(file); return 0; }
-
写入文件:
#include <stdio.h> int main() { FILE* file = fopen("file.txt", "w"); if (file == NULL) { perror("Error opening file"); return 1; } fputs("Hello, World!", file); fclose(file); return 0; }
-
关闭文件:在打开文件中已经有所展示.
而这些编程语言的实习从设计架构上是类似与VFS和文件系统上对于文件操作上的函数的
-
了解原型,参考Linux系统中VFS中相关的几个函数原型:
-
int (*open)(struct inode *inode, struct file *filp)
open
函数是VFS中的方法,用于打开文件。它接受一个inode
结构体指针和一个file
结构体指针作为参数。inode
结构体代表文件的元数据信息,而file
结构体代表打开的文件实例。函数的返回值是一个整数,表示打开文件的结果。 -
ssize_t (*read)(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
read
函数是VFS中的方法,用于从打开的文件中读取数据。它接受一个file
结构体指针、一个用户空间缓冲区指针buf
、要读取的字节数count
,以及一个文件偏移指针f_pos
作为参数。函数的返回值是一个ssize_t
类型的整数,表示实际读取的字节数。 -
ssize_t (*write)(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
write
函数是VFS中的方法,用于向打开的文件中写入数据。它接受一个file
结构体指针、一个指向要写入数据的用户空间缓冲区的指针buf
、要写入的字节数count
,以及一个文件偏移指针f_pos
作为参数。函数的返回值是一个ssize_t
类型的整数,表示实际写入的字节数。 -
int (*release)(struct inode *inode, struct file *filp)
release
函数是VFS中的方法,用于关闭打开的文件。它接受一个inode
结构体指针和一个file
结构体指针作为参数。该函数的返回值是一个整数,表示关闭文件的结果。
-
-
路径解析:在程序运行开始,我们去调用fopen时,其里面放入的
文件路径
或者文件名
,会被转换成一个对应的问文件描述符
,然后使用文件描述符
对文件进行其他操作.首先根据参数的类型,大致上有两类:
文件路径
(绝对路径),文件名
(相对路径).因此这个解析过程,他会将这个字符串参数递交给VFS,最后递交给底层的操作系统.
- 如果是
文件路径
的方式,比如/home/list/file.txt,他会先从根目录开始查找,也就是/
,在这个过程中,文件的查找同样存在缓存,同时更像缓存,当缓存中不存在此文件的索引时,才会继续向下寻找,寻找/llist目录,即使最后找到了这个文件,VFS也会去校验,比如当前程序是否有权限打开时,例如你去打开某些目录下的文件,将权限设置为不可读的状态,那么依然会打开失败.同样,若这个文件在目录项中不管是符号链接
还是硬链接
,都会根据目录项中的inode号
查找对应的文件. - 如果是
文件名
的方式,比如file.txt,他会从当前目录开始查找,因此也就是相对路径的方式,至于其查找过程其实与通过文件路径
的方式相同,只不过这个当前目录是进程在执行的当前目录
,即进程在进行文件操作时默认的相对路径的基准目录
,因此每一个进程其实都有一个相对应的工作目录
.
- 如果是
-
文件描述符:在程序执行之后,会返回一个与之对应的
文件描述符
,这个文件描述符实际上是一个整数,没有错,但这个整数指向了文件描述结构
,而存储这些文件描述结构
就被统称位一张文件描述符表
,因此就如同结构体一样类似,即可通过索引确定各自文件的描述结构,而结构里的变量则存放着文件相应的信息.#include <stdio.h> #define MAX_FILE_DESCRIPTORS 1024 typedef struct { int fd; // 文件描述符 int flags; // 文件打开标志 off_t offset; // 文件偏移量 // 其他与文件相关的信息 } FileDescriptorEntry; typedef struct { FileDescriptorEntry entries[MAX_FILE_DESCRIPTORS]; // 文件描述符表的条目数组 int count; // 当前文件描述符表中的条目数 } FileDescriptorTable;
以上,仅仅一个实例,通过结构体的方式可以更好地理解文件描述符
- 注:只有当一个文件被打开或者使用,对应的
文件描述符结构
才会与之创建,Linux默认一般大小是1024,因此文件描述符表如果为每一份文件都创造一个文件描述符结构来存储相应的数据是没有意义的.
明明文件有对应的inode和目录项,为什么还需要文件描述符表?
- 注:只有当一个文件被打开或者使用,对应的
-
文件描述符表在操作系统中的作用是提供一种机制来跟踪和管理打开的文件。它是为了方便进程对文件的访问和操作而引入的。因此,设计的本质之处是为了让应用进程与操作系统之间有一个认证关系,如果应用程序没有这一层认证关系,那么文件的安全性就无法得到保障,导致进程肆无忌惮地使用任何文件,因此在应用进程使用文件时,必须首先获得
文件描述符
,同时,也可以通过文件描述符表监控每一个进程所打开的对应的文件,起到了一定的追踪作用,对资源可以进行一定的监视.
2.页缓存
其实缓存这一机制不光提高了速度,即减少了访问时间和操作,其另一个很重要的方面就是他细化了粒度
,我们的文件被持久化放入硬盘,而根据我们对硬盘空间的划分,成了对应的块,而每个块512个字节或者4KB的大小,这种块内存内部的浪费是必然会发生的,因此使用缓存,将粒度细化
,更重要更常用的文件数据则被记录了下来,且浪费的空间相比于更高层次的磁盘来说节省了不少,还提高了利用率.
因此面对文件最基本的两个功能:读写操作时,则通过施加缓存的方式来提高系统的性能
- 读取缓存:当应用程序请求读取文件时,操作系统会首先检查页缓存中是否已经缓存了所需的数据。如果数据已经在页缓存中,操作系统会直接从缓存中将数据拷贝到应用程序的内存空间中,避免了对磁盘的实际读取操作。
- 写入缓存:当应用程序请求写入文件时,操作系统会先将数据写入页缓存中,然后在合适的时机再将数据刷新到磁盘中。这种延迟写入(Deferred Write)的策略可以提高写入的效率,避免频繁地访问磁盘。
而面对写入缓存时需要注意的是,如果在写入缓存时,发生了不可控意外,也就是断电,此时保存在主存中的数据还未来得及持久化进硬盘,则会造成数据的丢失,为此,操作系统会有相应的策略去尽可能降低这种风险:比如日志
-
脏页:指在页缓存中已被修改但尚未写回到磁盘的页。当应用程序对页缓存中的数据进行写操作时,对应的页就会变为脏页。
而这种内存与磁盘数据不一致性的问题,就称之为
脏页
2.1内存映射
本质上就是不再需要通过读写接口去访问文件,而是依赖于虚拟内存,通过虚拟地址去访问所需要的文件数据:
大致流程就是这样:在处理内存映射请求时,VFS会分配对应的VMA结构,用VMA结构与文件inode进行关联,最后返回给一个虚拟地址给应用进程.但如果此时内存中并未更新页表,也就是并未有此页,即会发生缺页中断(首次),会根据VMA结构中记录的inode信息,调用对应的文件系统进行处理,文件系统则可以从对应文件的页缓存
中找到对饮的内存页返回给VFS,VFS将页缓存中的物理地址写入页表,至此,映射关系就此建立.
什么是VMA结构?
-
VMA:操作系统的一种数据结构,每一个进程都有一个,记录了进程的虚拟地址空间的不同区域的属性和状态。
VMA 表中的每个条目对应着进程虚拟地址空间中的一个连续的区域,该区域可能包含了进程的代码、数据、堆、栈以及其他映射的文件或设备等。每个 VMA 表项通常包含以下信息:
- 起始地址和结束地址:指定了该 VMA 区域在进程虚拟地址空间中的范围。
- 权限和属性:指定了对该 VMA 区域的访问权限,如可读、可写、可执行等,以及其他属性如共享、私有等。
- 文件映射信息:如果该 VMA 区域与文件映射相关联,记录了文件描述符、偏移量等相关信息。
- 内存回收信息:记录了该 VMA 区域是否可回收、是否是脏页等信息。
#include <stdio.h>
struct VMA {
unsigned long start; // 区域起始地址
unsigned long end; // 区域结束地址
int permissions; // 区域访问权限
// 其他属性字段...
};
- 从这方面来看,VMA和文件描述符表有着相似之处,他们都是基于对文件的一种管理,而VMA偏向于对虚拟地址的管理和访问,而文件描述符更偏向于追踪文件的使用状态及情况.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!