Linux kernel 分析之二十三:文件系统

对于linux 0.11内核的文件系统的开发,Linus主要参考了Andrew S.Tanenbaum所写的《MINIX操作系统设计与实现》,使用的是其中的1.0版本的MINIX文件系统。而高速缓冲区的工作原理参见M.J.Bach的《UNIX操作系统设计》第三章内容。

通过对源代码的分析,我们可以将minix文件系统分为四个部分,如下如1-1

l 高速缓冲区的管理程序。主要实现了对硬盘等块设备进行数据高速存取的函数。

l 文件系统的底层通用函数。包括文件索引节点的管理、磁盘数据块的分配和释放以及文件名与i节点的转换算法。

l 有关对文件中的数据进行读写操作的函数。包括字符设备、块设备、管道、常规文件的读写操作,由read_write.c函数进行总调度。

l 涉及到文件的系统调用接口的实现,这里主要涉及文件的打开、关闭、创建以及文件目录等系统调用,分布在nameiinode等文件中。

 

 

图1-1 文件系统四部分之间关系图

1.1超级块

首先我们了解一下MINIX文件系统的组成,主要包括六部分。对于一个360K软盘,其各部分的分布如下图1-2所示:

图 1-2 建有MINIX文件系统的一个360K软盘中文件系统各部分的布局示意图

注释1:硬盘的一个扇区是512B,而文件系统的数据块正好是两个扇区。

注释2:引导块是计算机自动加电启动时可由ROM BIOS自动读入得执行代码和数据。

注释3:逻辑块一般是数据块的2幂次方倍数。MINIX文件系统的逻辑块和数据块同等大小

对于硬盘块设备,通常会划分几个分区,每个分区所存放的不同的文件系统。硬盘的第一个扇区是主引导扇区,其中存放着硬盘引导程序和分区表信息。分区表中得信息指明了硬盘上每个分区的类型、在硬盘中其实位置参数和结束位置参数以及占用的扇区总数。其结构如下图1-3所示。

图1-3 硬盘设备上的分区和文件系统

对于可以建立不同的多个文件系统的硬盘设备来说,minix文件系统引入超级块进行管理硬盘的文件系统结构信息。其结构如下图1-4所示。其中,s_ninodes表示设备上得i节点总数,s_nzones表示设备上的逻辑块为单位的总逻辑块数。s_imap_blockss_zmap_blocks分别表示i节点位图和逻辑块位图所占用的磁盘块数。s_firstdatazone表示设备上数据区开始处占用的第一个逻辑块块号。s_log_zone_size是使用2为底的对数表示的每个逻辑块包含的磁盘块数。对于MINIX1.0文件系统该值为0,因此其逻辑块的大小就等于磁盘块大小。s_magic是文件系统魔幻数,用以指明文件系统的类型。对于MINIX1.0文件系统,它的魔幻数是0x137f

图 1-4 MINIX超级块结构

对于超级块来说有两个特殊之处:

l 逻辑位图的最低比特位(位0)闲置不用,并在创建文件系统时会预先置1

l I节点位图的最低比特位(位0)闲置不用,并在创建文件系统时会预先置1。因此i节点位图只能表示8191i节点的状况。

1.2 i节点     

I节点则是用来存放文件的相关信息。对于每个文件或者目录名都有一个i节点,各自i节点结构中存放着对应文件的相关信息。其i节点的结构(32个字节)如图1-6所示。

其中i_mode字段的是用来保存文件的类型和访问权限属性。其15-12用于保存文件类型,为11-9保存执行文件时设置的信息,位8-0用于设置范文权限。如图1-5所示。

I节点有一个特殊之处:

l I节点0是闲置不用的,在文件系统创建时被置1

图 1-5 i节点属性字段内容

图 1-6 MINIX文件系统1.0版的i节点结构

    其中文件所占用得盘上逻辑块号数组结构如下图1-7所示。

图 1-7 i节点的逻辑块(区块)数组的功能

1.3文件类型、属性和目录项

     Linux下文件属性的查看可以通过“ls –l”,具体如下图1-8所示:

图 1-8 linux下“ls –l“显示的文件信息

从上图可以看出来,在linux下通常可以分为六种类型的文件:

l 正规文件(‘-’),系统对其不做任何解释,包含有任何长度的字节流。

l 目录(‘d’)在linux下也是一种文件,文件管理系统会对其内容进行解释。

l 符号链接(‘s’)用于使用不同的文件名来引用另外一个文件。分为软链接和硬链接。软链接可以跨文件系统进行链接,删除时不会影响到源文件;而硬链接则不能跨文件系统(或者设备),它和被链接的文件地位相同,被作为一般文件对待,并且会递增文件的链接计数。

l 命名管道(‘p’)文件时系统创建有名管道建立的文件,用于无关进程间的通信。

l 字符设备(‘c’)文件用于以操作文件的方式访问字符设备。主要包括tty终端、内存设备和网络设备。

l 块设备(‘b’)文件用于访问硬盘、软盘等设备。在linux下,块设备和字符设备文件一般存放在/dev目录下。

注释1:每个i节点都有一个链接计数i_nlinks,记录着指向该i节点的目录项数,这就是文件的硬链接计数。在执行删除文件时,只有i_nlinks等于0时才允许删除此数据。

