Linux 011文件系统的源码阅读_第1页
Linux 011文件系统的源码阅读_第2页
Linux 011文件系统的源码阅读_第3页
Linux 011文件系统的源码阅读_第4页
Linux 011文件系统的源码阅读_第5页
已阅读5页,还剩15页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、 Linux 0.11文件系统的源码阅读总结1. minix文件系统对于linux 0.11内核的文件系统的开发,Linus主要参考了Andrew S.Tanenbaum所写的MINIX操作系统设计与实现,使用的是其中的1.0版本的MINIX文件系统。而高速缓冲区的工作原理参见M.J.Bach的UNIX操作系统设计第三章内容。通过对源代码的分析,我们可以将minix文件系统分为四个部分,如下如1-1。l 高速缓冲区的管理程序。主要实现了对硬盘等块设备进行数据高速存取的函数。l 文件系统的底层通用函数。包括文件索引节点的管理、磁盘数据块的分配和释放以及文件名与i节点的转换算法。l 有关对文件中的

2、数据进行读写操作的函数。包括字符设备、块设备、管道、常规文件的读写操作,由read_write.c函数进行总调度。l 涉及到文件的系统调用接口的实现,这里主要涉及文件的打开、关闭、创建以及文件目录等系统调用,分布在namei和inode等文件中。图1-1 文件系统四部分之间关系图1.1超级块首先我们了解一下MINIX文件系统的组成,主要包括六部分。对于一个360K软盘,其各部分的分布如下图1-2所示:图 1-2 建有MINIX文件系统的一个360K软盘中文件系统各部分的布局示意图注释1:硬盘的一个扇区是512B,而文件系统的数据块正好是两个扇区。注释2:引导块是计算机自动加电启动时可由ROM

3、BIOS自动读入得执行代码和数据。注释3:逻辑块一般是数据块的2幂次方倍数。MINIX文件系统的逻辑块和数据块同等大小对于硬盘块设备,通常会划分几个分区,每个分区所存放的不同的文件系统。硬盘的第一个扇区是主引导扇区,其中存放着硬盘引导程序和分区表信息。分区表中得信息指明了硬盘上每个分区的类型、在硬盘中其实位置参数和结束位置参数以及占用的扇区总数。其结构如下图1-3所示。图1-3 硬盘设备上的分区和文件系统对于可以建立不同的多个文件系统的硬盘设备来说,minix文件系统引入超级块进行管理硬盘的文件系统结构信息。其结构如下图1-4所示。其中,s_ninodes表示设备上得i节点总数,s_nzone

4、s表示设备上的逻辑块为单位的总逻辑块数。s_imap_blocks和s_zmap_blocks分别表示i节点位图和逻辑块位图所占用的磁盘块数。s_firstdatazone表示设备上数据区开始处占用的第一个逻辑块块号。s_log_zone_size是使用2为底的对数表示的每个逻辑块包含的磁盘块数。对于MINIX1.0文件系统该值为0,因此其逻辑块的大小就等于磁盘块大小。s_magic是文件系统魔幻数,用以指明文件系统的类型。对于MINIX1.0文件系统,它的魔幻数是0x137f。图 1-4 MINIX超级块结构对于超级块来说有两个特殊之处:l 逻辑位图的最低比特位(位0)闲置不用,并在创建文件

5、系统时会预先置1。l I节点位图的最低比特位(位0)闲置不用,并在创建文件系统时会预先置1。因此i节点位图只能表示8191个i节点的状况。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 MINI

6、X文件系统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)用于使用不同的文件名来引用另外一个文件。分为软链接和硬链接。软链接可以跨文件系统进行链接,删除时不会影

7、响到源文件;而硬链接则不能跨文件系统(或者设备),它和被链接的文件地位相同,被作为一般文件对待,并且会递增文件的链接计数。l 命名管道(p)文件时系统创建有名管道建立的文件,用于无关进程间的通信。l 字符设备(c)文件用于以操作文件的方式访问字符设备。主要包括tty终端、内存设备和网络设备。l 块设备(b)文件用于访问硬盘、软盘等设备。在linux下,块设备和字符设备文件一般存放在/dev目录下。注释1:每个i节点都有一个链接计数i_nlinks,记录着指向该i节点的目录项数,这就是文件的硬链接计数。在执行删除文件时,只有i_nlinks等于0时才允许删除此数据。注释2:符号链接(即软链接)类

