fat32文件系统的实现与buddy算法

报告一    FAT32文件系统的实现

文件系统(File System)是计算机系统必不可少的组成部分,可以说除了部分结构简单的单片机系统之外,文件系统是支撑每一个计算机系统运行的最重要的支撑,无论是操作系统、应用程序、文档还是音视频都是基于文件系统的。所以由此可见文件系统在计算机上的重要地位。

起初所有的FAT文件系统都是为PC机器设计的,这说明了一个重要的问题:FAT文件系统在磁盘上的数据是以“小端”结构存储的。我们使用4个8-bit字节----起始字节为byte[0],结束字节为byte[3],类存储一个32-bit的FAT项。然后分别给这32位编号为00—31,从下表可以看出这32位如何排序。

Byte[3]  3 3 2 2 2 2 2 2

         1 0 9 8 7 6 5 4

Byte[2]  2 2 2 2 1 1 1 1

         3 2 1 0 9 8 7 6

Byte[1]  1 1 1 1 0 0 0 0

         5 4 3 2 1 0 9 8

Byte[0]  0 0 0 0 0 0 0 0

         7 6 5 4 3 2 1 0

这对于那些使用“大端”存储结构的机器就显得尤为重要,因为在磁盘存取数据之前,必须完成big-endian和little-endian之间的转换。

每个FAT32文件系统由DBR及其保留扇区,FAT1,FAT2和DATA四个部分组成,其结构如下图:

这些结构是在分区被格式化时创建出来的,含义解释如下:

DBR及其保留扇区:DBR的含义是DOS引导记录,也称为操作系统引导记录,在DBR之后往往会有一些保留扇区。

FAT1:FAT的含义是文件分配表,FAT32一般有两份FAT,FAT1是第一份,也是主FAT。

FAT2:FAT2是FAT32的第二份文件分配表,也是FAT1的备份。

DATA:DATA也就是数据区,是FAT32文件系统的主要区域,其中包含目录区域。

 

FAT32文件系统的DBR有5部分组成,分别为跳转指令,OEM代号,BPB,拓展BPB,引导程序和结束标志。

跳转指令将呈现执行流程跳转到引导程序处;

OEM ID由厂商指定,这里是MSDOS5.0;

BPB记录文件系统相关的重要信息,由BPB和拓展BPB组成。

 

接下来一个重要的数据结构就是FAT表,它是一一对应与数据区簇号的列表。

文件系统分配磁盘空间按簇来分配的。因此,文件占用磁盘空间时,基本单位不是字节,而是簇。即使某个文件只有一个字节,操作系统也会给他分配一个最小单元----即一个簇。

磁盘格式化后,用户文件是以簇为单位存放在数据区中,一个文件至少占用一个簇。当一个文件占用多个簇时,这些簇的簇号不一定是连续,但这些簇号间有由存储该文件时确定了的顺序,即每个文件都有其特定的“簇号链”。

在磁盘上的每一个可用的簇在FAT中就只有一个登记项,通过在对应簇号的登记项内填入“表项值”来表明数据区中的该簇是否占用、空闲或是已损坏的。损坏的簇是在格式化的过程中,通过FORMAT命令发现。在一个簇中,只要有一个扇区有问题,该簇就不能够使用。

现将FAT表的组成格式及功能总结如下:
·表明磁盘类型。FAT的第0簇和第1簇为保留簇,其中第0字节(首字节)表示磁盘类型,其值与BPB中磁介质说明符对应的磁盘类型相同。
·表明一个文件所占用各簇的簇链分配情况。FAT从002簇开始分配给文件。表项值“001H--FEFH”、“0001H--FFEFH”或“00000001H--FFFFFFEFH”中的任一值表明文件的下一簇号。文件的起始簇号由文件目录表(FDT)中每个目录登记项的第26、27字节决定,作为FAT的入口,起始簇号在FAT中的表项值即文件的第2簇号,第二簇号的表项值即第3簇号,依此类推,直到表项值为FF8H--FFFH、FFF8H--FFFFH或FFFFFFF8H--FFFFFFFFH,表示该簇为文件的最后一簇。
·标明坏簇和可用簇。若软盘格式化时发现坏扇区,即在相应簇的表项中写入FF7H(或FFF7H),表明该扇区所在簇不能使用,DOS就不会将它分配给用户文件。
磁盘上未用,但可用的“空簇”的表项值为000H(或0000H)。当需要存放新文件时,文件管理系统将它们按一定顺序分配给新文件。
虽然FAT表记录了文件所用的磁盘空间信息,但是DOS引导区、两个FAT表、文件目录区等并不由FAT表中的簇表示。

 

