最近一周报名的内核课程讲到了文件系统这一章,老师布置了2个作业:
1.复习文件系统中超级块的创建过程
2.复习文件系统中文件的创建过程
老师给了一个最简单的aufs文件系统的例子,里面注册了aufs, 然后调用kern_mount对文件系统进行挂载,然后创建了1个目录,3个文件,总体来说实现了最简单的
文件系统的功能,作业主要是为了熟悉超级块的创建(这也涉及到了mount的过程), 文件的创建(了解inode和dentry).
在做作业之前,先把课程视频学习了,然后网上找资料学习,这篇文章主要是根据网上的几篇比较牛的文章进行了整合,有些也是直接搬过来了,整合之后的过程更加清楚。
在基础知识之后,我也贴出了作业代码流程,有些代码还没怎么看懂(所以注释还没写)。。等我请教了别人之后,再来添加。
一、基本概念
1.一块磁盘(块设备),首先要按照某种文件系统(如 NTFS)格式进行格式化,然后才能在其上进行创建目录、保存文件等操作。在 Linux 中,有“安装(mount)”文件系统和“卸载(unmount)”文件系统的概念。一块经过格式化的“块设备”(不管是刚刚格式化完的,没有创建任何名录和文件;还是已经创建了目录和文件),只有先被“安装”,才能融入 Linux 的文件系统中,用户才可以在它上面进行正常的文件操作。
2. Linux 把目录或普通文件,统一看成“目录节点”。通常一个“目录节点”具有两个重要属性:名称以及磁盘上实际对应的数据。“符号链接”是一种特殊的目录节点,它只有一个名称,没有实际数据。这个名称指向一个实际的目录节点。
3.“接口结构”:在 内核代码中,经常可以看到一种结构,其成员全部是函数指针,例如:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
4. 虚拟文件系统
Linux 通过虚拟文件系统 (VFS) 来支持不同的具体的文件系统,那么 VFS 到底是什么?
从程序员的角度看,VFS 就是一套代码框架(framework),它将用户与具体的文件系统隔离开来,使得用户能够通过这套框架,以统一的接口在不同的具体的文件系统上进行操作。
这套框架包括:
l 为用户提供统一的文件和目录的操作接口,如 open, read, write
l 抽象出文件系统共有的一些结构,包括“目录节点”inode、“超级块”super_block 等。
l 面向具体的文件系统,定义一系列统一的操作“接口”, 如 file_operations, inode_operations, dentry_operation,具体的文件系统必须提供它们的实现。
l 提供一套机制,让具体的文件系统融入 VFS 框架中,包括文件系统的“注册”和“安装”
l 实现这套框架逻辑的核心代码
二、核心数据结构
1.inode 和 file_operations
先看一下文件在内存和磁盘上是如何描述的?每个文件至少有一个数据结构存放该文件的信息,例如大小,创建时间,修改时间、uid、gid 等,这个数据结构就是inode.
l inode 描述了一个目录节点物理上的属性.
l 本来inode中应该包含文件名称等信息,但是由于符号链接的存在,导致一个文件可能存在多个文件名称,所以把和文件名称相关的信息从inode中隔离出来,放入另外一个数据结构中dentry,由dentry结构中的d_inode指向对应的inode(所以寻找inode的过程实际上就是寻找dentry的过程). 所以一个文件可以有两个数据结构表示inode和dentry.
l 对于文件的操作,通过inode中的i_fop来指向file_operations(上面的一个数据结构)中的操作,不同的文件系统会实现具体的细节。
2. 目录节点入口(根据路径名寻找目标文件--简单版)
在Linux中目录也被作为文件看待,只是目录是一种比较特殊的文件。其特殊之处在于文件的内容是该目录中文件和子目录的dentry的描述符,通过这些dentry的描述符可以找到文件或子目录的dentry,进而找到相应的inode。
目录内容(文件和子目录dentry的描述符)---》文件或目录的dentry--->inode(文件信息)
下面我们看看如果根据绝对路径寻找一个文件/tmp/temp/abc的:
l 首先找到根文件系统的根目录文件(/)的 dentry 和 inode
l 由这个 inode 提供的操作接口 i_op->lookup(),找到下一层节点tmp的 dentry 和 inode
l 由 ‘tmp’ 的 inode 找到 ‘temp’ 的 dentry 和 inode
l 最后由 ‘temp’ 的 inode 找到 ‘abc’ 的 dentry 和 inode
通过相对路径找到文件/tmp/temp/abc的过程:
假如我们目前的工作目录为/tmp/temp/dir_a 中,比如我们通过拷贝命令拷贝该文件:cp ../abc ./ 如何通过相对路径寻找文件呢?
dentry这个数据结构的成员,其中有一个是d_parent,数据结构定义如下
struct dentry { 删除了无关的成员
struct dentry *d_parent; /* parent directory */
struct inode *d_inode; /* Where the name belongs to - NULL is * negative */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
}
d_parent指向了本目录的父目录的dentry,这样就在通过“..”时就是通过该指针找到的父目录dentry,找到父目录inode,进而找到父目录下的所有文件的信息。
Dentry , inode,和文件操作file_operations的关系:
3. Super_block和super_operations
一个存放在磁盘上的文件系统如 EXT2 等,在它的格式中通常包括一个“超级块”或者“控制块”的部分,用于从整体上描述文件系统,例如文件系统的大小、是否可读可写等等。
虚拟文件系统VFS中也通过“超级块”这种概念来描述文件系统整体的信息,对应的结构是 struct super_block。super_block 除了要记录文件大小、访问权限等信息外,更重要的是提供一个操作“接口”super_operations。
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_in