8、型的文件并不会直接指向对应的文件的i节点,而是在其数据块中存储这一文件的路径名字符串,内核查看这个文件是通过路劲名进行直接解析的。因此可以跨文件系统(或者设备)链接。 文件系统的目录项结构体定义在/include/linux/fs.h的头文件中,其结构如下图1-9所示。图 1-9 目录项结构体的定义注释1:一个逻辑块能存储的目录项1024/16=64注释2:对于i节点的i_zone0所对应的逻辑块来说,它在初始化时首先存储了“.”(自己目录的i节点号)和“.”(父目录的i节点号)两个目录,在代码中会有体现,对目录项进行遍历时,会从2开始。 通过文件名从文件系统中获取其数据块的流程图如下图1-1

9、0所示。图 1-10 以文件名获取其数据块1.4高速缓冲区高速缓冲区是文件系统访问块设备中数据的必经之道,它的主要作用如下:l 将磁盘块中的数据预读到缓冲区,减少对磁盘的频繁访问,提高系统性能。l 当需要将数据写入到块设备时,则先将数据转存在高速缓冲区中,然后由高速缓冲区通过设备数据同步来实现写入块设备。高速缓冲存放着最近使用过得各个块设备中的数据块。当需要从块设备中读取数据时,缓冲管理程序首先会再高速缓冲中寻找数据。如果在缓冲区,则直接返回数据块的指针,反之,则发出读设备的命令,将磁盘块中得数据读入高数缓冲区。如下图1-11所示,显示了高速缓冲区位于内核模块和主内存区之间。图 1-11 高速

10、缓冲区在整个内存中的位置注释1:end是内核模块链接期间由链接程序ld设置的一个外部变量,内核代码中没有定义这个符号。当在连接生成system模块时,ld程序设置了end的地址,它等于data_start+datasize+bss_size,即bss段结束后的第一个有效地址。注释2:高速缓冲区被划分为1024字节大小的缓冲块,正好与块设备上的磁盘逻辑块大小相同。高速缓冲区从物理角度看主要由缓冲块和指向缓冲块的缓冲头组成。其中缓冲头的结构体为:struct buffer_head char * b_data;/指向该缓冲块中数据区(1024字节)的指针unsigned long b_blockn

11、r; /块号unsigned short b_dev; /数据块的设备号unsigned char b_uptodate; /更新标志,表示设备是否更新unsigned char b_dirt; /修改标志,0-未修改,1-已修改unsigned char b_count; /使用该块的用户数,用于清除数据块时。unsigned char b_lock; /缓冲区上所标识,0-ok,1-lockedstruct task_struct * b_wait; /指向等待使用此缓冲块的进程struct buffer_head * b_prev; /hash队列上一块struct buffer_hea

12、d * b_next; /hash队列下一块struct buffer_head * b_prev_free; /空闲表上一块struct buffer_head * b_next_free; /空闲表下一块;注释1:字段b_dirt是脏标志,说明该缓冲块中得内容是否已修改而与块设备上得对应数据块内容不同(延迟写)。B_uptodate是数据更新标志,说明缓冲块中数据是否有效。初始化或者释放块时这两个标志设置为0,表示该缓冲块此时无效。注释2:b_dirt=1,b_uptodate=0,数据被写入缓冲块但是还没有被写入设备;b_dirt=0,b_uptodate=1,数据被写入了设备块或者刚从

13、设备快中读入缓冲块中变成有效;b_dirt=1,b_uptodate=1,表示缓冲块和设备快上得数据不同,但是数据还是有效的(更新的)。高速缓冲区采用hash表和空闲缓冲块队列进行操作管理,这也是高速缓冲区从逻辑上的组成。在缓冲区初始化过程中,初始化程序从整个缓冲区的两端开始,分别同时设置缓冲头块和划分对应的缓冲块,如图1-12所示。缓冲头的结构体描述了对应缓冲块的属性,并且用于把所有的缓冲头连接成一个双向链表结构。如图1-13所示。图 1-12 高速缓冲区的初始化示意图注释1:高端建立了1024大小的缓冲块,低端建立了对应的缓冲块头图 1-13 所有缓冲块组成的双向循环链表结构注释1:fre

14、e_list指针是该链表的头指针,指向空闲块链表中第一个“最为空闲的”缓冲块,即近期最少使用的块。为了能够快速而有效地在缓冲区中寻找判断出请求的数据块是否已经被读入缓冲区中,buffer.c使用了具有307个buffer_head指针项的hash数组结构。Hash函数采用了设备号和逻辑块号作为参数,具体函数:(设备号逻辑块号)Mod307。然后通过b_prev、p_next将hash表中散列在同一项上的多个缓冲块练成一个双向链表。通过hash数组实现管理缓冲块的好处是保证了同一设备号的块具有相同的散列值,加速对缓冲区中得块的查询。 图1-14所示为某一时刻内核中缓冲块散列队列示意图。图1-14