FAT表结构及作用

1、FAT32文件一般有两份FAT,他们由格式化程序在对分区进行格式化时创建,FAT1是主,FAT2是备份。

2、FAT1跟在DBR之后,其具体地址由DBR的BPB参数中指定,FAT2跟在FAT1的后面。

3、FAT表由FAT表项构成,我们把FAT表项简称FAT项,每个FAT项占用4字节。

4、每个FAT项都有一个固定的编号,这个编号从0开始。

5、FAT表项的前两个FAT项为文件系统保留使用,0号FAT为介质类型,1号FAT为文件系统错误标志。

6、分区的数据区中每个簇都会映射到FAT表中的唯一一个FAT项,因为0号FAT和1号FAT被系统占用,用户的数据从2号FAT开始记录。

7、如果某个文件占用很多个簇,则第一个FAT项记录下一个FAT项的编号(既簇号),如果这个文件结束了,则用“0F FF FF FF”表示。

8、分区格式化后,用户文件以簇为单位存放在数据区中,一个文件至少占用一个簇。

9、FAT的主要作用是标明分区存储的介质以及簇的使用情况。

 

定位FAT绝对位置的方法如下:

1、首先从MBR的分区表中得知分区的起始扇区,偏移到此扇区。

2、从DBR的BPB中得知DBR的保留扇区数,FAT表的个数,FAT表的大小。

3、因此FAT1=分区起始扇区+DBR保留扇区,FAT2=分区起始扇区+DBR保留扇区+FAT1。

 

数据区的位置在FAT2的后面,具体定位方式如下;

1、通过MBR中的分区表信息得知分区的起始位置。

2、通过分区中DBR得知DBR的保留扇区数以及FAT表的大小,FAT表的个数。

3、通过上面的信息就可以找到数据区的起始位置,根目录=数据区的起始扇区+(簇大小*2)。

 

数据区的类容主要由三部分组成:根目录,子目录和文件内容。在数据区中是以“簇”为单位进行存储的,2号簇被分配给根目录使用。

根目录的定位方式为:根目录=分区起始扇区+DBR保留扇区+(FAT表*2)+(簇大小*2)

FAT32文件系统中,分区根目录下的文件和目录都放在根目录区中,子目录中的文件和目录都放在子目录区中,并且没每32个字节为一个目录项,每个目录项纪录着一个目录或文件(也可能是多个目录项记录一个文件或目录),如上图所示就是一个目录项。

在FAT32文件系统中,目录项可以分为四类:卷标目录项、“.”和“..”目录项、短文件名目录项、长文件名目录项。

卷标目录项:卷标就是分区的名字,可以在格式化分区时创建,也可以随意修改,长度为11字节。

“.”和“..”目录项:“.”表示当前目录,“..”表示上一层目录。这两个目录项多存在子目录中。

短文件名目录项:所谓短文件名既文件名的“8.3”格式,此格式支持主文件名不能超过8字节,扩展名不能超过3字节。短文件名目录始终存放在一个目录项中。

短文件名的各参数解释如下:

关于时间的表达方式:10111(23)   111000(56)   00001(1*2)时间值:23时56分02秒。

关于日期的表达方式:0011101(29+1980)   1010(10)   10011(19)时间值:2009年10月19日。

长文件名目录项:由于短文件名“8.3”的格式远远不能满足现实中的需求,所以就出现了长文件名。

FAT32长文件目录项32个字节的表示定义如下:

 

小结

上述的内容已经简单的介绍了FAT32文件系统,下面根据定位某个文件来详细的了解FAT32文件系统是如何存储数据的。

1、根据磁盘0号扇区MBR的分区表得知分区的起始位置,既DBR;

