0%

《趣谈linux操作系统》小结(二十四) - 虚拟文件系统

虚拟文件系统

进程要想往文件系统里面读写数据,需要很多层的组件一起合作。

  • 在应用层,进程在进行文件读写操作时,可通过系统调用如 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
2
3
4
5
6
7
8
9
10
11

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到一个目录下, 才可以访问文件系统。通过系统调用mount来完成, do_mount->do_new_mount->vfs_kern_mount。

vfs_kern_mount 先是创建 struct mount 结构,每个挂载的文件系统都对应于这样一个结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

struct mount {
struct hlist_node mnt_hash;
struct mount *mnt_parent;
struct dentry *mnt_mountpoint;
struct vfsmount mnt;
union {
struct rcu_head mnt_rcu;
struct llist_node mnt_llist;
};
struct list_head mnt_mounts; /* list of children, anchored here */
struct list_head mnt_child; /* and going through their mnt_child */
struct list_head mnt_instance; /* mount instance on sb->s_mounts */
const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
struct list_head mnt_list;
......
} __randomize_layout;


struct vfsmount {
struct dentry *mnt_root; /* root of the mounted tree */
struct super_block *mnt_sb; /* pointer to superblock */
int mnt_flags;
} __randomize_layout;

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
2

struct files_struct *files;

files_struct 里面最重要的是一个文件描述符列表,每打开一个文件,就会在这个列表中分配一项,下标就是文件描述符。

1
2
3
4
5

struct files_struct {
......
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};

默认情况下,文件描述符 0 表示 stdin 标准输入,文件描述符 1 表示 stdout 标准输出,文件描述符 2 表示 stderr 标准错误输出。另外,再打开的文件,都会从这个列表中找一个空闲位置分配给它。

打开文件的操作,就是创建一个struct file结构,然后和fd关联起来,最后设置struct file的成员。这中间, 查找dentry又是一个缓存和老化的方案,提高查找效率。

图片替换文本

有关文件的数据结构层次多,而且很复杂,就得到了下面这张图

图片替换文本

行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。