15、 某一刻内核中缓冲块散列队列示意图2源码分析 这一部分主要从源码fs/目录下的各个文件进行逐个分析,理清它们之间的调用关系,并通过流程图的形式展现其中关键代码的实现。本章可以划分为五大部分:1)高速缓冲管理;2)文件底层操作;3)文件数据访问;4)文件高层访问控制;5)文件系统的初始化和define变量说明。2.1高速缓冲管理 高速缓冲管理在fs/buffer.c中得到实现。内核程序在使用高速缓冲区中的缓冲块时,是通过制定需要访问的设备号和数据逻辑块号来调用buffer.c的函数。本文件中主要实现这些接口函数:l bread(int dev,int block):快读取函数。l breada(

16、int dev,int block):块提前预读函数。l bread_page(unsigned long address,int dev,int b4):页块读取函数,它一般可以读取四个缓冲块。 所有这些缓冲块数据存取和管理函数的调用层次关系可以用图2-1所描述。图2-1 缓冲区管理函数之间的层次关系 其中brelse(struct buffer_head * buf)用来释放指定的缓冲块;get_hash_table(int dev, int block)查询对应的hash表项。Find_buffer(int dev, int block)通过hash(dev,block)获取缓存头的地址

17、来遍历缓冲头,找到需要的目的缓冲头。2.1.1 getblk函数 getblk(int dev,int block)这个函数主要实现了取高速缓冲中得指定的缓冲区。其实现的流程如下图2-2所示:getblk又被人占用?进入睡眠状态搜索hash表是块在高速缓冲?中?否搜索空闲队列否找到合适的块?等待解锁是又被人占用?否是块已被修改?是已在高速缓冲?否等待解锁设置该块的引用计数1,复位有效和修改标志,并从散列表和空闲队列中移除此块等待解锁是置占用该块的设备号、块号返回缓冲头指针图 2-2 getblk函数的实现流程Getblk函数的具体实现代码如下:/这行是个宏定义,用于同时判断缓冲区的修改标志和锁

18、定标志,并且定义修改标志的权重要比锁定标志大(具体是大一倍)。#define BADNESS(bh) (bh)->b_dirt<<1)+(bh)->b_lock)struct buffer_head * getblk(int dev,int block)struct buffer_head * tmp, * bh;repeat: /首先判断是否在高速缓冲区中,如果在这直接返回缓冲区指针。if (bh = get_hash_table(dev,block)return bh;tmp = free_list;/通过while循环搜索空闲的缓冲块,选择比重最小的缓冲头指针指向

