虚拟文件系统
进程要想往文件系统里面读写数据,需要很多层的组件一起合作。
- 在应用层,进程在进行文件读写操作时,可通过系统调用如 sys_open、sys_read、sys_write 等。
- 在内核,每个进程都需要为打开的文件,维护一定的数据结构。
- 在内核,整个系统打开的文件,也需要维护一定的数据结构。
- Linux 可以支持多达数十种不同的文件系统。Linux 内核向用户空间提供了虚拟文件系统这个统一的接口,来对文件系统进行操作。它提供了常见的文件系统对象模型,例如 inode、directory entry、mount 等,以及操作这些对象的方法,例如 inode operations、directory operations、file operations 等。
- 读写 ext4 文件系统,要通过块设备 I/O 层,也即 BIO 层。这是文件系统层和块设备驱动的接口。
- 为了加快块设备的读写效率,我们还有一个缓存层。
- 最下层是块设备驱动程序。
这里涉及到几个概念, mount, file, inode, 对应的operations, dentry。
挂载文件系统
想要操作文件系统,第一件事情就是挂载文件系统。每种文件系统都有一个结构file_system_type保存文件系统的信息及操作:
1 |
|
注册过文件系统之后, 需要把文件系统mount到一个目录下, 才可以访问文件系统。通过系统调用mount来完成, do_mount->do_new_mount->vfs_kern_mount。
vfs_kern_mount 先是创建 struct mount 结构,每个挂载的文件系统都对应于这样一个结构。
1 |
|
mnt_parent 是装载点所在的父文件系统,mnt_mountpoint 是装载点在父文件系统中的 dentry;struct dentry 表示目录,并和目录的 inode 关联;mnt_root 是当前文件系统根目录的 dentry,mnt_sb 是指向超级块的指针。最终调用文件系统类型的mount回调函数。
每个文件都由一个struct file来描述, struct file里面有mnt和dentry的指针, mnt表示文件系统的挂载点信息,dentry就是保存文件的inode 等信息的结构。通过指针构成一个复杂的网状结构。
下面是一张图, 假设根文件系统下面有一个目录 home,有另外一个文件系统 A 挂载在这个目录 home 下面。在文件系统 A 的根目录下面有另外一个文件夹 hello。由于文件系统 A 已经挂载到了目录 home 下面,所以我们就有了目录 /home/hello,然后有另外一个文件系统 B 挂载在 /home/hello 下面。在文件系统 B 的根目录下面有另外一个文件夹 world,在 world 下面有个文件夹 data。由于文件系统 B 已经挂载到了 /home/hello 下面,所以我们就有了目录 /home/hello/world/data。
打开文件
要打开一个文件,首先要通过 get_unused_fd_flags 得到一个没有用的文件描述符。 每个进程的task_struct里面都会有一个指针files:
1 |
|
files_struct 里面最重要的是一个文件描述符列表,每打开一个文件,就会在这个列表中分配一项,下标就是文件描述符。
1 |
|
默认情况下,文件描述符 0 表示 stdin 标准输入,文件描述符 1 表示 stdout 标准输出,文件描述符 2 表示 stderr 标准错误输出。另外,再打开的文件,都会从这个列表中找一个空闲位置分配给它。
打开文件的操作,就是创建一个struct file结构,然后和fd关联起来,最后设置struct file的成员。这中间, 查找dentry又是一个缓存和老化的方案,提高查找效率。
有关文件的数据结构层次多,而且很复杂,就得到了下面这张图
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。