您好,欢迎来到尚车旅游网。
搜索
您的当前位置:首页FAT32中文版分析+补充

FAT32中文版分析+补充

来源:尚车旅游网
FAT32中文版分析+补充

概述

起先所有的FAT文件系统都是为IBM PC机器而设计的,这说明了一个重要的问题:FAT文件系统在磁盘上的数据是用“小端”(Little Endian)结构存储的。我们使用4个8-bit的字节——起始字节为byte[0],结束字节为byte[3]——来存储一个32-bit的FAT项(FAT entry)。然后分别给这32位编号为00-31。

这对于那些使用“大端”(big-endian)存储结构的机器就显得尤为重要,因为在磁盘存取数据之前,必须先完成Big-Endian到Little-Endian之间的转换。每个FAT文件系统都由4部分组成,这些基本区域按如下顺序排列。

0——保留区(Reserved Region); 1——FAT区(FAT Region);

2——根目录区(Root Directory Region,FAT32卷没有此域); 3——文件和目录数据区(File and Directory Region) 启动扇区与BPB

BPB(BIOS Parameter Block)是FAT文件系统中第一个很重要的数据区,它位于该FAT卷的第一个扇区,同时也属于FAT文件系统基本区域的保留区。这个扇区又叫做“启动扇区”、“保留扇区”、“0扇区”,众多的说法都说明一个相同的问题:该扇区是FAT卷的第一个扇区。

这是FAT文件系统中第一个让人感到迷惑的地方,对于MS-DOS 1.x的版本,启动扇区中并没有BPB这么一个东西,FAT文件系统的最早期版本只有两种不同的格式:使用于单面或双面的360K 5寸软盘。这两种格式是通过FAT的第一个字节(FAT[0]的低8位)来区分的。

在MS-DOS 2.x以后,启动扇区里增加了BPB用于区分磁盘介质,同时不再支持老的磁盘介质区分方式(用FAT的第一个字节来区分),所有的FAT文件系统卷必须在启动扇区中包含BPB。

这又是一个迷惑人的地方,BPB具体是什么样的?在MS-DOS

2.x的定义中,每个FAT卷的扇区数不能多于65536(每个扇区512字节的话最多32MB——2^16×2^9=32MB),这一个限定是由于定义“总扇区数”的变量本身是一个16-bit的数据类型。这一个在MS-DOS 3.x中有所改进,它使用一个32bit的变量来存储“总扇区数”(若每个扇区512字节,则每个FAT卷的最大容量为2^32×2^9=2^41=2048GB)。

在WIN95操作系统中,确切地说应该是在OSR2(OEM Service Release 2)出现的时候BPB的内容有了新的变化,在这一版本中引入了新的FAT类型——FAT32。在FAT16中,由于FAT表的大小了有效的簇数(Cluster),同时也就了磁盘空间的大小,如果每个扇区为512字节的话,那么FAT16格式只能支持到2G。FAT32的引入改变了这一个状况。不再需要增加分区来管理大于2G的硬盘。

FAT32的BPB内容和FAT12/FAT16的内容在BPB_ToSet32区域以前完全一致,而从偏移量36开始,他们的内容有所区别,具体内容要看FAT类型为FAT12/FAT16还是FAT32(后面的内容会提到如何区分FAT格式),这点保证了在启动扇区中包含一个完整的FAT12/FAT16或FAT32的BPB内容,这么做是为了达到最好的兼容性,同时也为了保证所有的FAT文件系统驱动程序能正确地识别和驱动不同的FAT格式,并让他们良好地工作,因为他们包含了现有的全部内容。

NOTE:在以下的描述中,凡是名称与BPB_开头的域都是BPB的一部分,凡是名称与BS_开头的项都是启动扇区(boot sector)的一部分,而不是真正属于BPB内容。下面是FAT 0扇区的内容,BPB也包含其中。

字节位移 0x00 0x03 0x0B 0x40 字段长度 3个字节 8个字节 53个字节 26个字节 字段名 跳转指令 厂商标志和OS版本号 BPB 扩展BPB 0x5A 0x01FE 名称 420个字节 2个字节 Offset(Byte) 大小(Byte) 引导程序代码 有效结束标志 描述 跳转指令。指向启动代码,允许以下两种形式 jmpBoot[0]=0xEBjmpBoot[1]=0x??jmpBoot[2]=0x90;以jmpBoot[0]=0xE9jmpBoot[1]=0x??jmpBoot[2]=0x?? ,,及,,BS_jmpBoot 0 (跳转指令) 3 0x??表示该字节可以为任意8-bit值,这是Intel x86架构3字节的无条件转移指令,跳转到操作系统的启动代码,这些启动代码往往紧接BPB后面0扇区里的剩余字节,当然也可能位于其他扇区。以上的两种形式任取。jmpBoot[0]=0xEB是一种常用的格式。 建议值为MSWIN4.1,此域经常引起人们的误解,其实这只是一个字符串而已,Microsoft的操作系统似乎并不关心此域。但其他厂商的FAT驱动程序可能会检测到此项,这就是为什么建议将此域设定为“MSWIN4.1”的原因,这样可以尽量避免兼容性的问题。你可以更改它的内容,但这有可能造成某些FAT驱动程序无法识别该磁盘。在很多情况下,该域用于显示格式化该FAT卷的操作系统的名称。(厂商标志和OS版本号) 每扇区字节数,取值只能是以下的几种情况:512、1024、2048或者是4096,一般情况下,设置为BS_OEMName 3 (OEM、OS版本号) 8 BPB_BytesPersec 11 (每扇字节数) 2 512将会取得更好的兼容性,目前有很多的FAT代码都是硬性的规定每扇区字节数为512,而不是实际检测该区域的值。Microsoft操作系统能够很好地支持1024、2048和4096各种数值。 NOTE:请不要曲解此处“最好的兼容性”的意思,如果某些存储介质的物体特性决定其值为N,那么你就必须使用该数值N,该数值N一定是小于等于4096。那么取得“最好的兼容性”的办法就是使用该特定的N值。 每簇扇区数,其值必须是2的整数次方(该整数必须大于等于0),如1、2、4、8、16、32、或128,同时还必须保证每簇的字节数不超过32K,即——保证(BPB_BytesPersec * BPB_SecPerClus ≤ 32K)(1024×32)该值大于32K是绝对不允许的,虽然有些版本的操作系统支持每簇字节数最大到K,但很多应用程序的安装程序都无法在这样的FAT文件系统上正常运行。 保留区中保留扇区的数目,保留扇区从FAT卷的第一个扇区开始,此域不能为0,对于FAT12和FAT16必须为1,FAT32的典型取值为32,目前很多FAT程序都是硬性规定FAT12/FAT16的保留扇区为1,而不对此域进行实际的检测,Microsoft的操作系统支持任何非零的BPB_SecPerClus 13 (每簇扇区数) 1 BPB_RsvdSecCnt 14 (保留扇区数) 2 值。 此卷中FAT表的份数。任何FAT格式此域都建议为2。虽然此域取值为其他≥1的数值也是合法的,但是对于很多FAT程序和部分操作系统来说,此项不为2的时候将无法正常工作。但是当不为2时,Microsoft的操作系统仍然能良好工作。可依然强烈建议此项值为2。 BPB_NumFATs 16 (FAT副本数) 1 选择此项的标准值为2的原因是为了提供一份FAT表的备份,当其中一个FAT表所在的扇区被损坏时我们可以从备份的FAT表中读出正确的数据。可是对于一些非磁盘介质的存储器(如FLASH卡),这一特性变得毫无用处,如果想使用1个FAT表来节省空间,那么带来的问题将是某些操作系统无法识别该FAT卷。 对于FAT12和FAT16,此域中包含根目录中的目录项数(每个项长度为32Kbytes),对于FAT32,此项必须为0。对于FAT12和FAT16,此数值乘以32,必须为BPB_BytesPersec的偶数倍,为了达到更好的兼容性,FAT12/FAT16应该取值为512。 早期版本中16-bit的总扇区数,这里的总扇区数包括FAT卷上四个基本区的全部扇区,此域可以为0,若此域为0,那么BPB_TotSec32必须非0,对于FAT32,此域必须为0。对于FAT12/FAT16,此域填写总扇区数,如果该数值小于BPB_RootEntCnt 17 (根目录项数) 2 BPB_TotSec16 19 (总扇区数) 2 0x10000的话,BPB_TotSec32必须为0。 对于“固定”(不可移动)的存储介质而言,0xF8是标准值,对于可以移动的存储介质,经常使用的数值是0xF0,此域合法的取值可以是0xF0,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE和0xFF。另外要提醒的一点是,无论此域写入什么数值,同时也必须在FAT[0]的低字节写入相同的值,这是因为早期的MSDOS 1.x使用该字节来判定是何种存储介质。 FAT12/FAT16一个FAT表所占的扇区数,对于FAT32来说,此域必须为0,在BPB_FATSz32中有指定其FAT表的大小。 每磁道扇区数,用于BIOS中断0x13,此域只对于有“特殊形状”(由磁头和柱面分割为若干磁道)的存储介质有效,同时必须可以调用BIOS的0x13中断得到此数值。 磁头数,用于BIOS的0x13中断,类似于上面的BPB_SecPerTrk,只有对特殊的介质才有效,此域包含一个至少为1的数值,比如1.4M的软盘,此域为2。 在此FAT分区之前所隐藏的扇区数,必须使用调用BIOS的0x13中断可以得到这个数值,对于那些没有分区的存储介质,此域必须为0,具体使用什么由操作系统决定。 BPB_Media 21 (媒体描述符) 1 BPB_FATSz16 22 (每FAT扇区数) 2 BPB_SecPerTrk 24 (每磁道扇区数) 2 BPB_NumHeads 26 (磁头数) 2 BPB_HiddSec 28 (隐藏扇区数) 4 BPB_TotSec32 32 (总扇区数) 4 该卷总扇区数(32-bit),这里的总扇区数包括FAT卷上四个基本区的全部扇区,此域可以为0,若此域为0,则BPB_TotSec16必须为非0,对于FAT32,此域一定是非0。对于FAT12/FAT16,如果总扇区数大于或等于0x10000(KB)的话,此域就是总扇区数,同时BPB_TotSec16的值为0。 从Offset 36(0x24)开始FAT12/16的内容开始区别于FAT32,现在分两个表格列出来,下表为FAT12/16的内容:

