Linux 可以支持多达数十种不同的文件系统。它们的实现各不相同,因此 Linux 内核向用户空间提供了虚拟文件系统这个统一的接口,来对文件系统进行操作。它提供了常见的文件系统对象模型,例如 inode、directory entry、mount 等,以及操作这些对象的方法,例如 inode operations、directory operations、file operations 等。
解析系统调用是了解内核架构最有力的一把钥匙。
- mount 系统调用用于挂载文件系统;
- open 系统调用用于打开或者创建文件,创建要在 flags 中设置 O_CREAT,对于读写要设置 flags 为 O_RDWR;
- read 系统调用用于读取文件内容;
- write 系统调用用于写入文件内容。
内核是不是支持某种类型的文件系统,需要我们进行注册才能知道。例如, ext4 文件系统,就需要通过 register_filesystem 进行注册,传入的参数是 ext4_fs_type,表示注册的是 ext4 类型的文件系统。这里面最重要的一个成员变量就是 ext4_mount。
register_filesystem(&ext4_fs_type);
static struct file_system_type ext4_fs_type = {
.owner = THIS_MODULE,
.name = "ext4",
.mount = ext4_mount,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
如果一种文件系统的类型曾经在内核注册过,这就说明允许你挂载并且使用这个文件系统。
来解析经过 mount 之后数据结构之间的关系。
我们假设根文件系统下面有一个目录 home,有另外一个文件系统 A 挂载在这个目录 home 下面。在文件系统 A 的根目录下面有另外一个文件夹 hello。由于文件系统 A 已经挂载到了目录 home 下面,所以我们就有了目录 /home/hello,然后有另外一个文件系统 B 挂载在 /home/hello 下面。在文件系统 B 的根目录下面有另外一个文件夹 world,在 world 下面有个文件夹 data。由于文件系统 B 已经挂载到了 /home/hello 下面,所以我们就有了目录 /home/hello/world/data。
文件系统是树形关系。如果所有的文件夹都是几代单传,那就变成了一条线。注意看图中的三条斜线。
第一条线是最左边的向左斜的 dentry 斜线。每一个文件和文件夹都有 dentry,用于和 inode 关联。
第二条线是最右面的向右斜的 mount 斜线,因为这个例子涉及两次文件系统的挂载,再加上启动的时候挂载的根文件系统,一共三个 mount。
第三条线是中间的向右斜的 file 斜线,每个打开的文件都有一个 file 结构,它里面有两个变量,一个指向相应的 mount,一个指向相应的 dentry。
对于虚拟文件系统的解析,有关文件的数据结构层次多,而且很复杂。
对于每一个进程,打开的文件都有一个文件描述符,在 files_struct 里面会有文件描述符数组。每个一个文件描述符是这个数组的下标,里面的内容指向一个 file 结构,表示打开的文件。这个结构里面有这个文件对应的 inode,最重要的是这个文件对应的操作 file_operation。如果操作这个文件,就看这个 file_operation 里面的定义了。
对于每一个打开的文件,都有一个 dentry 对应,虽然叫作 directory entry,但是不仅仅表示文件夹,也表示文件。它最重要的作用就是指向这个文件对应的 inode。
如果说 file 结构是一个文件打开以后才创建的,dentry 是放在一个 dentry cache 里面的,文件关闭了,他依然存在,因而他可以更长期地维护内存中的文件的表示和硬盘上文件的表示之间的关系。
inode 结构就表示硬盘上的 inode,包括块设备号等。
此文章为11月Day9学习笔记,内容来源于极客时间《趣谈Linux操作系统》,推荐该课程。