2、根据DBR中BPB记录的信息,得知DBR保留扇区数,FAT的大小,FAT的个数;

3、根据上述信息可以算出数据的起始位置,数据区=分区起始扇区+DBR保留扇区+(FAT表*2);

4、计算根目录所在的绝对位置,根目录=数据区的起始扇区+(簇大小*2);

5、根据根目录中的目录项信息得知,根目录下的文件以及子目录等所对应的簇;

6、根据文件的簇号就可以找到文件内容的绝对扇区;

7、如果一个文件占用多个簇,则需要根据FAT表项得知下一个数据簇的簇号。

7、如果根目录下的目录项是子目录的话,则根据子目录中的文件目录项得知文件内容的簇号;

8、如果子目录中还有子目录的话,则根据这种方法一直找下去即可。

 

报告二    linux中内存管理Buddy如何实现

在系统运行过程中,经常需要分配一组连续的页,而频繁的申请和释放内存页会导致内存中散布着许多不连续的页,这样,当某一时刻要申请一块较大的连续内存时,虽然系统内存余量足够,即很多页是空闲的,但找不到一大块连续的内存供使用。

Linux内核中使用伙伴系统(buddy system)算法来管理内存页。它把所有的空闲页放到11个链表中,每个链表分别管理大小为1,2,4,8,16,32,64,128,256,512,1024个页的内存块。