名称 大小Offset(Byte) 描述 (Byte) 用于BIOS中断0x13得到磁盘驱动器参数,(0x00为软盘,0x80为硬盘)。 NOTE:此域的值实际上由操作系统来决定。 保留(供NT使用),格式化FAT卷时必须把此域设置为0。 扩展引导标记(0x29),用于指明此后的三个域可用。 卷标序列号,此域以BS_VolLab一起,可用用来检测磁盘是否正确,FAT文件系统可用用此判断连接的可移动磁盘是否正确,此域往往是由时间和日期组成的一个32位值。 磁盘卷标,此域的值必须与根目录中11字节长的卷标一致。 NOTE:FAT文件系统必须保证在根目录的卷标文件更改或是创建的同时,此域的内容能得到及时的更新,当FAT卷没有卷标时,此域的内容为“NO NAME”。 以下的几种之一:FAT12、FAT16、FAT32。 NOTE:不少人错误认为FAT文件系统的类型由此域来确定,仔细点你就能BS_drvNum 36(0x24) 1 BS_Reserved1(壹) BS_BootSig 37(0x25) 38(0x26) 1 1 BS_VolID 39(0x27) 4 BS_VolLab 43(0x2B) 11 BS_FileSysType (0x36) 8 发现此域并不是BPB的一部分,只是一个字符串而已。Microsoft的操作系统并不使用此域来确定FAT文件的类型,因为它常常被写错或是根本不存在,后面将讨论如何来检测一个FAT文件系统的类型,但不管如何,建议您在此域填写正确信息,因为一些非Microsoft的操作系统会检测此域。 下标为FAT32的内容: 名称 大小Offset(Byte) 描述 (Byte) 36(0x24) 4 一个FAT表所占的扇区数,此域为FAT32特有,同时BPB_FATSz16必须为0。 此域FAT32特有。 Bits 0~3:不小于0的活动FAT(active FAT)数目,只有在镜像(mirroring)禁止时才有效。 Bits 4~6:保留; Bits 7:——0 表示FAT实时镜像到所有的FAT表中; ——1 表示只有一个活动的FAT表,这个表就是Bits 0~3所指定的那个。 Bits 8~15:保留。 此域FAT32特有。高位为FAT32的主版本号,低位为次版本号,这个版本号是为了以后更高级的FAT版本考虑,假设当前的操作系统所能支持的FAT32版本号为0:0。那么该操作系统检测到此域不为0时,它便会忽略这个FAT卷,因为它的版本号比系统能支持的版本要高。 此域FAT32特有。根目录所在第一个簇的簇号,通常该数值为2,担不是必须为2。 NOTE:磁盘工具在改变根目录位置时,必须想办法让磁盘上的第一个非坏簇作为根目录的第一个簇(比如第2簇,除非它已经被标记为坏簇),这样的话,如果此域正好为0的话,磁盘检测工具也能很轻松地找到根目录所在簇的位BPB_FATSz32 BPB_ExtFlags 40(0x28) 2 BPB_FSVer 42(0x2A) 2 BPB_RootClus 44(0x2C) 4 置。 此域FAT32特有。保留区中FAT32卷FSINFO结构所占的扇区数,通常为1。 NOTE:在Backup Boot中会有一个FSINFO的备份,但该备份只是更新其中的指针,也就是说无论是主引导记录,还是备份引导记录都是指向同一个FSINFO结构。 此域FAT32特有。如果不为0,表示在保留区中引导记录的备份数据所占的扇区数,通常为6。同时不建议使用6以外的其他数值。 此域FAT32特有。用于以后的FAT扩展使用,对于FAT32,此域用0填充。 与FAT12/FAT16的定义相同,只不过两者位于启动扇区不同位置而已。 与FAT12/FAT16的定义相同,只不过两者位于启动扇区不同位置而已。 与FAT12/FAT16的定义相同,只不过两者位于启动扇区不同位置而已。 与FAT12/FAT16的定义相同,只不过两者位于启动扇区不同位置而已。 与FAT12/FAT16的定义相同,只不过两者位于启动扇区不同位置而已。 通常设置为“FAT32”,请参照FAT12/FAT16部分关于此域的陈述,该域的内容和FAT类型的判定无关。 BPB_FSInfo 48(0x30) 2 BPB_BkBootSec 50(0x32) 2 BPB_Reserved 52(0x34) BS_DrvNum (0x40) 12 1 1 1 4 11 BS_Reserved1 65(0x41) BS_BootSig BS_VolID 66(0x42) 67(0x43) BS_FileSysType 71(0x47) BS_FileSysType 82(0x52) 8 关于FAT的启动扇区还有一点重要的说明:我们假设里面的内容是按照字节排序的,那么扇区[510]的内容一定是0x55,扇区[511]的内容一定是0xAA。

NOTE:很多FAT的资料文档会错误地把0xAA55说成是“启动扇区最后两字节的内容”,这样的陈述是正确的,如果,仅仅是如果——BPB_BytesPerSec的值为512的话。若是BPB_BytesPerSec的值大于512,该标记的位置并没有变(虽然在启动扇区的最后两个字节写上0xAA55完全没有问题)。

关于BPB_TotSec16/32这里再作一点补充:假设现在我们有一块

磁盘或者一个分区,其扇区数为DskSz,如果BPB_aTotSec(BPB_TotSec16或是BPB_TotSec32其中不为0的那个)的值小于或等于DskSz并不会使该FAT卷在使用中出现什么错误,实际上,BPB_TotSec16/32的值不要比DskSz小得离谱就不会有什么错误。

