文件系统组件:
- 在应用层,进程在进行文件读写操作时,可通过系统调用如 sys_open、sys_read、sys_write 等。
- 在内核,每个进程都需要为打开的文件,维护一定的数据结构。
- 在内核,整个系统打开的文件,也需要维护一定的数据结构。
- Linux 可以支持多达数十种不同的文件系统。它们的实现各不相同,因此 Linux 内核向用户空间提供了虚拟文件系统这个统一的接口,来对文件系统进行操作。它提供了常见的文件系统对象模型,例如 inode、directory entry、mount 等,以及操作这些对象的方法,例如 inode operations、directory operations、file operations 等。
- 然后就是对接的是真正的文件系统,例如我们上节讲的 ext4 文件系统。
- 为了读写 ext4 文件系统,要通过块设备 I/O 层,也即 BIO 层。这是文件系统层和块设备驱动的接口。
- 为了加快块设备的读写效率,我们还有一个缓存层。
- 最下层是块设备驱动程序。
在这之前,有一点你需要注意。解析系统调用是了解内核架构最有力的一把钥匙,这里我们只要重点关注这几个最重要的系统调用就可以了:
- mount 系统调用用于挂载文件系统;
- open 系统调用用于打开或者创建文件,创建要在 flags 中设置 O_CREAT,对于读写要设置 flags 为 O_RDWR;
- read 系统调用用于读取文件内容;
- write 系统调用用于写入文件内容。
挂载文件系统
- 通过 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 系统调用的定义如下:
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data)
{
......
ret = do_mount(kernel_dev, dir_name, kernel_type, flags, options);
......
}
在文件系统的实现中,每个在硬盘上的结构,在内存中也对应相同格式的结构。当所有的数据结构都读到内存里面,内核就可以通过操作这些数据结构,来操作文件系统了。
- 第一条线是最左边的向左斜的 dentry 斜线。每一个文件和文件夹都有 dentry,用于和 inode 关联。
- 第二条线是最右面的向右斜的 mount 斜线,因为这个例子涉及两次文件系统的挂载,再加上启动的时候挂载的根文件系统,一共三个 mount。
- 第三条线是中间的向右斜的 file 斜线,每个打开的文件都有一个 file 结构,它里面有两个变量,一个指向相应的 mount,一个指向相应的 dentry。
文件管理解释:
- dentry
- 磁盘上的结构
- mount
- 挂载到哪
- 挂载什么
- file
- 打开的文件
打开文件
- 要打开一个文件,首先要通过 get_unused_fd_flags 得到一个没有用的文件描述符。
下标就是文件描述符:
struct files_struct {
......
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
对于任何一个进程,默认情况下,文件描述符 0 表示 stdin 标准输入,文件描述符 1 表示 stdout 标准输出,文件描述符 2 表示 stderr 标准错误输出。另外,再打开的文件,都会从这个列表中找一个空闲位置分配给它。
文件描述符列表的每一项都是一个指向 struct file 的指针,也就是说,每打开一个文件,都会有一个 struct file 对应。fd_install(fd, f) 是将文件描述符和 struct file 关联起来
**