19、tmp缓冲区头,如果发现tmp既没有被修改有没有被锁定的情况下,则说明以为指定设备上的块取得对应的高速缓冲区,退出循环。否则知道遍历完整个空闲缓冲块队列。do if (tmp->b_count)continue;if (!bh | BADNESS(tmp)<BADNESS(bh) /这里增加了b_dirt的权重!bh = tmp;if (!BADNESS(tmp)break;/* and repeat until we find something good */ while (tmp = tmp->b_next_free) != free_list);/如果没有找到空闲的(

20、连比重最小的也没有),直接睡眠,等待有空闲的缓冲区可用。if (!bh) sleep_on(&buffer_wait);goto repeat;/如果缓冲块加锁,则等待加锁。wait_on_buffer(bh);/如果缓冲块被人使用,则直接跳至标志repeat重新执行一遍。if (bh->b_count)goto repeat;/通过while循环等待脏数据被完全同步至设备上。每同步完一次,检查一下是否被人占用,如果占用了,直接返回值repeat开始。while (bh->b_dirt) sync_dev(bh->b_dev);wait_on_buffer(bh);i

21、f (bh->b_count)goto repeat;/* NOTE! While we slept waiting for this block, somebody else might */* already have added "this" block to the cache. check it */在高速缓冲hash表中检查指定缓冲区是否已经被加入。如果是的话,就跳至repeat处执行。if (find_buffer(dev,block)goto repeat;/* OK, FINALLY we know that this buffer is the o

22、nly one of it's kind, */* and that it's unused (b_count=0), unlocked (b_lock=0), and clean */bh->b_count=1;bh->b_dirt=0;bh->b_uptodate=0;remove_from_queues(bh); /conghash队列和空闲链表中移除该缓冲头,让该缓冲区用于指定设备和其上的指定块。bh->b_dev=dev;bh->b_blocknr=block;insert_into_queues(bh); /然后根据新的设备号和块号重新

23、插入空闲链表和hash队列新位置处。并最终返回缓冲头指针。return bh;2.1.2 bread函数 根据对getblk()函数的分析,它最终可能返回了一个新的空闲块,也可能返回的正好使我们需要数据的缓冲块。对于bread()函数来说,通过getblk()获取了新的缓冲块,它就需要判断该缓冲块的更新标志,看看所含数据是够有效,如果有效就可以直接将该数据块返回给申请的程序,否则就需要调用设备的底层读写函数(ll_rw_block(),并同时让自己进入睡眠状态,等待数据被读入缓冲块。其具体的流程图如下图2-3所示。bread获取缓冲块(getblk)否是块中数据有效?调用块设备底层块读写函数l

24、l_rw_block()进入睡眠等待状态是块中数据有效?否返回缓冲头指针释放该缓冲块返回NULL图 2-3 bread()函数的实现流程图注释1:breada()和bread_page()函数与bread()实现方式类型,就暂时不做流程图分析的。 bread()函数的具体实现代码及分析如下:struct buffer_head * bread(int dev,int block)struct buffer_head * bh; /判断getblk()是否返回缓冲头指针,如果没有,则打印错误信息。if (!(bh=getblk(dev,block)panic("bread: getbl

25、k returned NULLn"); /判断数据的更新标志,数据有效则返回缓冲头指针if (bh->b_uptodate)return bh; /如果数据无效,则利用底层函数直接从设备上读取数据。ll_rw_block(READ,bh); /等待解锁wait_on_buffer(bh); /判断更新标志位。如果数据无效,释放bh,返回NULL;反之,则返回bh。if (bh->b_uptodate)return bh;brelse(bh);return NULL;2.1.3 remove_from_queues、insert_into_queues、find_buffe

26、r和get_hash_table函数remove_from_queues和insert_into_queues实现了缓冲头的移出和插入的功能,其具体的实现代码在下面分别进行分析。l remove_from_queues/这里是以内联函数实现的,可以加速代码执行速度,免去了函数调用的系统代价。static inline void remove_from_queues(struct buffer_head * bh)/* remove from hash-queue */if (bh->b_next)bh->b_next->b_prev = bh->b_prev;if (b

27、h->b_prev)bh->b_prev->b_next = bh->b_next;/如果该缓冲区是该队列的头一块,则让hash表的对应项指向本队列中得下一个缓冲区。 by chhif (hash(bh->b_dev,bh->b_blocknr) = bh)hash(bh->b_dev,bh->b_blocknr) = bh->b_next; /如果缓冲头是hash队列的链表头,则将hash项指向下一个缓冲头。/* remove from free list */if (!(bh->b_prev_free) | !(bh->b_

28、next_free)panic("Free block list corrupted");bh->b_prev_free->b_next_free = bh->b_next_free;bh->b_next_free->b_prev_free = bh->b_prev_free;if (free_list = bh)free_list = bh->b_next_free;l insert_into_queuesstatic inline void insert_into_queues(struct buffer_head * bh)/

29、* put at end of free list */bh->b_next_free = free_list;bh->b_prev_free = free_list->b_prev_free;free_list->b_prev_free->b_next_free = bh;free_list->b_prev_free = bh;/* put the buffer in new hash-queue if it has a device */bh->b_prev = NULL;bh->b_next = NULL;if (!bh->b_dev

30、)return; /将缓冲头在链表头进行插入。bh->b_next = hash(bh->b_dev,bh->b_blocknr);hash(bh->b_dev,bh->b_blocknr) = bh;bh->b_next->b_prev = bh;对于这两个函数进行分析后发现其插入规则如下:n 插入到空闲缓冲链表中时,是选择插入到free_list之前一个位置。n 插入hash队列中,是直接插入到dev对应的hash行所在的链表的头指针处。l find_bufferstatic struct buffer_head * find_buffer(int

31、 dev, int block)struct buffer_head * tmp; /首先通过hash(dev,block)所在的hash行链表中,然后对联表进行遍历,如果找到了dev和block号都相同的缓冲头,指直接返回tmp指针,反之,则返回NULL。for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next)if (tmp->b_dev=dev && tmp->b_blocknr=block)return tmp;return NULL;l get_hash_tablestruct buff

32、er_head * get_hash_table(int dev, int block)struct buffer_head * bh;/进入一个循环,首先在高速缓冲区中寻找相应的数据块。如果没有找到,直接返回NULL,否则对其引用递增1,等待此缓冲块的解锁。如for (;) if (!(bh=find_buffer(dev,block)return NULL;bh->b_count+;wait_on_buffer(bh);/由于经过了睡眠状态,因此有必要再验证一下该缓冲区的正确性,并返回缓冲头的指针。这都是由于竞争的原因,并且没有对缓冲区加锁。if (bh->b_dev = dev && bh->b_blocknr = block)return bh;/如果该缓冲区所属的设备号或者块

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论