这样做将造成磁盘空间的浪费,程序本身并不会认为该FAT卷存在什么错误。但是,如果BPB_TotSec16/32的值比DskSz大的话将会使FAT卷遭受严重的损坏。因为它超出了存储介质或是磁盘分区的边界。当BPB_TotSec16/32的值比DskSz大时,一些数据将不幸地被丢失。

FAT数据结构(FAT Data Structure)

接下来一个重要的数据结构就是FAT表(Fat Allocation Table),它是——对应于数据区簇号的列表。

文件系统分配磁盘空间是按照簇来分配的。因此,文件占用磁盘空间时,基本单位不是字节而是簇,即使某个文件只有一个字节,操作系统也会给它分配一个最小存储单元——簇。为了可以将磁盘空间有序地分配给相应的文件,而读取文件的时候又能够从相应的地址读出文件,我们把数据区空间分为BPB_BytesPerSec(每扇区字节数)×BPB_SecPerClus(每簇扇区数)字节长的簇来管理,FAT表项的大小与FAT类型有关,FAT12的表项为12bit,FAT16的表项为16bit,而FAT32的表项则为32bit。

对于大文件,需要分配多个簇。同一个文件的数据并不一定完整地存放在磁盘中一个连续的区域内!而往往会分成若干段,像链条一样存放。这种存储方式称为文件的链式存储。为了实现文件的链式存储,文件系统必须准确地记录哪些簇已经被文件占用,还必须为每个已经占用的簇指明存储后继内容的下一个簇的簇号,对于文件的最后一个簇,则需要指明本簇无后继簇。这些都是由FAT表来保存的,FAT表的对应表项中记录着它所代表的簇的有关信息:诸如是否空,是否坏簇,是否已经是某个文件的尾簇等等。

FAT的项数与硬盘上的总簇数相关(因为每一个项要代表一个簇,簇越多,当然需要的FAT表项越多),每一项占用的字节数也与总簇

数有关(因为其中需要存放簇号,簇号越大,当然每项占用的字节数就越大)。

这里说一下FAT目录,其实它和普通文件并没有什么不一样的地方,只是多了一个表示它是目录的属性(attrib),另外就是目录所链接的内容是一个32字节的目录项(32-byte FAT directory entries后面有具体讨论)。除此之外,目录和文件没什么区别。FAT表是根据簇数和文件对应的。第一个存放数据的簇是簇2。

簇2的第一个扇区(磁盘的数据区)根据BPB来计算,首先我们计算根目录所占的扇区数:

RootDirSectors=((BPB_RootEntCnt (BPB_BytesPerSec - 1))/BPB_BytesPerSec;

根目录扇区数=((根目录数 × 32) + (每扇区字节数 - 1))/每扇区字节数(根目录数代表目录的项数,每个项目长度为32bytes)

上图中BPB_RootEntCnt=512,BPB_BytesPerSec=512,则按照上述计算公式,可以得出RootDirSectors=32,也就是说,这个U盘根目录扇区数为32,约为16KB。

因为FAT32的BPB_RootEntCnt(根目录项数)为0,所以对于FAT32卷RootDirSectors的值也一定是0。上式中的32是每个目录项所占的字节数。计算结果四舍五入。

数据区的起始地址,簇2的第一个扇区由下面的公式计算: If(BPB_FATSz16 != 0)(如果FAT12/16一个FAT表所占的扇区数不为0,则该FAT必为FAT12/16)

FATSz = BPB_FATSz16; Else

FATSz = BPB_FATSz32;

FirstDataSector = BPB_RsvdSecCnt + (BPB_NumFATs × FATSz) + RootDirSectors;

仍以上图为例,我们来计算一下这个U盘的数据区第一个扇区号。 上图中BPB_RsvdSecCnt=4,BPB_NumFATs=2,FATSz=246,RootDirSectors已经算出,为32。则数据区起始扇区号

×

32)

+

=4+246×2+32=528.换言之,数据区紧跟在根目录后,根目录的最后一个扇区号为527。

数据区第一个扇区 = 保留扇区数 + (FAT表的份数 × FAT表所占的扇区数) + 根目录扇区数。

NOTE:扇区号指的是针对卷中包含BPB的第一个扇区的偏移量(包含BPB的第一个扇区是扇区0),并不是必须直接和磁盘的扇区相对应。因为卷的扇区0并不一定就是磁盘的扇区0。

给一个合法的簇号N,该簇的第一个扇区号(针对FAT卷扇区0的偏移量)由下式计算:

FirstSectorofCluster 号的位置的缘故)。

例如:下图中是U盘的FAT1截图(WinHex)

=

((N-2)×BPB_SecPerClust)

+