buddy就是为了解决外部碎片(外部碎片指的是还没有被分配出去(不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域。)问题,它将内存按照按2的幂级(order)大小排成链表队列,存放在free_area数组。

对1024个页框的最大请求对应着4MB大小的连续RAM块。每个块的第一个页框的物理地址是该块大小的整数倍。例如,大小为16个页框的块,其起始地址是16*2^12的倍数。内核试图把大小为b的一对空闲伙伴块合并为一个大小为2b的单独块。满足以下条件的两个块称为伙伴:

1,两个块具有相同的大小,记作b

2,他们的物理地址是连续的

3,第一块的第一个页框的物理地址是2*b*2^12的倍数

该算法是迭代的,如果它成功合并所释放的块,它会试图合并2b的块,以再次试图形成更大的块。

 

伙伴算法主要步骤:

1.将空闲页面分为m个组,第1组存储2^0个单位的内存块,第2组存储2^1个单位的内存块,第3组存储2^2个单位的内存块,第4组存储2^3个单位的内存块,以此类推,直到m组。

2.每个组是一个链表,用于连接同等大小的内存块。

3.伙伴块的大小是相等的,并且第1块和第2块是伙伴,第三块和第四块是伙伴,以此类推。

 

伙伴算法分配内存:

1.如果该数组有剩余内存块,则分配出去。

2.若没有剩余内存块就沿数组向上查找,然后再将该内存块分割出来s并将剩余的内存块放入相应大小的数组中。

Linux使用Buddy算法来有效的分配与回收页面块。页面分配代码每次分配包含一个或者多个物理页面的内存块。页面以2的次幂的内存块来分配。这意味着它可以分配1个、2个和4个页面的块。只要系统中有足够的空闲页面来满足这个要求(nr_free_pages > min_free_page),内存分配代码将在free_area中寻找一个与请求大小相同的空闲块。free_area中的每个元素保存着一个反映这样大小的已分配与空闲页面 的位图。例如,free_area数组中第二个元素指向一个反映大小为四个页面的内存块分配情况的内存映象。

分配算法首先搜寻满足请求大小的页面。它从free_area数据结构的list域着手沿链来搜索空闲页面。如果没有这样请求大小的空闲页面,则它搜索两倍于请求大小的内存块。这个过程一直将持续到free_area 被搜索完或找到满足要求的内存块为止。如果找到的页面块大于请求的块则对其进行分割以使其大小与请求块匹配。由于块大小都是2的次幂所以分割过程十分简单。空闲块被连进相应的队列而这个页面块被分配给调用者。

例如分配5大小的内存块

定位到大小为8的链表中,若该链表中之中没有空余元素,则定位到16的链表中,16中有剩余元素,则取出该元素,并分割出大小为8的内存块供用户使用,然后将剩余的8连接到大小为8的数组中。

 再比如如图所示,要分配4(2^2)页(16k)的内存空间,算法会先从free_area[2]中查看nr_free是否为空,如果有空闲块,就直接从中摘下并分配出去,如果没有空闲块,就顺着数组向上查找,从它的上一级free_area[3](每块32K)中分配,如果free_area[3]中有空闲块,则将其从链表中摘下,分成等大小的两部分,前四个页面作为一个块插入free_area[2],后4个页面分配出去,如果free_area[3]也没有空闲,则从更上一级申请空间,如果free_area[4]中有,就将这16(2*2*2*2)个页面等分成两份,前一半挂在free_area[3]的链表头部,后一半的8个页等分成两等分,前一半挂free_area[2]的链表中,后一半分配出去。依次递推,直到free_area[max_order],如果顶级都没有空间,那么就报告分配失败。

 

伙伴算法内存合并:

当用户用完内存后会归还,然后根据该内存块实际大小(向上取整为2的幂)归入链表中,在归入之前,

1.我们还要检测他的伙伴内存块是否空闲,

2.如果空闲就合并在一起,合并后转到1,继续执行。

3.若果不是空闲的就直接归入链表中。

将大的页面块打碎进行分配将增加系统中零碎空闲页面块的数目。页面回收代码在适当时机下要将这些页面结合起来形成单一大页面块。事实上页面块大小决定了页面重新组合的难易程度。

当页面块被释放时,代码将检查是否有相同大小的相邻或者buddy内存块存在。如果有,则将它们结合起来形成一个大小为原来两倍的新空闲块。每次结合完之后,代码还要检查是否可以继续合并成更大的页面。最佳情况是系统的空闲页面块将和允许分配的最大内存一样大。

注意:在Buddy System进行合并时,只会将互为伙伴的两个存储块合并成大块,也就是说如果有两个存储块大小相同,地址也相邻,但是不是由同一个大块分裂出来的,也不会被合并起来。

一般来说,伙伴算法实现中会用位图记录内存块是否被使用,用于伙伴内存的合并。

再比如如图所示,释放是申请的逆过程,也可以看作是伙伴的合并过程。当释放一个内存块时,先在其对于的free_area链表中查找是否有伙伴存在,如果没有伙伴块,直接将释放的块插入链表头。如果有或板块的存在,则将其从链表摘下,合并成一个大块,然后继续查找合并后的块在更大一级链表中是否有伙伴的存在,直至不能合并或者已经合并至最大块2^10为止。内核试图将大小为b的一对空闲块(一个是现有空闲链表上的,一个是待回收的),合并为一个大小为2b的单独块,如果它成功合并所释放的块,它会试图合并2b大小的块。

 

伙伴算法的特点:

显而易见,伙伴算法会浪费大量的内存,(如果需要大小为9的内存块必须分配大小为16的内存块)。而优点也是明显的,可以解决物理内存的碎片化,分配和合并算法都很简单易行。但是,当分配和回收较快的时候,例如分配大小为9的内存块,此时分配16,然后又回收,即合并伙伴内存块,这样会造成不必要的cpu浪费,应该设置链表中内存块的低潮个数,即当链表中内存块个数小于某个值的时候,并不合并伙伴内存块,只要当高于低潮个数的时候才合并。

 

Buddy算法的优缺点:

优点:

  1,较好的解决外部碎片问题

   2,当需要分配若干个内存页面时,用于DMA的内存页面必须连续,伙伴算法很好的满足了这个要求

  3,只要请求的块不超过512个页面(2K),内核就尽量分配连续的页面。

  4,针对大内存分配设计。

缺点:

   1,合并的要求有点严格,只能是满足伙伴关系的块才能合并,比如第1块和第2块就不能合并。

  2, 碎片问题:一个连续的内存中仅仅一个页面被占用,导致整块内存区都不具备合并的条件

  3,浪费问题:伙伴算法只能分配2的幂次方内存区,当需要8K(2页)时,这个好解决,当需要9K时,那就需要分配16K(4页)的内存空间,但是实际只用到9K空间,多余的7K空间就被浪费掉。

 

 

 

猜你喜欢

转载自blog.csdn.net/lcl497049972/article/details/85269114