注释2:符号链接(即软链接)类型的文件并不会直接指向对应的文件的i节点,而是在其数据块中存储这一文件的路径名字符串,内核查看这个文件是通过路劲名进行直接解析的。因此可以跨文件系统(或者设备)链接。

     文件系统的目录项结构体定义在/include/linux/fs.h的头文件中,其结构如下图1-9所示。

图 1-9 目录项结构体的定义

注释1:一个逻辑块能存储的目录项1024/16=64

注释2:对于i节点的i_zone[0]所对应的逻辑块来说,它在初始化时首先存储了“.”(自己目录的i节点号)和“..”(父目录的i节点号)两个目录,在代码中会有体现,对目录项进行遍历时,会从2开始。 

通过文件名从文件系统中获取其数据块的流程图如下图1-10所示。

图 1-10 以文件名获取其数据块

1.4高速缓冲区

高速缓冲区是文件系统访问块设备中数据的必经之道,它的主要作用如下:

l 将磁盘块中的数据预读到缓冲区,减少对磁盘的频繁访问,提高系统性能。

l 当需要将数据写入到块设备时,则先将数据转存在高速缓冲区中,然后由高速缓冲区通过设备数据同步来实现写入块设备。

高速缓冲存放着最近使用过得各个块设备中的数据块。当需要从块设备中读取数据时,缓冲管理程序首先会再高速缓冲中寻找数据。如果在缓冲区,则直接返回数据块的指针,反之,则发出读设备的命令,将磁盘块中得数据读入高数缓冲区。如下图1-11所示,显示了高速缓冲区位于内核模块和主内存区之间。

图 1-11 高速缓冲区在整个内存中的位置

注释1end是内核模块链接期间由链接程序ld设置的一个外部变量,内核代码中没有定义这个符号。当在连接生成system模块时,ld程序设置了end的地址,它等于data_start+datasize+bss_size,即bss段结束后的第一个有效地址。

注释2:高速缓冲区被划分为1024字节大小的缓冲块,正好与块设备上的磁盘逻辑块大小相同。

高速缓冲区从物理角度看主要由缓冲块和指向缓冲块的缓冲头组成。其中缓冲头的结构体为:

struct buffer_head {

char * b_data; //指向该缓冲块中数据区(1024字节)的指针

unsigned long b_blocknr;  //块号

unsigned short b_dev;     //数据块的设备号

unsigned char b_uptodate;   //更新标志,表示设备是否更新

unsigned char b_dirt;    //修改标志,0-未修改,1-已修改

unsigned char b_count;   //使用该块的用户数,用于清除数据块时。

unsigned char b_lock;      //缓冲区上所标识,0-ok1-locked

struct task_struct * b_wait;   //指向等待使用此缓冲块的进程

struct buffer_head * b_prev;  //hash队列上一块

struct buffer_head * b_next;  //hash队列下一块

struct buffer_head * b_prev_free;   //空闲表上一块

struct buffer_head * b_next_free;   //空闲表下一块

};

注释1:字段b_dirt是脏标志,说明该缓冲块中得内容是否已修改而与块设备上得对应数据块内容不同(延迟写)。B_uptodate是数据更新标志,说明缓冲块中数据是否有效。初始化或者释放块时这两个标志设置为0,表示该缓冲块此时无效。

注释2b_dirt=1,b_uptodate=0,数据被写入缓冲块但是还没有被写入设备;b_dirt=0b_uptodate=1,数据被写入了设备块或者刚从设备快中读入缓冲块中变成有效;b_dirt=1,b_uptodate=1,表示缓冲块和设备快上得数据不同,但是数据还是有效的(更新的)。

高速缓冲区采用hash表和空闲缓冲块队列进行操作管理,这也是高速缓冲区从逻辑上的组成。在缓冲区初始化过程中,初始化程序从整个缓冲区的两端开始,分别同时设置缓冲头块和划分对应的缓冲块,如图1-12所示。缓冲头的结构体描述了对应缓冲块的属性,并且用于把所有的缓冲头连接成一个双向链表结构。如图1-13所示。

图 1-12 高速缓冲区的初始化示意图

注释1:高端建立了1024大小的缓冲块,低端建立了对应的缓冲块头

图 1-13 所有缓冲块组成的双向循环链表结构

注释1:free_list指针是该链表的头指针,指向空闲块链表中第一个“最为空闲的”缓冲块,即近期最少使用的块。

为了能够快速而有效地在缓冲区中寻找判断出请求的数据块是否已经被读入缓冲区中,buffer.c使用了具有307buffer_head指针项的hash数组结构。Hash函数采用了设备号和逻辑块号作为参数,具体函数:(设备号^逻辑块号)Mod307。然后通过b_prevp_nexthash表中散列在同一项上的多个缓冲块练成一个双向链表。

通过hash数组实现管理缓冲块的好处是保证了同一设备号的块具有相同的散列值,加速对缓冲区中得块的查询。 图1-14所示为某一时刻内核中缓冲块散列队列示意图。

图1-14 某一刻内核中缓冲块散列队列示意图

 

 

 

你可能感兴趣的:(Linux kernel 分析之二十三:文件系统)