FirstDataSector;(-2是因为起始标志F8 FF FF FF占用两个存放簇

要注意,这里的簇号指的是相当于FAT1的偏移位置,而非指磁盘的最小单位。

根据上面提供的公式:我们计算出任意簇号对应的第一个扇区号。比如,簇号为7。那么簇7的第一个扇区号=((7-2)×)+528=848。(这里的BPB_SecPerClust为)。

NOTE:因为BPB_SecPerClus总是2的整数次方(1,2,4,8,……)这意味着BPB_SecPerClus的乘除法运算可以通过移位(SHIFT)来进行。在当前Intel x86架构2进制的乘法(MULT)和

除法(DIV)的机器指令非常的繁杂和强大,而使用移位来运算则会相对的快很多。

FAT类型辨别

这是一个经常产生错误的地方,并且常常会出现诸如“off by 1”,“off by 2”,“off by 10”和“massively off”的错误,事实上,FAT类型的检测十分简单,FAT的类型——FAT12,或是FAT16或是FAT32——只能通过FAT卷中簇的数量来判定,没有其他办法。

请仔细阅读本段的每一个细节,每个词都很关键。比如“簇数(count of cluster)”并不是指“最大可取得的簇的数量(maximum valid cluster number)”,因为数据区的第一个簇是簇2而不是0或1。

首先我们讨论这个“簇数”是如何计算的,它完全根据BPB的内容来确定,我们先计算根目录所占的扇区数(前面已经有叙述)。

RootDirSectors

=

((BPB_RootEntCnt

×

32)

+

(BPB_BytesPerSec - 1))/BPB_BytesPerSec;

FAT32的RootDirSectors为0。 接下来我们检测数据区中的扇区数: If(BPB_FATSz16 != 0) FATSz = BPB_FATSz16; Else

FATSz = BPB_FATSz32; If(BPB_TotSec16 != 0) TotSec = BPB_TotSec16; Else

TotSec = BPB_TotSec32;

DataSec = TotSec – (BPB_RsvdSecCnt + (BPB_NumFATs × FATSz) + RootDirSectors);

数据扇区 = 总扇区数 – (保留扇区数 + (FAT数 × FAT表所占扇区数) + 根目录扇区数)

仍然以上图中的U盘数据为例,由于U盘的文件系统是FAT16,

所以FAT表的大小即BPB_FATSz16,又因为图示U盘中BPB_TotSec16为0,所以FAT卷上四个基本区的总扇区数取决于BPB_TotSec32,这里的BPB_TotSec32的值为42029408。BPB_RsvdSecCnt为4,BPB_NumFATs为2,FATSz为246,RootDirSectors上面已经计算出为32,则数据扇区数=42029408-(4+(2×246)+32)=4028880。

计算簇数:

CountofClusters = DataSec / BPB_SecPerClus; 簇数 = 数据扇区数 / 每簇扇区数

同样,数据区的簇数=4028880/=62951。这个计算结果和实际结果吻合!

请记住计算结果四舍五入。

现在我们就可以判定FAT的类型了,这部分请仔细阅读,否则会导致off by 1的错误。

在下面的程序中,“<”和“<=”是不一样的,另外注意数字不要弄错。

If(CountofCluster < 4085) { }

Else if(CountofCluster < 65525) { } Else { }

这是检测FAT类型的唯一办法。世界上不存在簇数大于4084的FAT12卷,也不存在簇数小于4085或是大于65524的FAT16卷,同样没有哪个FAT32卷的簇数小于65525。如果你坚持要违背这个规则来创建一个FAT卷,那么Microsoft的操作系统将无法对此卷进行操作,因为它不认为这是FAT文件系统。

NOTE:如前面所说,目前有很多FAT的代码有一些错误,常常会出现 off by 1,2,8,10或是off by 16的错误。因为,为了和现存的代码取得最好的兼容性,强烈建议在格式化FAT文件系统时,尽量

使簇数的取值不要接近4085或65525,最好能和这个分割点的值相差16或更多。

同时请注意这里的簇数(Count of Cluster)是指数据区所占簇的数量(the count of data clusters),从簇2开始算起,而“最大可用的簇数”(Maximum valid cluster number for the volume)是簇数+1,“包括保留簇的簇数(count of cluster including the two reserved cluster)”则为簇数+2。

FAT的另一个重要计算公式:给一个簇号N,它位于FAT表的什么位置呢?对于FAT16和FAT32都比较容易计算,而FAT12则会复杂一些:

If(BPB_FATSz16 != 0) FATSz = BPB_FATSz16; Else

FATSz = BPB_FATSz32 If(FATType == FAT16) FATOffset = N * 2;

Else if (FATType == FAT32) FATOffset = N * 4;

ThisFATSecNum = BPB_RsvdSecCnt + (FATOffset / BPB_BytesPerSec); //FAT表中包含簇N的扇区数 = 保留扇区数 + (簇N位于FAT表的位置/每扇区字节数)

ThisFATEntOffset = REM (FATOffset / BPB_BytesPerSec); 这里要注意,簇的概念,既然FAT分区中存储数据的最小单位是簇,那么现在就是以簇来看待FAT表,对于FAT16来说,每个簇号占据两个字节,所以簇N前的字节数就是(N-1+1)*2,也就是N*2,N*2/每扇区的字节数就可以得到簇N前的扇区数,再加上保留扇区数,就得到簇号在FAT表中的位置。

例如:仍然以U盘的BPB为例。现在想要知道簇128位于FAT表中的什么位置。则按照以上的计算公式,有:

该FAT扇区号=保留扇区+(FAT偏移地址N*2/每扇区字节

数)=4+(128×2/512)=4。这与实际结果吻合(看winhex的info panel)。

REM(…)为求余符号,就是求FATOffset除以BPB_BytesPerSec的余数。ThisFATSecNum是FAT表中包含簇N的扇区数,如果你想得到第二个FAT表中的扇区数,只要加上FATSz(FAT表大小)就是了,如果想得到第三个FAT表中的扇区数,只需要加上FATSz × 2,依此类推。

现在你得到扇区数ThisFATSecNum(记住这是针对FAT卷扇区0的偏移量),假设把该值读入到一个指定的8-bit SecBuff,同时假定数据类型WORD是一个16-bit的带符号类型,而DWORD是一个32-bit的无符号类型。

If(FATType == FAT16) FAT16ClusEntryVal 的内容。

Else

FAT32ClusEntryVal ThisFATEntOffset的内容。

这里要注意:&是取址运算符。运算方向从右到左。加*号后变成指针变量,字节型被强制转换成WORD型。这样,对于FAT16,可以读出一个字的内容,而对于FAT32,可以读出双字——32位。

取得该扇区的内容。

设置该扇区的值使用如下算式: If(FATType == FAT16) *((WORD Else {

FAT32ClusEntryVal = FAT32ClusEntryVal & 0x0FFFFFFF; //舍弃高4位,实际上FAT32的FAT表项被使用的只有28位。

*)

&

SecBuff[ThisFATEntOffset])

=

FAT16ClusEntryVal;

=

(*((DWORD

*)

&

SecBuff[ThisFATEntOffset])) & 0x0FFFFFFF;//返回FAT32类型下

=

*((WORD

*

)

&

SecBuff[ThisFATEntOffset]);// 返回FAT16类型下ThisFATEntOffset

*((DWORD *) & SecBuff[ThisFATEntOffset]) = (*((DWORD *) & SecBuff[ThisFATEntOffset])) & 0xF0000000;//取出高4位的内容,并清空低28bit的内容。

*((DWORD *) & SecBuff[ThisFATEntOffset]) = (*((DWORD *) & SecBuff[ThisFATEntOffset])) | FAT32ClusEntryVal; //放入低28位的内容。

}

我们看看上述FAT代码是如何工作的,实际上每个FAT32的FAT表项只有28-bit可以使用,它的高4位保留,这4位只有在被格式化的时候会被使用到,在格式化时整个FAT32单元的32bit都被设置为0,包括高位的4-bit。

另外要说明的一点,这也是经常被混淆的地方,因为FAT32表项实际上被使用的只有28-bit而不是32-bit。比如,以下几个FAT32簇的值为0x10000000,0xF0000000和0x00000000都表示该簇为空,因为程序忽略了高位4-bit的值。如果当前簇的值为0x30000000,你想要把数值0x0FFFFFF7写入当前簇来标记坏簇,那么当你的操作结束后该簇的实际值为0x3FFFFFF7,因为你必须舍去0x0FFFFFF7这个坏簇标记高位的4-bit。

因为BPB_BytesPerSec的值一定能够被2和4整除,对于FAT16/FAT32来说,你不必担心元素会超越扇区的边界,但对于FAT12,你就必须小心了。

FAT12的代码会显得复杂一点,因为它每个元素(簇号)占1.5个字节(12-bit)。

If(FATType == FAT12)

FATOffset = N + (N/2); //注意等式并没有乘以浮点数1.5,除以2的值四舍五入;

ThisFATSecNum = BPB_RsvdSecCnt + (FATOffset / BPB_BytesPerSec);

ThisFATEntOffset = REM(FATOffset / BPB_BytesPerSec); 现在我们必须考虑扇区边界的情况。

If(ThisFATEntOffset == (BPB_BytesPerSec - 1)) { }

现在我们可以像FAT16一样使用WORD数据类型来对FAT12的数据进行读取,但仍需要注意,如果簇号为偶数,我们取16-Bit中的低12-Bit,如果是奇数则取高12-Bit,如果簇号是奇数,则取高12-Bit(这是因为对于FAT12来说,簇号占据12Bit,也就是说,要存储两个簇号,需要3个字节,回忆一下FAT16里面的簇号排列,

现在一下子取16位,即两个字节,FAT12表里的簇号也是从0开始的,第0簇和第1簇一共三个字节存放FAT12的起始标志,从第2簇(第四个字节)开始存放FAT表项,现在假设要读取第2簇的内容,ThisFATEntOffset的值为3,也就是说从第三个字节开始读取第2簇的内容,记住,簇号为12bit,第3个字节存放这12bit的低8位,第4个字节的低4位存放这12bit的高4位。见下图:

FAT12ClusEntryVal &SecBuff[ThisFATEntOffset]);

If(N & 0x0001)

= *((WORD

*)

FAT12ClusEntryVal = FAT12ClusEntryVal >> 4; Else

FAT12ClusEntryVal = FAT12ClusEntryVal & 0x0FFF; 设置簇的值按照下面的代码进行:

If(N & 0x0001) {

FAT12ClusEntryVal = FAT12ClusEntryVal << 4; *((WORD *) &SecBuff[ThisFATEntOffset]) =

(*((WORD *) &SecBuff[ThisFATEntOffset])) & 0x000F; } Else {

FAT12ClusEntryVal = FAT12ClusEntryVal & 0x0FFF; *((WORD *) &SecBuff[ThisFATEntOffset]) =

(*((WORD *) &SecBuff[ThisFATEntOffset])) & 0xF000; }

*((WORD *) &SecBuff[ThisFATEntOffset]) = (*((WORD FAT12ClusEntryVal;

NOTE:这里的>>为右移操作符,并往高4位填充0;<<为左移操作符,同时低4位用0填充。数据区中的文件是按照以下方式与FAT表相对应的:数据区中文件存放的第一个簇号被记录在目录项中,文件就是根据这个簇号与FAT表相关联,数据区中文件的位置由前面讨论的FirstSectorofCluster来计算。

对于一个大小为0的文件——一个没有数据的文件——在目录项中分配的第一个簇的簇号为0,此簇(参见前面讨论的ThisFATSecNum和ThisFATEntryOffset)的内容要么是一个EOC标记(End Of Cluster chain簇链表结束标记)要么就是该文件的下一个簇的簇号。EOC的值和FAT类型有关(假设FATContent是需要检测看是否包含EOC标记的簇的内容)。

isEOF = FALSE; if (FATType == FAT12) { if(FATContent >= 0x0FF8) isEOF = TRUE; }

Else if (FATType == FAT16) {

*)

&SecBuff[ThisFATEntOffset]))

|

if (FATContent >= 0xFFF8) isEOF = TRUE; }

Else if (FATType == FAT32) { if (FATContent >= 0x0FFFFFF8) isEOF = TRUE; }

NOTE:包含EOC标记的簇属于当前文件并且是当前文件的最后一个簇。Microsoft的操作系统设置EOC标记时FAT12使用0x0FFF,FAT16使用0xFFFF,FAT32使用0x0FFFFFFF,但有一些运行于Microsoft系统的工具并不使用这个值。

还有一个特殊的标记就是“坏簇(Bad Cluster)标记”,任何包含“坏簇”标记的簇都不应该被列入到剩余簇的范畴内,这个“坏簇”标记对于FAT12是0x0FF7,FAT16是0xFFF7,FAT32是0xFFFFFF7。另外,这些坏簇看起来也像是丢失的簇——它们似乎已经被分配出去,因为它们的值并不为0,但同时它们又不属于任何文件。磁盘修复程序一定要认出这些被标记为坏簇标记的“丢失簇”,并且不去修改它们的内容。

NOTE:对于FAT12和FAT16而言,坏簇标记不可能是某个已经分配簇的簇号,但是对于FAT32而言,0x0FFFFFF7就有可能是某个簇的簇号,因此FAT32文件系统必须避免把0x0FFFFFF7这个数字分配出去作为某个簇的簇号。

FAT表中剩余簇的列表就是卷中所有内容为0的簇的列表。这些数据必须尽早取得并记录下来以表示剩余簇是已经被使用的。这个列表并没有存储在卷的任何一个地方,它必须在系统挂上(mount)该卷时,由FAT扫描程序获得内容为0的簇的列表。FAT32的BPB_FSInfo扇区可能会包含剩余簇的数量,请参阅FAT32关于BPB_FSInfo扇区的讨论部分。

在FAT卷起始部分的两个保留扇区到底是做什么用的呢?第一个保留簇FAT[0],它的低位8-bit为BPB_Media,剩余的位用1填充,

比如BPB_Media的内容为0xF8,那么FAT12的内容为0x0FF8,FAT16为0xFFF8,FAT32为0x0FFFFFF8,第二个保留簇FAT[1]在格式化的时候被填充EOC标记。FAT12卷此域不用,其值始终为EOC标记。FAT16和FAT32此域的高2bit可以被用于标记磁盘是否为“脏”(下面描述),剩余的位均用“1”填充。请注意FAT16和FAT32这两位的位置是不一样的,因为是高位2-bit。

对于FAT16

ClnShutBitMask = 0x8000; HrdErrBitMask = 0x4000; 对于FAT32

ClnShutBitMask = 0x08000000; HrdErrBitMask = 0x04000000;

ClnShutBitMask:如果此位为1,那么卷是“干净”的。如果为0,那么此卷是“脏”的。这意味着系统在上次卸载(unmount)此卷时,没有正常地断开连接,此时建议使用ChkDsk/ScanDisk等工具来检测磁盘是否有错误。

Bit HrdErrBitMask : 如果此位为1,表示没有发生磁盘读/写错误。如果为0,表示系统在上次挂载该卷时,有发生过磁盘读/写错误,此时建议运行ChkDsk/Scandisk等工具来扫描磁盘表面看看是否有出现新的坏簇。

关于FAT表这里还有两个重要的问题:

1、FAT表的结束扇区不一定就是FAT表的最后一个扇区,FAT表的结尾扇区位于簇号为CountofClusters + 1(参阅前面关于CountofClusters的计算式)的簇中。这个扇区未必在FAT表的最后面。FAT程序不应该尝试着去访问CountofClusters + 1以后的簇,FAT格式化程序应该把这个簇号后面所有的簇用0填充;

2、BPB_FATSz16(对于FAT32为BPB_FATSz32)的值会比它实际需要的大,也就是说,在FAT表中可能有部分扇区没有被使用。因此,FAT表的结束扇区都是由CountofClusters + 1来计算得到,而不是使用BPB_FATSz16/32来计算。FAT程序不应该尝试去访问这些“额外”

的扇区。FAT格式化程序应该把这些扇区用0来填充。

初始化FAT卷

读到这里,细心的读者一定会发现一个有趣的问题,前面说过FAT的类型(FAT12,FAT16或是FAT32)是根据总的簇数来判别——并且数据区中最大可取得的扇区数由FAT表的大小来决定——那么当一个磁盘还没有被格式化时,我们无法得到这BPB数据,这时是如何检测并计算出正确的值来放到BPB_SecPerClus和BPB_FATSz16或是BPB_FATSz32中呢?Microsoft的操作系统使用一些固定的值和表格配合一个巧妙的算法来完成这些工作。

Microsoft只在软盘上使用FAT12文件系统,因为软盘的种类很少,并且都有固定的参数,格式化时使用的是一张简单的表:

“如果这是一张这种格式的软盘,那么它的BPB看起来就是这个样子(If it is a floppy of this type, then the BPB look like this.)”

对于FAT12格式的计算相对简单,所有写到BPB中的值都可以在一张纸上用手计算出来的(当然得小心——Cluster的值始终小于4085),如果存储介质的容量大于4M,那就别再麻烦FAT12了,只需要把BPB_SecPerClus的值改小一点,这个卷就成FAT16了。

本节以下部分描述如何驱动每扇区512字节的FAT卷。如果扇区大小不是这样子的话,你将不能使用这个表格和算法。不同磁盘的扇区大小不一。这里根据磁盘容量的大小来选择一个“合适的值”来简单区分FAT类型。如果磁盘容量小于该值就是FAT16;如果大于或等于该值,就是FAT32。Windows操作系统选择该值为512MB,任何小于512MB的卷都是FAT16卷,任何大于或等于512MB的卷都是FAT32卷。

这里请特别注意,别过早下结论。

有很多FAT16卷的容量都大于512MB,因为有很多不同的方法可以强制把磁盘格式化成FAT16格式而不是象默认的那样格式化成FAT32格式,并且不同的FAT程序遵循不同的规定来格式化磁盘。这里我们讨论的只是MS-DOS和Windows默认情况下是如何处理未格式化的磁盘。这里有两个表格,一个用于FAT16,一个用于FAT32。

这个表格其中的一项是根据每扇区512字节的磁盘空间大小计算而来(该数值将写入到BPB_TotSec16或是BPB_TotSec32中),还有一个值用来设置BPB_SecPerClus。

struct DSKSZTOSECPERCLUS { DWORD DiskSize; BYTE SecPerClusVal; };

DSKSZTOSECPERCLUS DskTableFAT16 [ ] = {

{ 8400, 0 }, / *磁盘容量最大为4.1MB,SecPerClusVal的值为0表示这是一个错误 */

{ 32680, 2 },/ *磁盘容量最大为16MB,1K Cluster */ { 262144, 4 }, / *磁盘容量最大为128MB,2K Cluster */ { 524288, 8 }, / *磁盘容量最大为256MB,4K Cluster */ { 1048576, 16 }, / *磁盘容量最大为512MB,8K Cluster */ / *除非强制使用FAT16,否则以下数据不使用 */

{ 2097152, 32 }, / *磁盘容量最大为1GB,16K Cluster */ { 4194304, }, / *磁盘容量最大为2GB,32K Cluster */ { 0xFFFFFFFF, 0 } / *磁盘容量超过2GB,SecPerClusVal的值为0意味着这是一个错误 */

};

DSKSZTOSECPERCLUS DskTableFAT32 [ ] = {

{ 66600, 0 }, / *磁盘容量最大为32.5MB,SecPerClusVal的值为0表示这是一个错误 */

{ 532480, 1 }, / *磁盘容量最大为260MB,5K Cluster */ { 16777216, 8 }, / *磁盘容量最大为8GB,4K Cluster */ { 3332, 16 }, / *磁盘容量最大为16GB,8K Cluster */ { 671088, 32 }, / *磁盘容量最大为32GB,16K Cluster */ { 0xFFFFFFFF, }, / *磁盘容量超过了32GB,32K Cluster */ };

这样,只要给出磁盘的大小和FAT的类型就可以确定

BPB_SecPerClus的值,现在我们唯一所缺少的就是FATSz16和FATSz32

的大小。这里我们假设

BPB_RootEntCnt,

BPB_RsvdSecCnt和BPB_NumFATs的值已经按照上面的约定正确地设置。同时我们还假设DiskSize就是我们要写到BPB_TotSec32或是BPB_TotSec16的值。

RootDirSectors

=

((BPB_RootEntCnt

*

32)

+

(BPB_BytesPerSec - 1)) / BPB_BytesPerSec;

TmpVal1 = DskSize – (BPB_RsvdSecCnt + RootDirSectors); TmpVal2 = (256 * BPB_SecPerClus) + BPB_NumFATs; If (FATType == FAT32) TmpVal2 = TmpVal2 / 2;

FATSz = (TmpVal1 + (TmpVal2 - 1)) / TmpVal2; If (FATType == FAT32) { BPB_FATSz16 = 0; BPB_FATSz32 = FATSz; } else {

BPB_FATSz16 = LOWORD(FATSz); }

不要花费太多功夫去琢磨上面的代码是如何工作的,这个算法的原理十分复杂,放在这里的目的只是说明Microsoft的操作系统是如何实现磁盘初始化的。但是,上面的代码并不完美,在一些偶然的情况下,它会使FAT16的FATSz比实际需要的要多出2个扇区,或使FAT32的FATSz多8个扇区,不过它永远不会使FATSz的值比实际需要的小。因为FATSz的值比实际的大并不会影响文件系统的正常工作,唯一的缺点就是造成很少一部分磁盘空间的浪费。由于以上的代码是如此的简单可靠,这在很大程度上弥补了它的不足。

FAT32 FSInfo扇区结构和备份启动扇区

FAT3的FAT表可能非常大,而不像FAT16的大小被在128K以内,或是FAT12的FAT表大小被在6K范围以内一样,因此,

在FAT32的卷中存放着“最新”的剩余簇的数量,在API函数想知道剩余空间时(比如在DIR命令的最后显示剩余空间)不必马上去计算该数值,FSInfo的扇区号存放在BPB_FSInfo中,对于Microsoft的操作系统此值为1,下表是FSInfo的结构:

名称 FSI_LeadSig 大小Offset(byte) 描述 (Byte) 0 4 值为0x41615252,这个标记用来表示该扇区为FSInfo扇区。 保留为以后扩展使用,FAT32格式化程序应该把此域全部设置为0,当前版本的FAT程序不可以访问该域。 值为0x61417272,更具体地表明该扇区已经被使用。 保存最新的剩余簇的数量,如果为0xFFFFFFFF表示剩余簇未知,需要重新计算,除此之外的其他值都可以用,而且不要求十分精确,但必须保证其值≤磁盘所有的簇数。 该域为FAT驱动程序提供一条有利的线索,它告诉驱动程序应该从哪里开始寻找剩余簇,因为FAT32的FAT表可能非常庞大,如果已经分配的簇很多的话要从头开始查找剩余簇,这将耗费大量时间。通常这个值被设定为驱动程序最后分配出去的簇号,如果值为0xFFFFFFFF,那么驱动程序必须从簇2开始查找,除此之外其他的值都可以使用,当然,前提是这个值必须是合法的。 保留为以后扩展使用,FAT32格式化程序应该把此域全部设置为0,当前版本的FAT程序不可以访问该域。 值为0xAA550000,此结束标记用来表示这是一个FSInfo扇区,注意此域的高两位偏移量为510和511,这和启动扇区在相同偏移处的标记是一样的。 FSI_Reserved1 4 480 FSI_StrucSig 484 4 FSI_Free_Count 488 4 FSI_Nxt_Free 492 4 FSI_Reserved2 496 12 FSI_TrailSig 508 4 FAT32区别于FAT12/FAT16的另外一个地方就是BPB_BkBootSec,FAT12/FAT16有可能由于丢失启动扇区的内容而使整个卷都无法访问,这是一个“单点错误(Single Point of Failure)”,

为了避免这种严重的后果,FAT32引进了BPB_BkBootSec,FAT32在扇区号为6的地方完整地拷贝了一份启动扇区的备份,包括BPB的内容。

当启动扇区的内容被损坏后,磁盘修复程序只需要把启动备份扇区中的数据拷贝回启动扇区即可,即使在启动扇区被损坏的情况下,磁盘驱动程序仍然可以在更换硬盘之前正常访问该卷。

在第二种情况下——扇区0被损坏——这就是为什么BPB_BkBootSec的值为什么必须为6的原因,当扇区0不可读时,很多不同的操作系统都是硬性检查FAT32卷在扇区6的启动备份。保存在扇区6中的是一个完整的启动记录。Microsoft的FAT32的“启动扇区”实际上是由3个长度为512字节的扇区组成,在BPB_BkBootSec扇区开始的启动备份中完整的包含了这3个扇区,FSInfo也在其中,虽然在备份中BPB_FSInfo的内容和0扇区中BPB_FSInfo所指向的是同一个FSInfo结构。

NOTE:这三个扇区和启动扇区一样也在偏移量为510和511的地方包含标记0xAA55(参看前面的叙述)。

FAT目录结构(FAT Directory Structure) 这里我们先忽略长目录项的情况,只讨论短目录项。

FAT目录其实就是一个由32Bytes的线性表构成的“文件”。根目录(root directory)是一个特殊的目录,它存在于每一个FAT卷中,对于FAT12/16,根目录的扇区号是相对于该FAT卷第一个扇区(0扇区)的偏移量。

FirstDirRootSecNum = BPB_RsvdSecCnt + (BPB_NumFATs * BPB_FATsz16);

FAT32的根目录由簇链组成,其扇区数是不确定的,这点和普通文件相同,根目录的第一个扇区号存储在BPB_RootClus中,根目录不同于其他的目录,没有日期和时间戳,也没有目录名(“/”并不是其目录名),同时根目录里没有“.”和“..”这两个目录项,根目录另一个特殊的地方在于,根目录中有一个设置了ATTR_VOLUME_ID位(见下表)的文件,这个文件在整个FAT卷中是唯一的。

名称 DIR_Name Offset 大小描述 (Byte) (Byte) 0 11 短文件名 文件属性: ATTR_READ_ONLY 0x01 ATTR_HIDDEN 0x02 ATTR_SYSTEM 0x04 ATTR_VOLUME_ID 0x08 ATTR_DIRECTORY 0x10 DIR_Attr 11 1 ATTR_ARCHIVE 0x20 ATTR_LONG_NAME ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID 前两个属性位为保留位,在文件创建时应该把这两位设为0,在以后的使用中不能读写和更改。 DIR_NTRes 12 1 保留给Windows NT使用,在文件创建时设置该位为0,在以后的使用中不能读写和更改。 文件创建时间的毫秒级时间戳,由于DIR_CrtTime的精度为2秒,所以此域的有效值在0-199之间。 文件创建时间。 文件创建日期。 最后访问日期,请注意并没有最后访问时间域,而只有日期,这日期是指文件被读写的日期,如果是写,该日期还应该被写到DIR_WrDate中。 该目录项簇号的高位字(FAT12/16此位为0) DIR_CrtTimeTeenth 13 DIR_CrtTime DIR_CrtData 14 16 1 2 2 DIR_LastAccDate 18 2 DIR_FstClusHI 20 2 DIR_WrtTime DIR_WrtDate DIR_FstClusL0 DIR_FileSize 22 24 26 28 2 2 2 2 最后写的时间,文件创建被认作写 最后写的日期,文件创建被认作写 该目录项簇号的低位字 文件大小,由32-bit双字组成 DIR_Name[0]

此处特别注释目录项的第一个字节(DIR_Name[0])。

●如果DIR_Name[0] == 0xE5,则此目录为空(目录项不包含文件和目录)

●如果DIR_Name[0] == 0x00,则此目录为空(同0xE5),并且此后的不再分配有目录项(此后所有的DIR_Name[0]均为0)。

不同于0xE5,如果DIR_Name[0]的值为0,那么FAT程序将不会再去检测其后续的磁盘空间,因为这些空间都是空闲的。

●如果DIR_Name[0] == 0x05,则文件名在该位的实际值为0xE5,但0xE5是日文中合法的字符,当需要用0xE5来作为DIR_Name[0]时使用0x05来代替,避免程序误认为该目录项为空。

DIR_Name域实际由两部分组成:8个字符的主文件名和3个字符的扩展名。两部分如果字符数不够的话用空格(0x20)填充(Trailing Space Padded)。

DIR_Name[0]不允许为0x20,主文件名和扩展文件名的间隔“.”并不真实存在于DIR_Name中,小写字母不允许出现在DIR_Name中(这些字符因为不同的国家和地区而异)。

●以下字符不允许出现在DIR_Name中的任何位置

0x22, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D还有0x7C。

以下是一些例子显示用户的输入文件名如何与DIR_Name对应: “foo.bar” → “FOOBAR” “FOO.BAR”; → “FOOBAR” “Foo.Bar” → “FOOBAR” “foo” → “FOO”

“PICKLE.A” →“PICKLE A”

“prettybg.big” → “PRETTYBGBIG” “.big” →非法,DIR_Name[0]不能为0x20。

在FAT的目录中,所有的文件名都是唯一的,上面例子中的三个文件名看起来似乎各有千秋,事实上它们都是相同的文件名,在同一个目录中,只能有一个DIR_Name被设置为“FOOBAR”。

文件属性(DIR_Attr):

ATTR_READ_ONLY:对这个文件的写操作将会失败。 ATTR_HIDDEN:正常模式显示该目录列表时不显示该文件。 ATTR_SYSTEM:是系统文件。

ATTR_VOLUME_ID:在一个FAT卷中,只能有一个“文件”设置此位,并且该文件必须在根目录中,该文件的文件名实际上就是该卷的卷标,并且该文件的DIR_FstClusHI和DIR_FstClusL0必须为0(卷标文件不分配空间)。

ATTR_DIRECTORY:目录

ATTR_ARCHIEVE:此属性用于支持一些备份程序,当文件创建,改名或写入时,FAT文件系统会设置此位,备份程序可以利用此位来判断卷中的哪些程序从上次备份到现在有更改过。

另外,ATTR_LONG_NAME位实际上表明该“文件”实际上为另外一个有长文件名的文件的一部分,在下一个章节我们将详细讨论长文件名的情形。

创建一个目录时,该“文件”的ATTR_DIRECTORY位被置位的同时DIR_FileSize被设置为0,ATTR_DIRECTORY被置位的文件不使用DIR_FileSize并且该域通常为0(目录所占空间为其起始簇到EOC结束的簇链所占的空间),每个目录项分配一个簇(除非是FAT12/FAT16的根目录),将DIR_FstClusL0和DIR_FstClusHI的值设置为该簇的簇号,然后在FAT表中为该簇设置一个EOC标志,并把该簇的每一个字节设置为0,如果这是根目录,那么你的工作就完成了(根目录没有“.”和“..”)。否则,你必须在该目录空间(就是刚刚分配的那个簇)的头两个32-byte 创建两个特殊的目录项。

第一个目录项的DIR_Name设置为:

“.” “..”

我们称这两个目录为“点”和“点点”,这两个目录的DIR_FileSize均设置为0,同时两个目录的时间和日期和日期标志也设置为以包含它们本身的目录相同,将“.”目录项的DIR_ClusL0和DIR_ClusHI设置为与它自身所在的目录(包含这两个“.”和“..”目录项的目录)相同。

最后把“..”目录项的DIR_ClusL0和CLUS_ClusHI设置为与刚刚创建目录项所在目录的第一个簇号(如果刚创建的目录在根目录则这些值为0,FAT32也是如此)。

“.”和“..”的特征可以概括如下: “.”目录指向该目录本身; “..”目录指向该目录的上级目录。

还记得DOS操作系统吗?要返回上级目录需要输入CD .. 这里的“..”就是上级目录的意思,这个指令的意思就是进入当前目录的上级目录。

转载于:https://my.oschina.net/u/58/blog/95978

FAT16、FAT32 文件系统

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。

https://blog.csdn.net/zisehuoxia/article/details/46659531

FAT,英文为File Allocation Table,文档分配表。 先要记住几个概念:

扇区:一般扇区为512个字节。 簇:由若干个扇区组成。

而FAT文件系统(其他文件系统应该相似)就是专门管理这些簇的。一个文件可能占据一个或者多个簇,按正确的顺序去读取这些簇,就可以获取这个文件的内容了。

FAT16, FAT32的区别

FAT16是用16位即2个字节来表示一个簇,这2个字节有两重意思:第一,它本身所在的位置对应一个特定位置的簇。第二,它的内容指向的是下一个2个字节。

而FAT32是用32位即4个字节来表示一个簇。这4个字节的意思和FAT16中2个字节的意思相同。

2个字节取值范围是0x0000-0xFFFF。4个字节的取值范围是0x00000000-0xFFFFFFFF。但是它们真正的有效范围如下表:

看之前可以先详细看看下面的网址。 参考网址:

http://wenku.baidu.com/link?url=BpR7QQNeY2fOJGWg0M5AB-DTy9OlR5rJc4tn77dtObFJOUoT5laUi0ScspjsIacrx1lZIzBVnxWL7HIkyDk2nCrlkPBeVh6KfiME_4xt56u

http://wenku.baidu.com/view/c0656e1ea300a6c30c229f15.html

http://blog.csdn.net/new_abc/article/details/7409279 http://wenku.baidu.com/view/45817b49cf84b9d528ea7a99.html

http://www.cnblogs.com/andtt/articles/2352820.html 相信看了上面网址的内容,下面的就可以忽略了。

FAT文件系统的组成一般有MBR,DBR,FAT1,FAT2,FDT和数

据区组成。FAT16和FAT32差别不大。一般将DBR所处的位置称谓逻辑扇区0。既然DBR是逻辑上的扇区0,那么怎么能够找到它嘞。MBR是物理上的扇区0,即绝对地址0处。通过MBR可以获取DBR的绝对地址。

以SD卡为例简要说明FAT32: FAT32的结构

MBR-保留扇区0-DBR-FSINFO-保留扇区1-DBR副本-FSINFO副本-保留扇区2-FAT1-FAT2-根目录FDT-数据区

通过WinHex可以以物理和逻辑两种方式来打开SD卡。在这里用物理方式打开,分别查找FAT32结构中的信息。

FAT文件系统最初是在IBM PC机上实现的,因此磁盘上的数据都是按“小端模式”存储的。 MBR

下图是一个4G sd卡的MBR信息,图中只标出了感兴趣的内容。

保留扇区0

在MBR中可以看到此处保留扇区为0x00002000-1个扇区,因为第0x00002000个扇区是DBR。如果在没有MBR的分区中,当然也就不存在保留扇区0了。 DBR

(有时可能没有MBR。DBR的物理地址和逻辑地址相同,都是0地址。)

由MBR图可知,DBR所在的扇区在8192物理扇区处。即8192*512=4194304D=400000H=4MB处。在WinHex中跳转到此

处。

在DBR中可以得出DBR之后结构的信息。

其中:

地址了。此处为:0x400000H+0x24H*0x200H

标号3保留扇区数,意为此扇区加上这么多扇区就是FAT1的扇区标号5为此扇区之前有多少个空余扇区,和MBR中指向DBR扇区地址是完全一致的。

标号6为磁盘扇区总数。关于这里的总数包括哪些扇区呢?经过观察几个U盘和SD卡,发现这里的扇区总数是从DBR开始在内的共0x760000

个扇区。即此盘的最后地址为

0x400000+0x760000*0x200-1=0xEC400000-1=0xEC3FFFFF。

这个地址是SD的物理地址的最末尾。

标号9为FSINFO扇区地址,即此扇区加上这么多就是FSINFO的扇区地址了。此处为:0x400000H+0x200H

标号10为DBR的备份扇区相对地址。此处为:0x400000H+0x06*0x200

问题1:如果将MBR和DBR中的磁盘扇区总数修改会出现什么情况?(这个问题可以看了上面的网址和这篇博文后理解)

答:关于这一点,在上面网址中的FAT中也有说明(第6页)。现结合自己理解谈谈。

1>如果将MBR和DBR中的磁盘扇区总数同时减小,比如减小为0x3FE000,即磁盘的最后地址为0x400000+0x3FE000*0x200-1=0x80000000B-1=2GB,即磁盘变成了2G的了。就是说在FAT扇区数没有变化的情况,磁盘可以使用的FAT文件系统变小了。但是FAT表占的扇区数是不会变的。上面说扇区总数同时减小,是的,如果不同时减小,可能文件系统检测是会出异常。可知,可以减少磁盘扇区总数达到减小FAT文件系统的目的,当然磁盘扇区总数后面的地址数据将不会被刷新。

2>如果将MBR和DBR中的磁盘扇区总数同时增大,会出现什么情况呢。

针对FAT32,4个字节表示一个簇。那么一个扇区可以表示512/4=128个簇。(假设每个扇区有512字节,最好使用此值)。如果一个簇4k,那么FAT表的一个扇区可以表示512k。我们知道,FAT表是以扇区数来计算个数的。因此,FAT表的最末尾扇区中有可能有空余字节。如果增大扇区总数,那么FAT文件系统就会去寻址磁盘最大容量以外的地址(可能会出现异常),而且FAT表最末尾的空余字节就会被使用。FAT上说导致数据丢失,可能就是超出磁盘最大容量地址以外的那部分数据吧。原有的数据应该不会丢失。

DBR备份扇区和DBR扇区完全一样 FSINFO

根据DBR中的信息,可知道FSINFO的地址为0x400200

Note:

剩余簇数量:保存最新的剩余簇数量,如果为0xFFFFFFFF表示剩余簇未知,需要重新计算,除此之外其他的值都可以用,而且不要求十分精确,但必须保证其值<=磁盘所有的簇数。

下一个可用簇:该域为FAT驱动程序提供一条有利的线索,它告诉驱动程序从哪里开始寻找剩余簇。因为FAT32的FAT表可能非常的庞大,如果已经分配的簇很多的话要从头开始查找剩余簇将耗 费大量时间。通常这个值被设定为驱动程序最后分配出去的 簇号。如果值为0xFFFFFFFF,那么驱动程序必须从簇2开始查找,除此之外其他的值都可以使用,当然前提是这个值必须合法的。

保留扇区1

此处为5个,因为第6个是DBR备份扇区。(DBR结构中已说明)

DBR副本(DBR中已说明)内容和DBR完全一致,用于DBR访问出错时,来恢复DBR。

FSINFO副本(DBR副本+FSINFO相对于DBR的位置)FSINFO的副本与FSINFO并不是完全一致 保留扇区2

在DBR结构中知道,DBR到FAT1中间有36个扇区,减去里面的FSINFO,保留扇区1,DBR副本,FSINFO副本,就是保留扇区2的数量了。

FAT1

FAT1的地址在DBR中就可以看出来了,即DBR的地址加上保留扇区数就是FAT1的地址。即:0x400000+0x24*0x200=0x404800。如下图所示。

FAT32是用4个字节表示一个簇。第0簇和第1簇事先被占用。数据区是从第2簇开始的。

第2簇中的数据为结束簇,它又是根目录簇,说明了,根目录下面的文件数小于4k/32=128个文件(4k表示1个簇的大小,32表示32个字节代表1个文件或目录)。如果根目录下面的文件数大于128,那么FAT表中第2簇的位置应该存的是指向下一个根目录簇的位置。

如下图:

上图中FAT表中第2簇位置数据位0x76FA,表示根目录的下一簇在0x76FA簇处。由于数据区是由第2簇开始的,因此计算0x76FA簇的位置为,第的位置计算下面有方法。

问题2:FAT表表示的最大空间和MBR/DBR中的最大磁盘总数相等么?

答:以FAT32举例。4个字节表示一个簇,但是第一个扇区中只

2

簇的位置+(0x76FA-2)

*4k=0x0B61000+76F8000=0x8259000地址处。如上图。第2簇

能表示(512-8)/4=126个簇,之外的每个扇区可以表示128个簇。总共有0x1D72个扇区(DBR中可知)。即可以表示这么多簇:126+128*(0x1D72-1)=9862。每簇

4k,

9862*4k=3859448kB。即可以表示3859448kB的数据。数据区是从FDT开始的,FDT的地址为0x0B61000。那么FAT表所占扇区表示的最大地址空间是:0x0B61000+3859448kB-

1=0x0EC45F000-1=0xEC45EFFF。根据DBR中扇区总数可以计算出此盘的最大地址为:0xEC3FFFFF。所以FAT表表示的最大地址空间比扇区总数计算出来的最大地址多:0xEC45EFFF-0xEC3FFFFF=0x5F000=3120B=380kB。因此可以得知,FAT表的最末尾扇区并没有被完全使用,准确的说最末尾扇区只用到了前面的512-380=132个字节。

问题3:在不改变磁盘扇区总数的前提下,怎么减小此盘文件系统的大小,或者说超过某个地址空间以后不能写入新的数据。

答:类似于问题1。同样假如超过地址空间2GB之后的地址不写入新的数据。

我们知道,FAT表中每4个字节都代表了特别的含义。如上面的表格所示。我们可以将FAT表中某扇区之后的数据置成坏簇,那么这些置成坏簇的FAT表扇区,FAT文件系统将不会去刷新对应空间的数据。从而让FAT文件系统假象地认为对应的空间已经被占据了。

那么对应到此题,应该怎么解决呢?只要找到2GB地址对应到的FAT表扇区位置就可以了。从数据区起始地址到2GB地址处空余:0x0B61000+X=21474838B(2GB)

X=2135552000B=2085500kB。这个值就是FAT表要表示的最大地址空间了。那么:(126+128*(Y-1))*4kB=2085500kB得出,Y=4073.2578125。即要占FAT表的前4073.2578125扇区(包括FAT表的第一个扇区)。再准确点,FAT表的前4073个扇区被完全使用,第4074个扇区使用了0.2578125*512=132个字节。如果FAT文件系统使用FAT表第4074扇区第132个字节之后的扇区数据时,可以返回给文件系统坏簇的信息。也就意味着2GB之后的空间不能使用了。从而达到了超过某一地址空间后不能写入新的数据。 FAT2

FAT2的地址为FAT1地址+FAT所占的扇区数。从DBR中可以得知,FAT2起始地址为:0x404800+0x1D72*0x200=0x7B2C00

根目录FDT(根目录通常位于第2簇)

FDT的地址紧挨着FAT2,FDT地址就为FAT2加上FAT扇区数。即0x7B2C00+0x1D72*0x200=0x0B61000

根目录占1簇,4kB,一个文件或目录占用32个字节。文件或目录的簇号就位于这32个字节之后。上面的网址有详细介绍,此处不再赘述。 数据区

其实根目录所在的位置已经是数据区了。这里的数据区是根目录后面的数据。在本例中,每簇为8个扇区,即4k。根目录所占1簇,即4k。那么此处的数据区地址为0x0B61000+4k=0x0B62000

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- sceh.cn 版权所有 湘ICP备2023017654号-4

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务