首页 > 代码库 > Linux 文件系统(二)---执行过程及结构间的关系

Linux 文件系统(二)---执行过程及结构间的关系

(内核2.4.37)


一、首先,看看磁盘,超级块,inode节点在物理上整体的分布情况:

(图示来自:www.daoluan.net)

                                       

对于一个分区,对应一个文件系统,一个文件系统其实本质上还是磁盘的一部分,所以可以包括多个柱面。对于柱面上的数据,主要看看inode节点位图、block位图,i节点,数据块。inode节点位图是为了记录inode节点的使用情况,之前的违章中已经说过,inode节点在文件系统安装的时候,会初始化所有的inode节点,那么形成的位图表示使用or没使用的大表。对于block块也是一样的,记录数据块使用情况。

对于inode节点来说,每个文件都会对应一个inode节点,目录项也会对应一个inode节点。对于一个文件来说,只对应一个inode节点,但是一个文件可以有多个数据块,因为一个比较大的文件,一个数据块根本存放不了。所以inode中记录多个文件数据块的信息。

对于目录块来说,主要是为了索引而存在,所以里面的内容主要是inode节点号和文件名,其实就是一个映射表形式的东西。


二、

上一篇中对于VFS有一个简单的介绍与理解,我们知道,与用户打交道的是VFS,然后VFS与底层真正的文件系统交流。那我们知道在一个VFS下面,允许存在很多的“文件系统类型”,例如ext2,ext3,ext4,sysfs,proc等等。这些类型是可以共存的,同时,对于每一个类型来说,可以存在多个文件系统实体,例如:在一个目录下有多个子目录,子目录之间的文件系统类型可以不一样,也可以有部分是一样的类型,借用windows中的例子说就是,D盘和E盘可以都是NTFS类型文件系统,也可以是不一样的文件类型系统。

在Linux中,系统有一个全局变量叫做file_systems,这个变量用来管理所有的“文件系统类型”链表。也就是所有的文件系统类型都必须注册到(链接到)这个链表中,才可以被使用。如果是自己的文件系统,只要符合VFS的标准,也是可以注册进去的。最终形成一个单链表形式结构。

而对于一个文件系统类型,使用file_system_type结构表示:

<span style="font-size:14px;">995 struct file_system_type {
996         const char *name;
997         int fs_flags;
998         struct super_block *(*read_super) (struct super_block *, void *, int);
999         struct module *owner;
1000         struct file_system_type * next;
1001         struct list_head fs_supers;
1002 };</span>
字段意思:

name:文件系统类型名称,如ext2。

flags:安装文件类型标志,在fs.h中有定义。

read_super:各种文件系统读入其“超级块”的函数指针,不同的文件系统之间可能不一样,因此读入函数也不一样。

owner:如果这个文件系统是通过一个可安装模块实现的,那么这个指针指向这个模块。

next:这个就是链接到下一个“文件类型”的指针。

fs_supers:属于相同的文件系统类型的所有的super_blocks构成一个双向链表。在超级块中有一个s_instance就是连接这个双链表的连接点。(超级块在上一篇有介绍)


1、

那么根据上面的解释,一个大的框架图如下:

        


同时,在内核中,有一个全局的变量super_blocks用于将所有的suoer_block连接在一起,形成一个双向链表,这样就会发现s_list字段就有意义了!

如图:

                       


2、我们在之前也提过,文件系统最终还是要和进程一起协作的,任何对于文件的操作都是基于进程的。由操作系统的知识我们知道,对于进程来说,管理进程的叫“进程控制块PCB”,这个在内核中的结构为:task_struct,这个是很复杂的一个结构体,对于进程来说,可以有自己的操作的文件,那么进程的文件的信息,也是包含在这个结构中:

283 struct task_struct {
... ...
391 /* filesystem information */
392         struct fs_struct *fs;
393 /* open file information */
394         struct files_struct *files;
... ...
}

代码中的两个字段就是涉及进程的文件的字段。每个进程在PCB中保存着一份文件描述符表,文件描述符就是这个表的索引(数组的下标),每个表项都有一个指向已打开文件的指针。

代码中第一个字段fs:代码本进程自身的文件系统的信息。例如进程本身的根目录,挂载点,当前目录等信息。

代码中第二个字段files:保存着本进程涉及的所有的文件的信息的指针。files_struct结构之前已经说过:files_struct

里面有两个重要字段:

172 struct files_struct {  
... ...
178         struct file ** fd;      /* current fd array */  
... ...
183         struct file * fd_array[NR_OPEN_DEFAULT];  
184 };


fd就是涉及到的所有的文件的数组指针!一般情况下fd就是fd_array,但是如果打开的文件超过NR_OPEN_DEFAULT,那么就会重新分配新的数组,然后fd指向新的数组。

对于一个文件数组来说例如:fd[],所谓“文件描述符”其实就是这个数组的下标!例如:默认0就是标准输入文件描述符,1是标准输出,2是标准错误。对于用户来说操作的是这个“文件描述符”,但是对于内核来说,“文件描述符”仅仅是为了找到对应的文件而已!然后所有的在内核中的操作,都是使用实际文件的file指针进行的!关于file结构体在上一篇也说了(之前files_struct链接)。延伸一下:我们在写C语言程序的时候会遇到两个函数,open和fopen。对于前者,返回的就是一个“文件描述符”,即那个文件数组的下标,对于fopen,返回的是一个FILE的指针,这里面其实除了“文件描述符”之外,还包括IO缓冲这些信息。文件指针FILE*更上层,FILE指针将文件描述符和缓冲区封装在一起。

OK,那么用户进程打开一个文件的具体的过程是什么呢?下面分析总结一下:首先我们知道用户使用open返回一个“文件描述符”(具体怎么获得,之后再说),然后在进程PCB中,即task_struct文件数组中找到对应“文件描述符”(数组下标)的文件(file)指针,在file结构体中,f_dentry记录了这个文件的完整目录项,一般在内存中会有dentry的缓存,通过这个我们可以找到文件的inode。对于一个dentry来说,也是有自己的inode,目录名称之类信息。总之通过dentry,可以找到最终文件的inode,找到inode之后,就可以定位到具体的文件数据在磁盘上的位置了!

对于上面的过程,整体的一张图如下:



3、多个进程和多个文件之间的关系:

对于一个进程来说,可以打开多个文件,也可以多个进程打开一个文件,对于文件来说,不变的永远是自己的inode节点,变化的仅仅是和进程直接关系的file结构。可以看一下下面的大图:




Linux 文件系统(二)---执行过程及结构间的关系