分享

FAT文件系统原理+FAT32文件系统学习+FAT32 FAT区__FAT表解析

 sdxy 2019-10-29

作者:zty,来源:www.,发布时间:13/05/14

一、硬盘的物理结构:

FAT文件系统原理(一)

硬盘存储数据是根据电、磁转换原理实现的。硬盘由一个或几个表面镀有磁性物质的金属或玻璃等物质盘片以及盘片两面所安装的磁头和相应的控制电路组成(图1),其中盘片和磁头密封在无尘的金属壳中。

硬盘工作时,盘片以设计转速高速旋转,设置在盘片表面的磁头则在电路控制下径向移动到指定位置然后将数据存储或读取出来。当系统向硬盘写入数据时,磁头中“写数据”电流产生磁场使盘片表面磁性物质状态发生改变,并在写电流磁场消失后仍能保持,这样数据就存储下来了;当系统从硬盘中读数据时,磁头经过盘片指定区域,盘片表面磁场使磁头产生感应电流或线圈阻抗产生变化,经相关电路处理后还原成数据。因此只要能将盘片表面处理得更平滑、磁头设计得更精密以及尽量提高盘片旋转速度,就能造出容量更大、读写数据速度更快的硬盘。这是因为盘片表面处理越平、转速越快就能越使磁头离盘片表面越近,提高读、写灵敏度和速度;磁头设计越小越精密就能使磁头在盘片上占用空间越小,使磁头在一张盘片上建立更多的磁道以存储更多的数据。

二、硬盘的逻辑结构。

硬盘由很多盘片(platter)组成,每个盘片的每个面都有一个读写磁头。如果有N个盘片。就有2N个面,对应2N个磁头(Heads),从0、1、2开始编号。每个盘片被划分成若干个同心圆磁道(逻辑上的,是不可见的。)每个盘片的划分规则通常是一样的。这样每个盘片的半径均为固定值R的同心圆再逻辑上形成了一个以电机主轴为轴的柱面(Cylinders),从外至里编号为0、1、2……每个盘片上的每个磁道又被划分为几十个扇区(Sector),通常的容量是512byte,并按照一定规则编号为1、2、3……形成Cylinders×Heads×Sector个扇区。这三个参数即是硬盘的物理参数。

三、磁盘引导原理。

3.1 MBR(master boot record)扇区:

计算机在按下power键以后,开始执行主板bios程序。进行完一系列检测和配置以后。开始按bios中设定的系统引导顺序引导系统。假定现在是硬盘。Bios执行完自己的程序后如何把执行权交给硬盘呢。交给硬盘后又执行存储在哪里的程序呢。其实,称为mbr的一段代码起着举足轻重的作用。MBR(master boot record),即主引导记录,有时也称主引导扇区。位于整个硬盘的0柱面0磁头1扇区(可以看作是硬盘的第一个扇区),bios在执行自己固有的程序以后就会jump到mbr中的第一条指令。将系统的控制权交由mbr来执行。在总共512byte的主引导记录中,MBR的引导程序占了其中的前446个字节(偏移0H~偏移1BDH),随后的64个字节(偏移1BEH~偏移1FDH)为DPT(Disk PartitionTable,硬盘分区表),最后的两个字节“55 AA”(偏移1FEH~偏移1FFH)是分区有效结束标志。

MBR不随操作系统的不同而不同,意即不同的操作系统可能会存在相同的MBR,即使不同,MBR也不会夹带操作系统的性质。具有公共引导的特性。

下面是用winhex查看的一块希捷120GB硬盘的mbr。

FAT文件系统原理(一)

你的硬盘的MBR引导代码可能并非这样。不过即使不同,所执行的功能大体是一样的。这是wowocock关于磁盘mbr的反编译,已加了详细的注释,感兴趣可以细细研究一下。

操作系统为了便于用户对磁盘的管理,加入了磁盘分区的概念。即将一块磁盘逻辑划分为几块。磁盘分区数目的多少只受限于C~Z的英文字母的数目,在上图DPT共64个字节中如何表示多个分区的属性呢?microsoft通过链接的方法解决了这个问题。在DPT共64个字节中,以16个字节为分区表项单位描述一个分区的属性。也就是说,第一个分区表项描述一个分区的属性,一般为基本分区。第二个分区表项描述除基本分区外的其余空间,一般而言,就是我们所说的扩展分区。这部分的大体说明见表1。

表1  图2分区表第一字段
字节位移字段长度字段名和定义
0x01BEBYTE0x80引导指示符(Boot Indicator):指明该分区是否是活动分区。
0x01BFBYTE0x01开始磁头(Starting Head)
0x01C06位0x01开始扇区(Starting Sector) 只用了0~5位。后面的两位(第6位和第7位)被开始柱面字段所使用
0x01C110位0x00开始柱面(Starting Cylinder):除了开始扇区字段的最后两位外,还使用了1位来组成该柱面值。开始柱面是一个10位数,最大值为1023
0x01C2BYTE0x07系统ID(System ID) 定义了分区的类型,详细定义,请参阅图4
0x01C3BYTE0xFE结束磁头(Ending Head)
0x01C46位0xFF结束扇区(Ending Sector):只使用了0~5位。最后两位(第6、7位)被结束柱面字段所使用
0x01C510位0x7B结束柱面(Ending Cylinder) 除了结束扇区字段最后的两位外,还使用了1位,以组成该柱面值。结束柱面是一个10位的数,最大值为1023
0x01C6DWORD0x0000003F相对扇区数(Relative Sectors) 从该磁盘的开始到该分区的开始的位移量,以扇区来计算
0x01CADWORD0x00DAA83D总扇区数(Total Sectors) 该分区中的扇区总数

注:上表中的超过1字节的数据都以实际数据显示,就是按高位到地位的方式显示。存储时是按低位到高位存储的。两者表现不同,请仔细看清楚。以后出现的表,图均同。

也可以在winhex中看到这些参数的意义:

FAT文件系统原理(一)

说明:每个分区表项占用16个字节,假定偏移地址从0开始。如图3的分区表项3。分区表项4同分区表项3。

1、0H偏移为活动分区是否标志,只能选00H和80H。80H为活动,00H为非活动。其余值对microsoft而言为非法值。

2、重新说明一下(这个非常重要):大于1个字节的数被以低字节在前的存储格式格式(little endian format)或称反字节顺序保存下来。低字节在前的格式是一种保存数的方法,这样,最低位的字节最先出现在十六进制数符号中。例如,相对扇区数字段的值0x3F000000的低字节在前表示为0x0000003F。这个低字节在前的格式数的十进制数为63。

3、系统在分区时,各分区都不允许跨柱面,即均以柱面为单位,这就是通常所说的分区粒度。有时候我们分区是输入分区的大小为7000M,分出来却是6997M,就是这个原因。 偏移2H和偏移6H的扇区和柱面参数中,扇区占6位(bit),柱面占10位(bit),以偏移6H为例,其低6位用作扇区数的二进制表示。其高两位做柱面数10位中的高两位,偏移7H组成的8位做柱面数10位中的低8位。由此可知,实际上用这种方式表示的分区容量是有限的,柱面和磁头从0开始编号,扇区从1开始编号,所以最多只能表示1024个柱面×63个扇区×256个磁头×512byte=8455716864byte。即通常的8.4GB(实际上应该是7.8GB左右)限制。实际上磁头数通常只用到255个(由汇编语言的寻址寄存器决定),即使把这3个字节按线性寻址,依然力不从心。 在后来的操作系统中,超过8.4GB的分区其实已经不通过C/H/S的方式寻址了。而是通过偏移CH~偏移FH共4个字节32位线性扇区地址来表示分区所占用的扇区总数。可知通过4个字节可以表示2^32个扇区,即2TB=2048GB,目前对于大多数计算机而言,这已经是个天文数字了。在未超过8.4GB的分区上,C/H/S的表示方法和线性扇区的表示方法所表示的分区大小是一致的。也就是说,两种表示方法是协调的。即使不协调,也以线性寻址为准。(可能在某些系统中会提示出错)。超过8.4GB的分区结束C/H/S一般填充为FEH FFH FFH。即C/H/S所能表示的最大值。有时候也会用柱面对1024的模来填充。不过这几个字节是什么其实都无关紧要了。

虽然现在的系统均采用线性寻址的方式来处理分区的大小。但不可跨柱面的原则依然没变。本分区的扇区总数加上与前一分区之间的保留扇区数目依然必须是柱面容量的整数倍。(保留扇区中的第一个扇区就是存放分区表的MBR或虚拟MBR的扇区,分区的扇区总数在线性表示方式上是不计入保留扇区的。如果是第一个分区,保留扇区是本分区前的所有扇区。

附:分区表类型标志如图4

FAT文件系统原理(一)

3.2 扩展分区:

扩展分区中的每个逻辑驱动器都存在一个类似于MBR的扩展引导记录( Extended Boot Record, EBR),也有人称之为虚拟mbr或扩展mbr,意思是一样的。扩展引导记录包括一个扩展分区表和该扇区的标签。扩展引导记录将记录只包含扩展分区中每个逻辑驱动器的第一个柱面的第一面的信息。一个逻辑驱动器中的引导扇区一般位于相对扇区32或63。但是,如果磁盘上没有扩展分区,那么就不会有扩展引导记录和逻辑驱动器。第一个逻辑驱动器的扩展分区表中的第一项指向它自身的引导扇区。第二项指向下一个逻辑驱动器的EBR。如果不存在进一步的逻辑驱动器,第二项就不会使用,而且被记录成一系列零。如果有附加的逻辑驱动器,那么第二个逻辑驱动器的扩展分区表的第一项会指向它本身的引导扇区。第二个逻辑驱动器的扩展分区表的第二项指向下一个逻辑驱动器的EBR。扩展分区表的第三项和第四项永远都不会被使用。

通过一幅4分区的磁盘结构图可以看到磁盘的大致组织形式。如图5:

FAT文件系统原理(一)

关于扩展分区,如图6所示,扩展分区中逻辑驱动器的扩展引导记录是一个连接表。该图显示了一个扩展分区上的三个逻辑驱动器,说明了前面的逻辑驱动器和最后一个逻辑驱动器之间在扩展分区表中的差异。

FAT文件系统原理(一)

除了扩展分区上最后一个逻辑驱动器外,表2中所描述的扩展分区表的格式在每个逻辑驱动器中都是重复的:第一个项标识了逻辑驱动器本身的引导扇区,第二个项标识了下一个逻辑驱动器的EBR。最后一个逻辑驱动器的扩展分区表只会列出它本身的分区项。最后一个扩展分区表的第二个项到第四个项被使用。

扩展分区表项中的相对扇区数字段所显示的是从扩展分区开始到逻辑驱动器中第一个扇区的位移的字节数。总扇区数字段中的数是指组成该逻辑驱动器的扇区数目。总扇区数字段的值等于从扩展分区表项所定义的引导扇区到逻辑驱动器末尾的扇区数。

四、FAT分区原理。

先来一幅结构图:

现在我们着重研究FAT格式分区内数据是如何存储的。FAT分区格式是MICROSOFT最早支持的分区格式,依据FAT表中每个簇链的所占位数(有关概念,后面会讲到)分为fat12、fat16、fat32三种格式“变种”,但其基本存储方式是相似的。

仔细研究图7中的fat16和fat32分区的组成结构。下面依次解释DBR、FAT1、FAT2、根目录、数据区、剩余扇区的概念。提到的地址如无特别提示均为分区内部偏移。

4.1 关于DBR.

DBR区(DOS BOOT RECORD)即操作系统引导记录区的意思,通常占用分区的第0扇区共512个字节(特殊情况也要占用其它保留扇区,我们先说第0扇)。在这512个字节中,其实又是由跳转指令,厂商标志和操作系统版本号,BPB(BIOS Parameter Block),扩展BPB,os引导程序,结束标志几部分组成。 以用的最多的FAT32为例说明分区DBR各字节的含义。见图8。

图8的对应解释见表3

表3 FAT32分区上DBR中各部分的位置划分
字节位移字段长度字段名对应图8颜色
0x003个字节跳转指令
0x038个字节厂商标志和os版本号
0x0B53个字节BPB
0x4026个字节扩展BPB
0x5A420个字节引导程序代码
0x01FE2个字节有效结束标志

图9给出了winhex对图8 DBR的相关参数解释:

根据上边图例,我们来讨论DBR各字节的参数意义。

MBR将CPU执行转移给引导扇区,因此,引导扇区的前三个字节必须是合法的可执行的基于x86的CPU指令。这通常是一条跳转指令,该指令负责跳过接下来的几个不可执行的字节(BPB和扩展BPB),跳到操作系统引导代码部分。

跳转指令之后是8字节长的OEM ID,它是一个字符串,OEM ID标识了格式化该分区的操作系统的名称和版本号。为了保留与MS-DOS的兼容性,通常Windows 2000格式化该盘是在FAT16和FAT32磁盘上的该字段中记录了“MSDOS 5.0”,在NTFS磁盘上(关于ntfs,另述),Windows 2000记录的是“NTFS”。通常在被Windows 95格式化的磁盘上OEM ID字段出现“MSWIN4.0”,在被Windows 95 OSR2和Windows 98格式化的磁盘上OEM ID字段出现“MSWIN4.1”。

接下来的从偏移0x0B开始的是一段描述能够使可执行引导代码找到相关参数的信息。通常称之为BPB(BIOS Parameter Block),BPB一般开始于相同的位移量,因此,标准的参数都处于一个已知的位置。磁盘容量和几何结构变量都被封在BPB之中。由于引导扇区的第一部分是一个x86跳转指令。因此,将来通过在BPB末端附加新的信息,可以对BPB进行扩展。只需要对该跳转指令作一个小的调整就可以适应BPB的变化。图9已经列出了项目的名称和取值,为了系统的研究,针对图8,将FAT32分区格式的BPB含义和扩展BPB含义释义为表格,见表4和表5。

表4 FAT32分区的BPB字段
字节位移字段长度(字节)图8对应取值名称和定义
0x0B20x0200扇区字节数(Bytes Per Sector) 硬件扇区的大小。本字段合法的十进制值有512、1024、2048和4096。对大多数磁盘来说,本字段的值为512
0x0D10x08每簇扇区数(Sectors Per Cluster),一簇中的扇区数。由于FAT32文件系统只能跟踪有限个簇(最多为4 294 967 296个),因此,通过增加每簇扇区数,可以使FAT32文件系统支持最大分区数。一个分区缺省的簇大小取决于该分区的大小。本字段的合法十进制值有1、2、4、8、16、32、64和128。Windows 2000的FAT32实现只能创建最大为32GB的分区。但是,Windows 2000能够访问由其他操作系统(Windows 95、OSR2及其以后的版本)所创建的更大的分区
0x0e20x0020保留扇区数(Reserved Sector) 第一个FAT开始之前的扇区数,包括引导扇区。本字段的十进制值一般为32
0x1010x02FAT数(Number of FAT) 该分区上FAT的副本数。本字段的值一般为2
0x1120x0000根目录项数(Root Entries)只有FAT12/FAT16使用此字段。对FAT32分区而言,本字段必须设置为 0
0x1320x0000小扇区数(Small Sector)(只有FAT12/FAT16使用此字段)对FAT32分区而言,本字段必须设置为0
0x1510xF8媒体描述符( Media Descriptor)提供有关媒体被使用的信息。值0xF8表示硬盘,0xF0表示高密度的3.5寸软盘。媒体描述符要用于MS-DOS FAT16磁盘,在Windows 2000中未被使用
0x1620x0000每FAT扇区数(Sectors Per FAT)只被FAT12/FAT16所使用,对FAT32分区而言,本字段必须设置为0
0x1820x003F每道扇区数(Sectors Per Track) 包含使用INT13h的磁盘的“每道扇区数”几何结构值。该分区被多个磁头的柱面分成了多个磁道
0x1A20x00FF磁头数(Number of Head) 本字段包含使用INT 13h的磁盘的“磁头数”几何结构值。例如,在一张1.44MB 3.5英寸的软盘上,本字段的值为 2
0x1C40x0000003F隐藏扇区数(Hidden Sector) 该分区上引导扇区之前的扇区数。在引导序列计算到根目录的数据区的绝对位移的过程中使用了该值。本字段一般只对那些在中断13h上可见的媒体有意义。在没有分区的媒体上它必须总是为0
0x2040x007D043F总扇区数(Large Sector) 本字段包含FAT32分区中总的扇区数
0x2440x00001F32每FAT扇区数(Sectors Per FAT)(只被FAT32使用)该分区每个FAT所占的扇区数。计算机利用这个数和 FAT数以及隐藏扇区数(本表中所描述的)来决定根目录从哪里开始。该计算机还可以从目录中的项数决定该分区的用户数据区从哪里开始
0x2820x00

扩展标志(Extended Flag)(只被FAT32使用)该两个字节结构中各位的值为:

位0-3:活动 FAT数(从0开始计数,而不是1).

只有在不使用镜像时才有效

位4-6:保留

位7:0值意味着在运行时FAT被映射到所有的FAT

1值表示只有一个FAT是活动的

位8-15:保留

0x2A20x0000文件系统版本(File ystem Version)只供FAT32使用,高字节是主要的修订号,而低字节是次要的修订号。本字段支持将来对该FAT32媒体类型进行扩展。如果本字段非零,以前的Windows版本将不支持这样的分区
0x2C40x00000002根目录簇号(Root Cluster Number)(只供FAT32使用) 根目录第一簇的簇号。本字段的值一般为2,但不总是如此
0x3020x0001文件系统信息扇区号(File System Information SectorNumber)(只供FAT32使用) FAT32分区的保留区中的文件系统信息(File System Information, FSINFO)结构的扇区号。其值一般为1。在备份引导扇区(Backup Boot Sector)中保留了该FSINFO结构的一个副本,但是这个副本不保持更新
0x3420x0006备份引导扇区(只供FAT32使用) 为一个非零值,这个非零值表示该分区保存引导扇区的副本的保留区中的扇区号。本字段的值一般为6,建议不要使用其他值
0x361212个字节均为0x00保留(只供FAT32使用)供以后扩充使用的保留空间。本字段的值总为0
表5 FAT32分区的扩展BPB字段
字节位移字段长度(字节)图8对应取值字段名称和定义
0x4010x80物理驱动器号( Physical Drive Number) 与BIOS物理驱动器号有关。软盘驱动器被标识为0x00,物理硬盘被标识为0x80,而与物理磁盘驱动器无关。一般地,在发出一个INT13h BIOS调用之前设置该值,具体指定所访问的设备。只有当该设备是一个引导设备时,这个值才有意义
0x4110x00保留(Reserved) FAT32分区总是将本字段的值设置为0
0x4210x29扩展引导标签(Extended Boot Signature) 本字段必须要有能被Windows 2000所识别的值0x28或0x29
0x4340x33391CFE分区序号(Volume Serial Number) 在格式化磁盘时所产生的一个随机序号,它有助于区分磁盘
0x4711"NO NAME"卷标(Volume Label) 本字段只能使用一次,它被用来保存卷标号。现在,卷标被作为一个特殊文件保存在根目录中
0x528"FAT32"系统ID(System ID) FAT32文件系统中一般取为"FAT32"

DBR的偏移0x5A开始的数据为操作系统引导代码。这是由偏移0x00开始的跳转指令所指向的。在图8所列出的偏移0x00~0x02的跳转指令“EB 58 90”清楚地指明了OS引导代码的偏移位置。jump 58H加上跳转指令所需的位移量,即开始于0x5A。此段指令在不同的操作系统上和不同的引导方式上,其内容也是不同的。大多数的资料上都说win98,构建于fat基本分区上的win2000,winxp所使用的DBR只占用基本分区的第0扇区。他们提到,对于fat32,一般的32个基本分区保留扇区只有第0扇区是有用的。实际上,以FAT32构建的操作系统如果是win98,系统会使用基本分区的第0扇区和第2扇区存储os引导代码;以FAT32构建的操作系统如果是win2000或winxp,系统会使用基本分区的第0扇区和第0xC扇区(win2000或winxp,其第0xC的位置由第0扇区的0xAB偏移指出)存储os引导代码。所以,在fat32分区格式上,如果DBR一扇区的内容正确而缺少第2扇区(win98系统)或第0xC扇区(win2000或winxp系统),系统也是无法启动的。如果自己手动设置NTLDR双系统,必须知道这一点。

DBR扇区的最后两个字节一般存储值为0x55AA的DBR有效标志,对于其他的取值,系统将不会执行DBR相关指令。上面提到的其他几个参与os引导的扇区也需以0x55AA为合法结束标志。

FAT16 DBR:

FAT32中DBR的含义大致如此,对于FAT12和FAT16其基本意义类似,只是相关偏移量和参数意义有小的差异,FAT格式的区别和来因,以后会说到,此处不在多说FAT12与FAT16。我将FAT16的扇区参数意义列表。感兴趣的朋友自己研究一下,和FAT32大同小异的。

表6 一个FAT16分区上的引导扇区段
字节位移字段长度(字节)字段名称
0x003跳转指令(Jump Instruction)
0x038OEM ID
0x0B25BPB
0x2426扩展BPB
0x3E448引导程序代码(Bootstrap Code)
0x01FE4扇区结束标识符(0x55AA)
表7 FAT16分区的BPB字段
字节位移字段长度(字节)例值名称和定义
0x0B20x0200扇区字节数(Bytes Per Sector) 硬件扇区的大小。本字段合法的十进制值有512、1024、2048和4096。对大多数磁盘来说,本字段的值为512
0x0D10x40每簇扇区数(Sectors Per Cluster) 一个簇中的扇区数。由于FAT16文件系统只能跟踪有限个簇(最多为65536个)。因此,通过增加每簇的扇区数可以支持最大分区数。分区的缺省的簇的大小取决于该 分区的大小。本字段合法的十进制值有 1、2、4、8、16、32、64和128。导致簇大于32KB(每扇区字节数*每簇扇区数)的值会引起磁盘错误和软件错误
0x0e20x0001保留扇区数(Reserved Sector) 第一个FAT开始之前的扇区数,包括引导扇区。本字段的十进制值一般为1
0x1010x02FAT数(Number of FAT)该分区上FAT的副本数。本字段的值一般为2
0x1120x0200根目录项数(Root Entries) 能够保存在该分区的根目录文件夹中的32个字节长的文件和文件夹名称项的总数。在一个典型的硬盘上,本字段的值为512。其中一个项常常被用作卷标号(Volume Label),长名称的文件和文件夹每个文件使用多个项。文件和文件夹项的最大数一般为511,但是如果使用的长文件名,往往都达不到这个数
0x1320x0000小扇区数(Small Sector) 该分区上的扇区数,表示为16位(<65536)。对大于65536个扇区的分区来说,本字段的值为0,而使用大扇区数来取代它
0x1510xF8媒体描述符( Media Descriptor)提供有关媒体被使用的信息。值0xF8表示硬盘,0xF0表示高密度的3.5寸软盘。媒体描述符要用于MS-DOS FAT16磁盘,在Windows 2000中未被使用
0x1620x00FC每FAT扇区数(Sectors Per FAT) 该分区上每个FAT所占用的扇区数。计算机利用这个数和FAT数以及隐藏扇区数来决定根目录在哪里开始。计算机还可以根据根目录中的项数(512)决定该 分区的用户数据区从哪里开始
0x1820x003F每道扇区数(Sectors Per Trark)
0x1A20x0040磁头数(Number of head)
0x1C40x0000003F隐藏扇区数(Hidden Sector) 该分区上引导扇区之前的扇区数。在引导序列计算到根目录和数据区的绝对位移的过程中使用了该值
0x2040x003EF001大扇区数(Large Sector) 如果小扇区数字段的值为0,本字段就包含该FAT16分区中的总扇区数。如果小扇区数字段的值不为0,那么本字段的值为0
表8 FAT16分区的扩展BPB字段
字节位移字段长度(字节)图8对应取值字段名称和定义
0x2410x80物理驱动器号( Physical Drive Number) 与BIOS物理驱动器号有关。软盘驱动器被标识为0x00,物理硬盘被标识为0x80,而与物理磁盘驱动器无关。一般地,在发出一个INT13h BIOS调用之前设置该值,具体指定所访问的设备。只有当该设备是一个引导设备时,这个值才有意义
0x2510x00保留(Reserved) FAT16分区一般将本字段的值设置为0
0x2610x29扩展引导标签(Extended Boot Signature) 本字段必须要有能被Windows 2000所识别的值0x28或0x29
0x2720x52368BA8卷序号(Volume Serial Number) 在格式化磁盘时所产生的一个随机序号,它有助于区分磁盘
0x2B11"NO NAME"卷标(Volume Label) 本字段只能使用一次,它被用来保存卷标号。现在,卷标被作为一个特殊文件保存在根目录中
0x368"FAT16"文件系统类型(File System Type) 根据该磁盘格式,该字段的值可以为FAT、FAT12或FAT16

4.2  关于保留扇区

在上述FAT文件系统DBR的偏移0x0E处,用2个字节存储保留扇区的数目。所谓保留扇区(有时候会叫系统扇区,隐藏扇区),是指从分区DBR扇区开始的仅为系统所有的扇区,包括DBR扇区。在FAT16文件系统中,保留扇区的数据通常设置为1,即仅仅DBR扇区。而在FAT32中,保留扇区的数据通常取为32,有时候用Partition Magic分过的FAT32分区会设置36个保留扇区,有的工具可能会设置63个保留扇区。

FAT32中的保留扇区除了磁盘总第0扇区用作DBR,总第2扇区(win98系统)或总第0xC扇区(win2000,winxp)用作OS引导代码扩展部分外,其余扇区都不参与操作系统管理与磁盘数据管理,通常情况下是没作用的。操作系统之所以在FAT32中设置保留扇区,是为了对DBR作备份或留待以后升级时用。FAT32中,DBR偏移0x34占2字节的数据指明了DBR备份扇区所在,一般为0x06,即第6扇区。当FAT32分区DBR扇区被破坏导致分区无法访问时。可以用第6扇区的原备份替换第0扇区来找回数据。

4.3 FAT表和数据的存储原则。

FAT表(File Allocation Table 文件分配表),是Microsoft在FAT文件系统中用于磁盘数据(文件)索引和定位引进的一种链式结构。假如把磁盘比作一本书,FAT表可以认为相当于书中的目录,而文件就是各个章节的内容。但FAT表的表示方法却与目录有很大的不同。

在FAT文件系统中,文件的存储依照FAT表制定的簇链式数据结构来进行。同时,FAT文件系统将组织数据时使用的目录也抽象为文件,以简化对数据的管理。

★存储过程假想:

我们模拟对一个分区存储数据的过程来说明FAT文件系统中数据的存储原则。

假定现在有一个空的完全没有存放数据的磁盘,大小为100KB,我们将其想象为线形的空间地址。为了存储管理上的便利,我们人为的将这100KB的空间均分成100份,每份1KB。我们来依次存储这样几个文件:A.TXT(大小10KB),B.TXT(大小53.6KB),C.TXT(大小20.5KB)。

最起码能够想到,我们可以顺序的在这100KB空间中存放这3个文件。同时不要忘了,我们还要记下他们的大小和开始的位置,这样下次要用时才能找的到,这就像是目录。为了便于查找,我们假定用第1K的空间来存储他们的特征(属性)。还有,我们设计的存储单位是1KB,所以,A.TXT我们需要10个存储单位(为了说明方便,我们把存储单位叫做“簇”吧。也能少打点字,呵呵。),B.TXT需要54个簇,C.TXT需要21个簇。可能有人会说B.TXT和C.TXT不是各自浪费了不到1簇的空间吗?干嘛不让他们紧挨着,不是省地方吗?我的回答是,如果按照这样的方式存储,目录中原本只需要记下簇号,现在还需要记下簇内的偏移,这样会增加目录的存储量,而且存取没有了规则,读取也不太方便,是得不偿失的。

根据上面所说的思想,我们设计了这样的图4.3.1所示的存储方式。

我们再考虑如何来写这三个文件的目录。对于每个文件而言,一定要记录的有:文件名,开始簇,大小,创建日期、时间,修改日期、时间,文件的读写属性等。这里大小能不能用结束簇来计算呢?一定不能,因为文件的大小不一定就是整数个簇的大小,否则的话像B.TXT的内容就是54KB的内容了,少了固然不行,可多了也是不行的。那么我们怎么记录呢?可以想象一下。为了管理上的方便,我们用数据库的管理方式来管理我们的目录。于是我把1KB再分成10份,假定开始簇号为0,定义每份100B的各个位置的代表含义如图4.3.2

这样设计的结构绝对可以对文件进行正确的读写了。接着让我们设计的文件系统工作吧。先改动个文件,比如A.TXT,增加点内容吧!咦?增加后往哪里放呀,虽然存储块的后面有很多空间,但紧随其后B.TXT的数据还顶着呢?要是把A.TXT移到后边太浪费处理资源,而且也不一定解决问题。这个问题看来暂时解决不了。

那我们换个操作,把B.txt删了,b.txt的空间随之释放。这时候空间如图4.3.3,目录如图4.3.4

这个操作看来还可以,我们接着做,在存入一个文件D.txt(大小为60.3KB),总共100簇的空间只用了31簇,还有68簇剩余,按说能放下。可是?往那里放呢?没有61个连续的空间了,目录行没办法写了,看来无连续块存储暂时也不行。

你一定能够想到我们可以在连续空间不够或增加文件长度的时候转移影响我们操作的其他文件,从而腾出空间来,但我要问你,那不是成天啥也不要干了,就是倒腾东西了吗?

看来我们设计的文件系统有致命的漏洞,怎么解决呢?。。。。。。。。。。

其实可以这样解决:

首先我们允许文件的不连续存储。目录中依然只记录开始簇和文件的大小。那么我们怎么记录文件占用那些簇呢,以文件映射簇不太方便,因为文件名是不固定的。我们换个思想,可以用簇来映射文件,在整个存储空间的前部留下几簇来记录数据区中数据与簇号的关系。对于上例因为总空间也不大,所以用前部的1Kb的空间来记录这种对应,假设3个文件都存储,空间分配如图4.3.5,同时修改一下目录,如图4.3.6

第一簇用来记录数据区中每一簇的被占用情况,暂时称其为文件分配表。结合文件分配表和文件目录就可以达到完全的文件读取了。我们想到,把文件分配表做成一个数据表,以图4.3.7的形式记录簇与数据的对应。

    用图4.3.7的组织方式是完全可以实现对文件占有簇的记录的。但还不够效率。比如文件名在文件分配表中记录太多,浪费空间,而实际上在目录中已经记录了文件的开始簇了。所以可以改良一下,用链的方式来存放占有簇的关系,变成图4.3.8的组织方式。

参照图4.3.8来理解一下文件分配表的意义。如文件a.txt我们根据目录项中指定的a.txt的首簇为2,然后找到文件分配表的第2簇记录,上面登记的是3,我们就能确定下一簇是3。找到文件分配表的第3簇记录,上面登记的是4,我们就能确定下一簇是4......直到指到第11簇,发现下一个指向是FF,就是结束。文件便丝毫无误读取完毕。

我们再看上面提到的第三种情况,就是将b.txt删除以后,存入一个大小为60.3KB的d.txt。利用簇链可以很容易的实现。实现后的磁盘如图4.3.9 4.3.10 ; 4.3.11

★FAT16存储原理:

当把一部分磁盘空间格式化为fat文件系统时,fat文件系统就将这个分区当成整块可分配的区域进行规划,以便于数据的存储。一般来讲,其划分形式如图7所示。我们把FAT16部分提取出来,详细描述一下:

FAT16是Microsoft较早推出的文件系统,具有高度兼容性,目前仍然广泛应用于个人电脑尤其是移动存储设备中,FAT16简单来讲由图4.3.11所示的6部分组成(主要是前5部分)。引导扇区(DBR)我们已经说过,FAT16在DBR之后没有留有任何保留扇区,其后紧随的便是FAT表。FAT表是FAT16用来记录磁盘数据区簇链结构的。像前面我们说过的例子一样,FAT将磁盘空间按一定数目的扇区为单位进行划分,这样的单位称为簇。通常情况下,每扇区512字节的原则是不变的。簇的大小一般是2n(n为整数)个扇区的大小,像512B,1K,2K,4K,8K,16K,32K,64K。实际中通常不超过32K。 之所以簇为单位而不以扇区为单位进行磁盘的分配,是因为当分区容量较大时,采用大小为512b的扇区管理会增加fat表的项数,对大文件存取增加消耗,文件系统效率不高。分区的大小和簇的取值是有关系的,见表9。

图4.3.11 Fat16的组织形式
引导扇区FAT1FAT2(重复的)根文件夹其他文件夹及所有文件剩余扇区
1扇区实际情况取大小同FAT132个扇区开始簇编号(从2开始)不足一簇
表9 FAT16分区大小与对因簇大小
分区空间大小每个簇的扇区簇空间大小
0MB-32MB1512个字节
33MB-64MB21k
65MB-128MB42k
129MB-225MB84k
256MB-511MB168k
512MB-1023MB3216k
1024MB-2047MB6432k
2048MB-4095MB12864k

注意:少于32680个扇区的分区中,簇空间大小可最多达到每个簇8个扇区。不管用户是使用磁盘管理器来格式化分区,还是使用命令提示行键入format命令格式化,格式化程序都创建一个12位的FAT。少于16MB的分区,系统通常会将其格式化成12位的FAT,FAT12是FAT的初始实现形式,是针对小型介质的。FAT12文件分配表要比FAT16和FAT32的文件分配表小,因为它对每个条目使用的空间较少。这就给数据留下较多的空间。所有用FAT12格式化的5.25英寸软盘以及1.44MB的3.5英寸软盘都是由FAT12格式化的。除了FAT表中记录每簇链结的二进制位数与FAT16不同外,其余原理与FAT16均相同,不再单独解释。。。

格式化FAT16分区时,格式化程序根据分区的大小确定簇的大小,然后根据保留扇区的数目、根目录的扇区数目、数据区可分的簇数与FAT表本身所占空间来确定FAT表所需的扇区数目,然后将计算后的结果写入DBR的相关位置。

FAT16 DBR参数的偏移0x11处记录了根目录所占扇区的数目。偏移0x16记录了FAT表所占扇区的数据。偏移0x10记录了FAT表的副本数目。系统在得到这几项参数以后,就可以确定数据区的开始扇区偏移了。

FAT16文件系统从根目录所占的32个扇区之后的第一个扇区开始以簇为单位进行数据的处理,这之前仍以扇区为单位。对于根目录之后的第一个簇,系统并不编号为第0簇或第1簇 (可能是留作关键字的原因吧),而是编号为第2簇,也就是说数据区顺序上的第1个簇也是编号上的第2簇。

FAT文件系统之所以有12,16,32不同的版本之分,其根本在于FAT表用来记录任意一簇链接的二进制位数。以FAT16为例,每一簇在FAT表中占据2字节(二进制16位)。所以,FAT16最大可以表示的簇号为0xFFFF(十进制的65535),以32K为簇的大小的话,FAT32可以管理的最大磁盘空间为:32KB×65535=2048MB,这就是为什么FAT16不支持超过2GB分区的原因。

FAT表实际上是一个数据表,以2个字节为单位,我们暂将这个单位称为FAT记录项,通常情况其第1、2个记录项(前4个字节)用作介质描述。从第三个记录项开始记录除根目录外的其他文件及文件夹的簇链情况。根据簇的表现情况FAT用相应的取值来描述,见表10

表10 FAT16记录项的取值含义(16进制)
FAT16记录项的取值对应簇的表现情况
0000未分配的簇
0002~FFEF已分配的簇
FFF0~FFF6系统保留
FFF7坏簇
FFF8~FFFF文件结束簇

看一幅在winhex所截FAT16的文件分配表,图10:

如图,FAT表以"F8 FF FF FF" 开头,此2字节为介质描述单元,并不参与FAT表簇链关系。小红字标出的是FAT扇区每2字节对应的簇号。

相对偏移0x4~0x5偏移为第2簇(顺序上第1簇),此处为FF,表示存储在第2簇上的文件(目录)是个小文件,只占用1个簇便结束了。

第3簇中存放的数据是0x0005,这是一个文件或文件夹的首簇。其内容为第5簇,就是说接下来的簇位于第5簇——〉 FAT表指引我们到达FAT表的第5簇指向,上面写的数据是“FF FF”,意即此文件已至尾簇。

第4簇中存放的数据是0x0006,这又是一个文件或文件夹的首簇。其内容为第6簇,就是说接下来的簇位于第6簇——〉FAT表指引我们到达FAT表的第6簇指向,上面写的数据是0x0007,就是说接下来的簇位于第7簇——〉FAT表指引我们到达FAT表的第7簇指向……直到根据FAT链读取到扇区相对偏移0x1A~0x1B,也就是第13簇,上面写的数据是0x000E,也就是指向第14簇——〉14簇的内容为"FF FF",意即此文件已至尾簇。

后面的FAT表数据与上面的道理相同。不再分析。

FAT表记录了磁盘数据文件的存储链表,对于数据的读取而言是极其重要的,以至于Microsoft为其开发的FAT文件系统中的FAT表创建了一份备份,就是我们看到的FAT2。FAT2与FAT1的内容通常是即时同步的,也就是说如果通过正常的系统读写对FAT1做了更改,那么FAT2也同样被更新。如果从这个角度来看,系统的这个功能在数据恢复时是个天灾。

FAT文件系统的目录结构其实是一颗有向的从根到叶的树,这里提到的有向是指对于FAT分区内的任一文件(包括文件夹),均需从根目录寻址来找到。可以这样认为:目录存储结构的入口就是根目录。

FAT文件系统根据根目录来寻址其他文件(包括文件夹),故而根目录的位置必须在磁盘存取数据之前得以确定。FAT文件系统就是根据分区的相关DBR参数与DBR中存放的已经计算好的FAT表(2份)的大小来确定的。格式化以后,跟目录的大小和位置其实都已经确定下来了:位置紧随FAT2之后,大小通常为32个扇区。根目录之后便是数据区第2簇。

FAT文件系统的一个重要思想是把目录(文件夹)当作一个特殊的文件来处理,FAT32甚至将根目录当作文件处理(旁:NTFS将分区参数、安全权限等好多东西抽象为文件更是这个思想的升华),在FAT16中,虽然根目录地位并不等同于普通的文件或者说是目录,但其组织形式和普通的目录(文件夹)并没有不同。FAT分区中所有的文件夹(目录)文件,实际上可以看作是一个存放其他文件(文件夹)入口参数的数据表。所以目录的占用空间的大小并不等同于其下所有数据的大小,但也不等同于0。通常是占很小的空间的,可以看作目录文件是一个简单的二维表文件。其具体存储原理是:

不管目录文件所占空间为多少簇,一簇为多少字节。系统都会以32个字节为单位进行目录文件所占簇的分配。这32个字节以确定的偏移来定义本目录下的一个文件(或文件夹)的属性,实际上是一个简单的二维表。

这32个字节的各字节偏移定义如表11:

表11 FAT16目录项32个字节的表示定义
字节偏移(16进制)字节数定义
0x0~0x78文件名
0x8~0xA3扩展名
0xB1属性字节00000000(读写)
00000001(只读)
00000010(隐藏)
00000100(系统)
00001000(卷标)
00010000(子目录)
00100000(归档)
0xC~0x1510系统保留
0x16~0x172文件的最近修改时间
0x18~0x192文件的最近修改日期
0x1A~0x1B2表示文件的首簇号
0x1C~0x1F4表示文件的长度

对图10中的一些取值进行说明:

(1)、对于短文件名,系统将文件名分成两部分进行存储,即主文件名+扩展名。0x0~0x7字节记录文件的主文件名,0x8~0xA记录文件的扩展名,取文件名中的ASCII码值。不记录主文件名与扩展名之间的"."  主文件名不足8个字符以空白符(20H)填充,扩展名不足3个字符同样以空白符(20H)填充。0x0偏移处的取值若为00H,表明目录项为空;若为E5H,表明目录项曾被使用,但对应的文件或文件夹已被删除。(这也是误删除后恢复的理论依据)。文件名中的第一个字符若为“.”或“..”表示这个簇记录的是一个子目录的目录项。“.”代表当前目录;“..”代表上级目录(和我们在dos或windows中的使用意思是一样的,如果磁盘数据被破坏,就可以通过这两个目录项的具体参数推算磁盘的数据区的起始位置,猜测簇的大小等等,故而是比较重要的)

(2)、0xB的属性字段:可以看作系统将0xB的一个字节分成8位,用其中的一位代表某种属性的有或无。这样,一个字节中的8位每位取不同的值就能反映各个属性的不同取值了。如00000101就表示这是个文件,属性是只读、系统。

(3)、0xC~0x15在原FAT16的定义中是保留未用的。在高版本的WINDOWS系统中有时也用它来记录修改时间和最近访问时间。那样其字段的意义和FAT32的定义是相同的,见后边FAT32。

(4)、0x16~0x17中的时间=小时*2048+分钟*32+秒/2。得出的结果换算成16进制填入即可。也就是:0x16字节的0~4位是以2秒为单位的量值;0x16字节的5~7位和0x17字节的0~2位是分钟;0x17字节的3~7位是小时。

(5)、0x18~0x19中的日期=(年份-1980)*512+月份*32+日。得出的结果换算成16进制填入即可。也就是:0x18字节0~4位是日期数;0x18字节5~7位和0x19字节0位是月份;0x19字节的1~7位为年号,原定义中0~119分别代表1980~2099,目前高版本的Windows允许取0~127,即年号最大可以到2107年。

(6)、0x1A~0x1B存放文件或目录的表示文件的首簇号,系统根据掌握的首簇号在FAT表中找到入口,然后再跟踪簇链直至簇尾,同时用0x1C~0x1F处字节判定有效性。就可以完全无误的读取文件(目录)了。

(7)、普通子目录的寻址过程也是通过其父目录中的目录项来指定的,与数据文件(指非目录文件)不同的是目录项偏移0xB的第4位置1,而数据文件为0。

对于整个FAT分区而言,簇的分配并不完全总是分配干净的。如一个数据区为99个扇区的FAT系统,如果簇的大小设定为2扇区,就会有1个扇区无法分配给任何一个簇。这就是分区的剩余扇区,位于分区的末尾。有的系统用最后一个剩余扇区备份本分区的DBR,这也是一种好的备份方法。

早的FAT16系统并没有长文件名一说,Windows操作系统已经完全支持在FAT16上的长文件名了。FAT16的长文件名与FAT32长文件名的定义是相同的,关于长文件名,在FAT32部分再详细作解释

★FAT32存储原理:

FAT32是个非常有功劳的文件系统,Microsoft成功地设计并运用了它,直到今天NTFS铺天盖地袭来的时候,FAT32依然占据着Microsoft Windows文件系统中重要的地位。FAT32最早是出于FAT16不支持大分区、单位簇容量大以致空间急剧浪费等缺点设计的。实际应用中,FAT32还是成功的。

FAT32与FAT16的原理基本上是相同的,图4.3.12标出了FAT32分区的基本构成。

图4.3.12 Fat32的组织形式
引导扇区其余保留扇区FAT1FAT2(重复的)根文件夹首簇其他文件夹及所有文件剩余扇区
1扇区31个扇区实际情况取大小同FAT1第2簇
不足一簇
保留扇区

┗━━━━━━━━数据区━━━━━━━━┛

FAT32在格式化的过程中就根据分区的特点构建好了它的DBR,其中BPB参数是很重要的,可以回过头来看一下表4和表5。首先FAT32保留扇区的数目默认为32个,而不是FAT16的仅仅一个。这样的好处是有助于磁盘DBR指令的长度扩展,而且可以为DBR扇区留有备份空间。上面我们已经提到,构建在FAT32上的win98或win2000、winXP,其操作系统引导代码并非只占一个扇区了。留有多余的保留扇区就可以很好的拓展OS引导代码。在BPB中也记录了DBR扇区的备份扇区编号。备份扇区可以让我们在磁盘遭到意外破坏时恢复DBR。

FAT32的文件分配表的数据结构依然和FAT16相同,所不同的是,FAT32将记录簇链的二进制位数扩展到了32位,故而这种文件系统称为FAT32。32位二进制位的簇链决定了FAT表最大可以寻址2T个簇。这样即使簇的大小为1扇区,理论上仍然能够寻址1TB范围内的分区。但实际中FAT32是不能寻址这样大的空间的,随着分区空间大小的增加,FAT表的记录数会变得臃肿不堪,严重影响系统的性能。所以在实际中通常不格式化超过32GB的FAT32分区。WIN2000及之上的OS已经不直接支持对超过32GB的分区格式化成FAT32,但WIN98依然可以格式化大到127GB的FAT32分区,但这样没必要也不推荐。同时FAT32也有小的限制,FAT32卷必须至少有65527个簇,所以对于小的分区,仍然需要使用FAT16或FAT12。

分区变大时,如果簇很小,文件分配表也随之变大。仍然会有上面的效率问题存在。既要有效地读写大文件,又要最大可能的减少空间的浪费。FAT32同样规定了相应的分区空间对应的簇的大小,见表12:

表12 FAT32分区大小与对因簇大小
分区空间大小每个簇的扇区簇空间大小
<8GB84k
>=8GB且<16GB168k
>=16GB且<32GB3216k
>=32GB6432k

簇的取值意义和FAT16类似,不过是位数长了点罢了,比较见表13:

表13 FAT各系统记录项的取值含义(16进制)
FAT12记录项的取值FAT16记录项的取值FAT32记录项的取值对应簇的表现情况
000000000000000未分配的簇
002~FFF0002~FFEF00000002~FFFFFFEF已分配的簇
FF0~FF6FFF0~FFF6FFFFFFF0~FFFFFFF6系统保留
FF7FFF7FFFFFFF7坏簇
FF8~FFFFFF8~FFFFFFFFFFF8~FFFFFFFF文件结束簇

FAT32的另一项重大改革是根目录的文件化,即将根目录等同于普通的文件。这样根目录便没有了FAT16中512个目录项的限制,不够用的时候增加簇链,分配空簇即可。而且,根目录的位置也不再硬性地固定了,可以存储在分区内可寻址的任意簇内,不过通常根目录是最早建立的(格式化就生成了)目录表。所以,我们看到的情况基本上都是根目录首簇占簇区顺序上的第1个簇。在图4.3.12中也是按这种情况制作的画的。

FAT32对簇的编号依然同FAT16。顺序上第1个簇仍然编号为第2簇,通常为根目录所用(这和FAT16是不同的,FAT16的根目录并不占簇区空间,32个扇区的根目录以后才是簇区第1个簇)

FAT32的文件寻址方法与FAT16相同,但目录项的各字节参数意义却与FAT16有所不同,一方面它启用了FAT16中的目录项保留字段,同时又完全支持长文件名了。

对于短文件格式的目录项。其参数意义见表14:

表14 FAT32短文件目录项32个字节的表示定义
字节偏移(16进制)字节数定义
0x0~0x78文件名
0x8~0xA3扩展名
0xB*1属性字节00000000(读写)
00000001(只读)
00000010(隐藏)
00000100(系统)
00001000(卷标)
  00010000(子目录)
00100000(归档)
0xC1系统保留
0xD1创建时间的10毫秒位
0xE~0xF2文件创建时间
0x10~0x112文件创建日期
0x12~0x132文件最后访问日期
0x14~0x152文件起始簇号的高16位
0x16~0x172文件的最近修改时间
0x18~0x192文件的最近修改日期
0x1A~0x1B2文件起始簇号的低16位
0x1C~0x1F4表示文件的长度

* 此字段在短文件目录项中不可取值0FH,如果设值为0FH,目录段为长文件名目录段

说明:

(1)、这是FAT32短文件格式目录项的意义。其中文件名、扩展名、时间、日期的算法和FAT16时相同的。

(2)、由于FAT32可寻址的簇号到了32位二进制数。所以系统在记录文件(文件夹)开始簇地址的时候也需要32位来记录,FAT32启用目录项偏移0x12~0x13来表示起始簇号的高16位。

(3)、文件长度依然用4个字节表示,这说明FAT32依然只支持小于4GB的文件(目录),超过4GB的文件(目录),系统会截断处理。

FAT32的一个重要的特点是完全支持长文件名。长文件名依然是记录在目录项中的。为了低版本的OS或程序能正确读取长文件名文件,系统自动为所有长文件名文件创建了一个对应的短文件名,使对应数据既可以用长文件名寻址,也可以用短文件名寻址。不支持长文件名的OS或程序会忽略它认为不合法的长文件名字段,而支持长文件名的OS或程序则会以长文件名为显式项来记录和编辑,并隐藏起短文件名。

当创建一个长文件名文件时,系统会自动加上对应的短文件名,其一般有的原则:

(1)、取长文件名的前6个字符加上"~1"形成短文件名,扩展名不变。

(2)、如果已存在这个文件名,则符号"~"后的数字递增,直到5。

(3)、如果文件名中"~"后面的数字达到5,则短文件名只使用长文件名的前两个字母。通过数学操纵长文件名的剩余字母生成短文件名的后四个字母,然后加后缀"~1"直到最后(如果有必要,或是其他数字以避免重复的文件名)。

(4)、如果存在老OS或程序无法读取的字符,换以"_"

长文件名的实现有赖于目录项偏移为0xB的属性字节,当此字节的属性为:只读、隐藏、系统、卷标,即其值为0FH时,DOS和WIN32会认为其不合法而忽略其存在。这正是长文件名存在的依据。将目录项的0xB置为0F,其他就任由系统定义了,Windows9x或Windows 2000、XP通常支持不超过255个字符的长文件名。系统将长文件名以13个字符为单位进行切割,每一组占据一个目录项。所以可能一个文件需要多个目录项,这时长文件名的各个目录项按倒序排列在目录表中,以防与其他文件名混淆。

长文件名中的字符采用unicode形式编码(一个巨大的进步哦),每个字符占据2字节的空间。其目录项定义如表15。

表15 FAT32长文件目录项32个字节的表示定义
字节偏移(16进制)字节数定义
0x01属性字节位意义7保留未用
61表示长文件最后一个目录项
5保留未用
4顺序号数值
3
2
1
0
0x1~0xA10长文件名unicode码①
0xB1长文件名目录项标志,取值0FH
0xC1系统保留
0xD1校验值(根据短文件名计算得出)
0xE~0x1912长文件名unicode码②
0x1A~0x1B2文件起始簇号(目前常置0)
0x1C~0x1F4长文件名unicode码③

系统在存储长文件名时,总是先按倒序填充长文件名目录项,然后紧跟其对应的短文件名。从表15可以看出,长文件名中并不存储对应文件的文件开始簇、文件大小、各种时间和日期属性。文件的这些属性还是存放在短文件名目录项中,一个长文件名总是和其相应的短文件名一一对应,短文件名没有了长文件名还可以读,但长文件名如果没有对应的短文件名,不管什么系统都将忽略其存在。所以短文件名是至关重要的。在不支持长文件名的环境中对短文件名中的文件名和扩展名字段作更改(包括删除,因为删除是对首字符改写E5H),都会使长文件名形同虚设。长文件名和短文件名之间的联系光靠他们之间的位置关系维系显然远远不够。其实,长文件名的0xD字节的校验和起很重要的作用,此校验和是用短文件名的11个字符通过一种运算方式来得到的。系统根据相应的算法来确定相应的长文件名和短文件名是否匹配。这个算法不太容易用公式说明,我们用一段c程序来加以说明。

假设文件名11个字符组成字符串shortname[],校验和用chknum表示。得到过程如下:

int i,j,chknum=0;

for (i=11; i>0; i--)

  chksum = ((chksum & 1) ? 0x80 : 0) + (chksum >> 1) + shortname[j++];

如果通过短文件名计算出来的校验和与长文件名中的0xD偏移处数据不相等。系统无论如何都不会将它们配对的。

依据长文件名和短文件名对目录项的定义,加上对簇的编号和链接,FAT32上数据的读取便游刃有余了。

FAT32文件系统学习

目的:需要编写SD读图片的底层驱动程序。所以要了解一个SD卡常用文件系统基本概念。累计学习用时2.5小时。

一,FAT32的保留区

1,引导扇区 :引导扇区是FAT32文件系统的第一个扇区,也称为DBR扇区。它包含这样一些文件系统的基本信息:

【1】 每扇区字节数 【2】 每簇扇区数 【3】 保留扇区数【4】 FAT表个数 【5】 文件系统大小(扇区数)【6】 每个FAT表大小(扇区数) 【7】 根目录起始簇号 【8】 其他一些附加信息

边看说明,边看图片不太方便,我就按照说明内容,把说明直接标注在图片上了。

我的SD卡是手机里的tf卡+sd卡套。之前没有问题。当我第一次格式化后,就发现不正常了。虽然存取文件都没问题。但是放在我的开发板上测试SD的时候,数据显示不正确。

现在我初步发现问题在这里

【13】0x1C~0x1F:4个字节,分区前已使用扇区数,137(0x00 00 00 89)。(这个数据要尤其的重视,文件系统初始化的第一步要找的就是这玩意儿。因为我们的SD卡没有分区,默认就是一个分区,这个数据就是相对于MBR(关于MBR的介绍请读者参看8.4小节的DOC 分区)的地址偏移量,MBR的扇区地址才是整个SD卡的物理扇区号为0的那个地址,也就是说文件系统并不是处在整个SD卡最开始的地方,它处在MBR所处的保留区之后,于是我们可以对使用FAT32文件系统的SD卡整体布局给出如下图示)

但是我0x1C到0x1F的4个字节为0.不知道是不是问题。

2,引导代码

FAT32文件系统引导扇区的512字节中,90~509字节为引导代码,而FAT12/16则是62~509字节为引导代码。同时,FAT32还可以利用引导扇区后的山区空间存放附加的引导代码。

一个FAT卷即使不是可引导文件文件系统,也会存在引导代码。

3,FSINFO信息扇区

FAT32在保留区中增加了一个FSINFO扇区,用以记录文件系统中空闲簇的数量以及下一可用簇的簇号等信息,以供操作系统作为参考。

FSINFO信息扇区结构

省略

温馨提示:通常情况下,文件系统的2号扇区结尾也会被设置“55 AA”标志。6号扇区也会有一个引导扇区的备份,相应的,7号扇区应该是一个备份FSINFO信息扇区。8号扇区可以看做是2号扇区的备份,它的结尾也会有一个“55 AA”标志。

二,FAT32的FAT表

1 FAT表概述

位于保留区后的是FAT区,有两个完全相同的FAT(File Allocation Table, 文件分配表)表组成,FAT文件系统的名字也是因此而来。

重要说明:

1. 对于文件系统来说,FAT表有两个重要作用:描述簇的分配状态以及标明文件或目录的下一簇的簇号。

2. 通常情况下,一个FAT把文件系统会有两个FAT表,但有时也允许只有一个FAT表,FAT表的具体个数记录在引导扇区的偏移0x10字节处。

3. 由于FAT区紧跟在文件系统保留区后,所以FAT1在文件系统中的位置可以通过引导记录中偏移0x0E~0x0F字节处的“保留扇区数”得到。

4. FAT2紧跟在FAT1之后,它的位置可以通过FAT1的位置加上FAT表的大小扇区数计算出来。

2 FAT表的特性

FAT表由一系列大小相等的FAT表项组成,总的说来FAT表有如下特性:

1. FAT32中每个簇的簇地址,是有32bit(4个字节)记录在FAT表中。FAT表中的所有字节位置以4字节为单位进行划分,并对所有划分后的位置由0进行地址编号。0号地址与1号地址被系统保留并存储特殊标志内容。从2号地址开始,每个地址对应于数据区的簇号,FAT表中的地址编号与数据区中的簇号相同。我们称FAT表中的这些地址为FAT表项,FAT表项中记录的值称为FAT表项值。

2. 当文件系统被创建,也就是进行格式化操作时,分配给FAT区域的空间将会被清空,在FAT1与FAT2的0号表项与1号表项写入特定值。由于创建文件系统的同时也会创建根目录,也就是为根目录分配了一个簇空间,通常为2号簇,所以2号簇所对应的2号FAT表项也会被写入一个结束标记。如下图所示:

3. 如果某个簇未被分配使用,它所对应的FAT表项内的FAT表项值即用0进行填充,表示该FAT表项所对应的簇未被分配。

4. 当某个簇已被分配使用时,则它对应的FAT表项内的FAT表项值也就是该文件的下一个存储位置的簇号。如果该文件结束于该簇,则在它的FAT表项中记录的是一个文件结束标记,对于FAT32而言,代表文件结束的FAT表项值为0x0FFFFFFF。

5. 如果某个簇存在坏扇区,则整个簇会用FAT表项值0xFFFFFF7标记为坏簇,不再使用,这个坏簇标记就记录在它所对应的FAT表项中。

6. 由于簇号起始于2号,所以FAT表项的0号表项与1号表项不与任何簇对应。FAT32的0号表项值总是“F8FFFF0F”。如上图所示。

7. 1号表项可能被用于记录脏标志,以说明文件系统没有被正常卸载或者磁盘表面存在错误。不过这个值并不重要。正常情况下1号表项的值为“FFFFFFFF”或“FFFFFF0F”。

8. 在文件系统中新建文件时,如果新建的文件只占用一个簇,为其分配的簇对应的FAT表项将会写入结束标记。如果新建的文件不只占用一个簇,则在其所占用的每个簇对应的FAT表项中写入为其分配的下一簇的簇号,在最后一个簇对应的FAT表象中写入结束标记。

9. 新建目录时,只为其分配一个簇的空间,对应的FAT表项中写入结束标记。当目录增大超出一个簇的大小时,将会在空闲空间中继续为其分配一个簇,并在FAT表中为其建立FAT表链以描述它所占用的簇情况。

10. 对文件或目录进行操作时,他们所对应的FAT表项将会被清空,设置为0以表示其所对应的簇处于未分配状态。

要找一个簇的FAT表项,只要用它的簇号乘以每个FAT表项的字节数即可。Winhex提供了直接跳转到某个指定FAT表项的功能,单击position|go to FAT Entry,即可弹出转到FAT项对话框,在对话框输入目标FAT项号码后单击OK,光标即会在该FAT项的第一个字节上闪烁。

看到这里,我明白了簇地址就是一个大文件,被拆分成小块后的一个个个像链表一样的地址。保存在FAT表中。

今天主要看了保留区和FAT表区。明天继续看数据区。

FAT32文件系统学习(2) —— FAT表

1、题外话

在继续本文学习FAT32文件系统之前,先来插入一点别的话题。我们都知道U盘有一个属性是容量,就拿笔者的U盘为例,笔者手上的U盘是金士顿的DataTraveler G3 4GB的一个U盘。电脑上显示的容量如图1所示为3.75GB。那么这个3.75GB是怎么计算出来的呢?

图 1 系统显示U盘属性

我们先来回顾一下上一篇BPB参数当中的Sectors(扇区总数)这个参数,这一参数代表了这个U盘在出厂时的总扇区数,笔者手上这个是7884672个,可以从图2中看到。其中每个扇区为512 B,也就是说总共可以容纳4036952064 B约为3.76GB的数据。但是这其中一部分是要用来存放FAT32文件系统的相关信息参数的,比如FAT表,BPB等。我们这边来算一下,首先需要减去1016个保留扇区,还有两个FAT表总共是7684 * 2 = 15368个扇区,所示还剩下的字节数为4036952064 B - ( 1016 + 15368 ) * 512 B = 4028563456 B 正好是图中显示的容量。所以可以得出结论,系统显示的U盘容量 = ( 总扇区数 - 保留扇区数 - FAT表扇区数 * FAT表个数 ) * 512 B。经计算可得实际的使用率是99.79%。所以相对与整个U盘来说,FAT32文件系统用于存储相关信息部分的损耗是很小的。

图 2 笔者用上一篇中写的工具查看了U盘的各项参数

好了,接下来进入正题,继续学习FAT32文件系统的FAT表部分。

2、本文目录

1、题外话

2、FAT表的读取

3、FAT表项

4、参考文献

3、FAT表的读取

首先FAT表一般来说有两张,另一张用于备份。两张表是前后紧挨在一起的,只要计算出了FAT1表的偏移之后加上FAT表的大小就可以得到FAT2表的偏移。FAT1表的偏移地址计算公式如下[4]

FAT1表偏移 = 保留扇区数 * 每扇区字节数

由图2可知,在本例中,FAT1表的偏移 = 1016 * 512 B = 520192 = 0x7F000。同理:

FAT2表的偏移 = FAT1+FAT表的大小 = (保留扇区数 + FAT表扇区数) * 每扇区字节数

在本例中,FAT2表的偏移 = (1016 + 7684) * 512 B = 4454400 = 0x43F800。用上一篇中讲到的程序可以读取出两张FAT表的内容,一般情况下两张表的内容应该是完全一样的。笔者读取了第一张FAT表起始部分的内容,如图3所示:

图 3 FAT表起始部分内容

4、FAT表项

在分析FAT表之前先来说明一下FAT的构成。FAT表即文件分配表(File Allocation Table)。FAT32文件表是由一个个表项组成的一张表,其中每一个表项由一个32位的二进制组成,其值对应了相应簇的使用情况,如2号表项对应了2号簇的使用情况,3号表项对应了3号簇的使用情况,依此类推。(但是第0和第1项例外,下面会有说明)。每个表项对应数值的含义如表1所示[2]

表项数值对应含义
0x00000000空闲簇,即表示可用
0x00000001保留簇
0x00000002 - 0x0FFFFFEF被占用的簇,其值指向下一个簇号
0x0FFFFFF0 - 0x0FFFFFF6保留值
0x0FFFFFF7坏簇
0x0FFFFFF8 - 0x0FFFFFFF文件最后一个簇

表 1 表项数值含义

具体每一项填写的内容规则如下表所示:如果该簇是文件的最后一簇,填入的值为0x0FFFFFFF;如果该簇不是文件的最后一簇,则填入的值为该文件占用的下一簇号(所以我们可以看到在FAT32中文件是以簇链的形式保存起来的)。下面我们根据实际情况,图3来分析一下FAT表的含义。

FAT表第0项(0x00000000~0x00000003): 0x0FFFFF8

FAT表第1项(0x00000004~0x00000007): 0xFFFFFFFF

这两项不代表任何簇的使用情况,而是FAT表的表头,表征了介质描述,是固定值,所以0x00和0x01这两个簇号是不用的,簇号的下标从2开始。其中1号表项可能被用于记录脏标志,以说明文件系统没有被正常卸载或者磁盘表面存在错误。接下来

FAT表第2项(0x00000008~0x0000000B): 0x0FFFFFFF

第2项存储的是第2簇的使用情况,通常第2簇存储的是文件系统的根目录。虽然在FAT32文件系统中,根目录的位置不再硬性地固定,可以存储在分区内可寻址的任意簇内,不过通常根目录是最早建立的(格式化就生成了)目录表。所以,我们看到的情况基本上都是根目录首簇紧邻FAT2,占簇区顺序上的第1个簇(即2号簇)。同时,FAT32文件系统将根目录当做普通的数据文件来看,所有没有了目录项数的限制,在需要的时候可以分配空簇,存储更多的目录项[1]

这一项的值为0x0FFFFFFF ,说明根目录占用且只占用了1个簇。

FAT表第3 ……

这里再穿插一点题外话,FAT32格式文件分配的最小单位是簇。也就是说你存储了一个实际大小1kB的文件,那么它占用的存储空间还是1簇(在这里换算成大小即为8*512B = 4KB)。笔者以一个实际的例子来说明一下:在U盘中放入一个8B大小的temp.txt文件,然后查看文件属性的时候发现其占用空间是4KB,和我们上面讲的理论符合。

图 4 temp.txt的大小和占用空间

看了下篇幅也差不多了,那么本文关于FAT表的部分到此结束。其实本来也没多少内容,笔者想到哪就扯到哪,胡扯了些其他的东西。剩下的数据区部分就留到下一篇当中再讲好了。同样的,本文当中有一些内容是笔者自己思考理解甚至推测出来的,如果有错误的地方欢迎指正,以免误人子弟了(笑)。

FAT32文件系统学习(3) —— 数据区(DATA区)

今天继续学习FAT32文件系统的数据区部分(Data区)。其实这一篇应该是最有意思的,我们可以通过在U盘内放入一些文件,然后在程序中读取出来;反过来也可以用程序在U盘内写入一下数据,然后在windows下可以看到写入的文件。这些笔者都会在这篇文章中演示(后来发现并没有成功,不过笔者也找到相关的原因,详见后来的更新部分吧:) )。同时,在写这篇文章的时候笔者也发现了许多意想不到的规律。

1、本文目录

1、读取根目录

2、短文件名目录项

3、长文件名目录项

4、U盘写入文件夹

5、参考文献

2、读取根目录

两张FAT之后的所有扇区都是数据区部分。我们再通过图1来回顾一下整个FAT32文件系统的分布规则。

图 1 FAT32文件系统分布图

通常情况下根目录都是位于数据区头部的,前面也提到过,有文献上说是因为一旦U盘格式化完毕之后,根目录就创建好了。本着探究的精神,笔者尝试格式化了一下U盘,发现其实并没有创建根目录。不过一旦有文件操作,马上就会创建根目录,因为这时整个数据区都是空的,所以自然是写入数据区的头部。到头来其实道理是一样的,也就是说根目录一般情况下都是在数据区的头部(第2簇)。

数据区偏移计算

经过前两篇关于BPBFAT部分学习之后,我们就可以计算出数据区头部的偏移

数据区偏移 = (保留扇区数 +  FAT表扇区数 * FAT表个数(通常为2) + (起始簇号-2) * 每簇扇区数) * 每扇区字节数

笔者首先格式化了U盘,通过偏移读取出了数据区的头部,发现都是0x00。

题外话

这里又要插一些题外话了,笔者试着改了一下U盘的卷标,把它改名为“FAT”。然后还记得BPB当中有一个参数叫做卷标吗?笔者看了下发现卷标这个参数还是“NO NAME”并没有改变。这时笔者把数据区的头部读取了出来,如图2所示:

图2 卷标

原来被写在了这里。最后经笔者的测试,卷标最长长度11个字节,偏移从0x00~0x0A,而偏移0x0B处的值0x08值的意思就是卷标(关于此处值的意思相面还会详细描述)。因为这个U盘其实还没有写入过任何数据,所以卷标才会显示在数据区的开头,但是如果换种情况呢,文件系统又是如何找到卷标的呢?笔者查阅了相关资料后发现,卷标一般都是写在根目录的下的,如果发现根目录项的其中一项其0x0B偏移处的值为0x08那么读取该项的前11个字节即为卷标。

3、短文件名目录项

短文件名目录项参数

好,回到正题。先来讲一下理论的东西:目录区是由一个个目录项构成,类似于FAT表。其中每一个目录项占用32个字节,可以是代表长文件名目录项、文件目录项、子目录项等。对于短文件名格式的目录项,其参数的含义如表1所示(不会画这种表,从别处引用了一个)[1]

表1 FAT32短文件名目录项参数表

参数解释

用一个实际的例子来解释一下这些参数的意思,首先创建一个短文件名文件,如“file1.txt”,读取根目录,如图3所示:

图3 file1.txt 短文件名目录项

先不管其他数据,我们来看一下红色矩形框部分的数据,它就是一个短目录项。我们来一个个对比表1的参数进行说明:

字节偏移参数含义
0x00~0x07文件名

对应字符串“FILE1”

0x08~0x0A后缀名

对应字符串“TXT”

0x0B属性字节

0x20 = 00100000(2进制) 表示归档

0x0C系统保留
0x0D创建时间的10毫秒位=88,即0x88 * 10ms = 1360ms(10进制)
0x0E~0x0F文件创建时间

0x785C = (0111100001011100)(2进制)

即为 15:02:57(注释1)

0x10~0x11文件创建日期

0x4508 = (0100010100001000)(2进制)

即为 2014/8/8(注释2)

0x12~0x13文件最后访问日期

0x4508 = (0100010100001000)(2 进制)

即为 2014/8/8 算法参考创建日期

0x14~0x15文件起始簇号高16位

0x0000,可以用来计算出文件实际内容的偏移值,

这个放到后面单独计算。

0x16~0x17文件最近修改时间

0x7869 = (0111100001101001)(2进制)

即为 15:03:18 算法参考创建时间

0x18~0x19文件最近修改日期

0x4508 = (0100010100001000)(2进制)

即为 2014/8/8 算法参考创建日期

0x1A~0x1B文件起始簇号低16位0x0005
0x1C~0x0F文件长度0x0000000C = 12

表2 file1.txt 参数解释

注释1:01111 000010 11100

1)这里高5位代表小时,由于2^5 = 32,足够表示24小时,这边01111(2进制) = 15(10进制);

2)次6位代表分钟,同理2^6 = 64,足够表示60分钟,这边000010(2进制) = 2;

3)低5位表示秒的1/2, 计算结果需要加上毫秒位上的进位,这边11100(2进制) = 28(10进制),所以秒数 = 28*2 = 56,再加上毫秒上的进位1所以结果为57。

注释2:0100010 1000 01000

1)这里高7位代表从1980年开始的年数,笔者计算了下可以到2108年,总之还有90多年可以使用,这边0100010(2进制) = 34,所以年份 = 1980+34 = 2014;

2)次4位代表月份,2^4=16,可以表示12个月份,这边 1000(2进制) = 8(10进制);

3)低5位代表日期,2^5 = 32,可以表示28~31天,这边 01000(2进制) = 8(10进制)。

这样除了文件起始簇号字段,其他字段的意思和计算方法都弄清楚了。下面来看一下文件起始簇号,首先根据高低各16位,计算出完整的文件起始簇号 = 0x00000005 ,文件起始地址偏移的计算

文件起始地址 = (保留扇区数 + FAT表扇区数 * FAT表个数(2) + (文件起始簇号-2)*每簇扇区数)*每扇区字节数

本例中计算结果为0x4010,然后到这个地址读取内容并切入到磁盘文件中(详细操作参考第一篇文章),如图4所示,windows下打开内容如图5所示:

图4 图5 文件内容

这个时候再去看一下FAT表的5号簇,计算方式在上一篇当中,结果如图6所示:

图 6 FAT表5号表项

红色矩形框的位置就是5号簇的位置,可以看到值0x0FFFFFFF,意思就是文件在这一簇结束了。 (具体不同数值的含义详见上一篇)。如果这里文件大小超过1簇,那么这个值应该是下一簇的簇号,继续读取下一簇的内容即可。虽然我们知道了文件占用的空间是1簇,但是怎么知道文件具体的大小呢?再回过头来看上面的短文件目录项,最后一个属性是文件长度,上面已经计算得到为12,“Hello World!”的长度正好是12:)。

至此短文件目录项应该已经分析的差不多了。

4、长文件名目录项

长文件名目录项参数

下面是长文件名目录项,笔者思考了好久该怎么把它讲清楚,毕竟理解是一回事,讲清楚就是另一回事了。

在讲长文件目录项之前先来说一下FAT32的一个很重要的特性,支持长文件名。长文件名也是记录在目录项当中的,区别与短目录项的是,前者可能会占据好几个目录项。为了兼容低版本的OS或程序能正确读取长文件名文件,系统自动为所有长文件名文件创建了一个对应的短文件名,使对应数据既可以用长文件名寻址,也可以用短文件名寻址。不支持长文件名的OS或程序会忽略它认为不合法的长文件名字短,而支持长文件名的OS或程序则会以长文件名为显式项来记录和编辑,并隐藏起短文件名[2]

当创建一个长文件名文件时,系统会自动加上对应的短文件名,其原则如下:

(1)、取长文件名的前6个字符加上"~1"形成短文件名,扩展名不变。

(2)、如果已存在这个文件名,则符号"~"后的数字递增,直到5。

那么系统是如何判断当前目录项是短文件名目录项呢还是长文件名目录项,这里关键是看目录项的第12个字节的值,如果为0x0F时则系统认为是长目录项。而如果是旧版本的系统看到第12个字节是0x0F则认为是异常而忽略掉。这里可以回过头去看一下短文件名目录项,第12个字节是文件属性字节,0x0F即为全1是无效的,所以系统认为是异常。系统将长文件名以13个字符为单位进行切割,每一组占据一个目录项。所以可能一个文件需要多个目录项,这时长文件名的各个目录项按倒序排列在目录表中,以防与其他文件名混淆。

这样讲可能还是很抽象,先来看一下长文件名目录项的参数,如表3所示[1]

表3 长文件名目录项参数表

参数解释

然后还是以一个实际的例子来说明,在根目录区读入一个长文件名目录项,如图7所示:

图7 长文件名目录项

图中选定部分即为多个长文件名目录项。我们来慢慢分析。系统在读入一个目录项的时候首先查看它的第12个字节,发现是0x0F,所以认为这是一个长文件名目录项。我们来看长文件名目录项的参数,如表4所示:

偏移字段含义
0x00属性字节位0x42 = (01000010)(2进制)(注释1)
0x01~0x0A10个字节的Unicode码即字符串”ename”>注释2)
0x0B长文件名目录项

0x0F前面已经讲过

0x0C系统保留
0x0D校验值这个等整个文件名读取完再讲
0x0E~0x1912字节Unicode即字符串“Test”
0x1A~0x1B文件起始簇号常置0
0x1C~0x1F4字节Unicode

0xFFFFFFFF

如果文件名已经结束的话则全部为0xFF

表4 长文件名目录项参数解释

注释1:01000010

第7位为1,说明是文件最后一个目录项目,低5位为顺序 0010(2进制) = 2(10进制),说明这是第2个长目录项,且是最后一个目录项。即为这个长文件名占用了两个目录项。

注释2:Unicode 百度百科Unicode 点我详细解释

这边有3个Unicode区,加起来正好是26个字节13个Unicode码,所以这就是为什么上面讲的以13个字符为单位切割。因为这是第2个目录项,所以后面应该还有第1个目录项,继续分析下一个目录项其余参数同上,看一下3个Unicode分别是“LongL” “engthF” “il”0x00的属性字节是01,说明这是第一个。至此这个长文件名读取完毕了。按照倒序(这里也解释了前面说的倒序的意思)的顺序拼接起来的话就是“LongLengthFilename”——这就是这个文件的文件名。

下面再来看一下下一个目录项,长文件名目录项后面还会跟一个短文件名目录项,这个目录项记录了除文件名以外的这个文件的信息,而文件名部分则用上面提到的短文件名目录项替换。所以读取方法和短文件名目录项是一样的,这里只看一下文件属性字节,偏移为0x0B,值为0x10=(00010000) 根据短文件名目录项参数的意思,这个文件是一个子目录。其余参数读者可以根据上面提到的计算方法得出。

最后再来补上刚才的校验码计算方法:

int i, j = 0, chksum=0;

for (i = 11; i > 0; i--)

chksum = ((chksum & 1) ? 0x80 : 0) + (chksum >> 1) + shortname[j++];

其中shortname即长文件名目录项对应的短文件名,所以这个校验码需要等到读完短文件名目录项之后才可以计算。这一段程序是笔者从网上摘来的,还没有时间验证一下。

5、U盘写入文件夹

这样关于数据区的部分差不多就讲完了。

最后在做一点有趣的事情,尝试向磁盘的扇区中写入一些数据,然后看是否会生成这个文件。为了方便起见,这里直接在根目录创建一个文件夹好了,

文件夹的名字叫做root,

创建时间日期2014/8/8 18:18:18

访问日期 2014/8/8

最近修改时间日期 2014/8/8 18:18:18

起始簇低16位 04 00

起始簇高16位 00 00

文件长度 0

同时修改2个FAT表第4项为0x0FFFFFFF

这样应该就可以了,好了,开始编码:

// 短文件名目录项数据结构

typedef struct ShortDirItem

{

char strFilename[8];

char strExtension[3];

char attribute;

char reserved;

char millisecond;

unsigned short createTime;

unsigned short createDate;

unsigned short accessDate;

unsigned short highWordCluster;

unsigned short updateTime;

unsigned short updateDate;

unsigned short lowWordCluster;

unsigned int filesize;17 }ShortDirItem;

// 定位到FAT1表

SetFilePointer(hDisc, 1016*512, 0, FILE_BEGIN);

DWORD dwNumber2Read = 512;

// 实际读取的字节数

DWORD dwRealNumber;

// 分配缓冲区

char* buffer = new char[512];

// 读取一个扇区的数据

BOOL bRet = ReadFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);

// 把4第项改为 0x0FFFFFFF

buffer[12] = buffer[13] = buffer[14] = 0xFF;

buffer[15] = 0x0F;

// 写回FAT1

bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);

// 定位到FAT2表

SetFilePointer(hDisc, (1016+7684)*512, 0, FILE_BEGIN);

// 把4第项改为 0x0FFFFFFF

bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);

// 定位到根目录

SetFilePointer(hDisc, (1016+7684*2)*512, 0, FILE_BEGIN);

// 读取根目录扇区

bRet = ReadFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);

// 准备数据

ShortDirItem* item = new ShortDirItem();

strcpy(item->strFilename, "root");

strcpy(item->strExtension, " ");

item->attribute = 0x10;

item->millisecond = 0x00;

item->createTime = 0x9249;

item->createDate = 0x4508;

item->accessDate = 0x4508;

item->highWordCluster = 0x0000;

item->updateTime = 0x9249;

item->updateDate = 0x4508;

item->lowWordCluster = 0x0004;

item->filesize = 0x00;

// 修改根目录数据

char* pData = (char*)item;

for (int i = 32; i < 64; ++i)

{

buffer[i] = *(pData++);

}

// 写回根目录

bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);

// 扫尾工作,释放缓冲区,关闭句柄

delete[] buffer;48 delete item;49 CloseHandle(hDisc);

但是笔者发现在win7(准确的说是win7、vista、win8,XP下获取管理员权限即可执行)下调用WriteFile函数无法将数据写入U盘,可能是由于系统保护措施的关系,由于时间关系,笔者也没去深究。

后来笔者专门去查找了相关资料,总的来说原因确实是因为系统保护措施的关系导致WriteFile函数操作的失败,具体的解释如下:

首先是msdn上的解释 http://msdn.microsoft.com/en-us/library/windows/hardware/ff551353(v=vs.85).aspx。大概意思是说在win7和vista上加入了一些新的特性,为了能够更好得保护系统,如果应用程序没有独占的权限就直接对装有文件系统的存储设备进行写入操作的话,这个操作是会被拒绝的。笔者上面的程序通过GetLastError()函数得到的ErroeCode=5,意思也确实是拒绝访问。那么到底要如何写入呢,msdn上给出了以下几种情况:

Write operations on a DASD volume handle will succeed if the file system is not mounted, or if:

The sectors being written to are the boot sectors.

The sectors being written to reside outside file system space.

The file system has been locked implicitly by requesting exclusive write access.

The file system has been locked explicitly by sending down a lock/dismount request.

The write request has been flagged by a kernel-mode driver that indicates that this check should be bypassed. The flag is called SL_FORCE_DIRECT_WRITE and it is in the IrpSp->flags field. This flag is checked by both the file system and storage drivers.

这里比较方便的做法可以采用第4种,即显示地发送一个锁定驱动的请求,然后再尝试写入。具体做法参考这个帖子22L吧,笔者打算去尝试一下,成功的话再来更新结果。

好了,看了下篇幅这篇文章也差不多可以结束了。FAT32文件系统其实差不多也都学习完了,为了巩固学习内容,笔者打算接下去根据前面所学的知识,并去了解一下windows快速格式化FAT32的机制,尝试自己格式化U盘,还可以根据FAT32的原理尝试删除数据的恢复等,总之还是有很多事情可以做的。

最后的最后,如果文章当中有任何错误或者遗漏指出,欢迎指出,谢谢。

FAT32 FAT区__FAT表解析

一、 FAT 表概述

位置:紧跟在文件系统的“保留区”之后 ; 有两个数据结构完全相同的FAT(FAT,File Allocation Tbale 文件分配表)组成。

作用:FAT表项,描述文件系统内的簇分配状态,说明文件系统内数据所分配的连续簇的顺序关系(即表明文件或目录的下一簇的序号)。

常规规则:

· 数量:通常情况下一个FAT 文件系统会有两个FAT 表, 但有时候也会允许只有一个FAT 表, FAT 表的具体个数记录在引导扇区的 偏移 0x10 字节处。

· 位置:因为FAT区位于文件系统的保留区之后,所以FAT1在文件系统中的位置可以通过引导记录中偏移0x0E~0x0F 字节处的“保留扇区”数得到。

· FAT2 紧跟在FAT1之后, 它的位置可以通过FAT1的位置加上每个FAT 表的大小扇区数获得。

FAT 表中记录了每个文件的簇链结构; FAT 表中记录的与数据区簇对应的表项,从0号标记开始至当前数据区所分配的簇的最大数值,记录簇信息到FAT 项;但是注意:其中 0号~1号簇的值都是操作系统预先不留设定的特殊标记,而数据区的起始簇是2号簇。

二、、FAT 表的特性

FAT 表由一些列大小相等的表项组成,有如下特性:

· FAT32 中每个簇的状态,使用32bit(4字节)记录在FAT表中。 FAT 表中的所有字节位置以 4个字节为单位进行划分;并以所有划分后的位置由0进行地址编号。“0号 和 1号” 地址被系统保留并存储特殊标识内容。从 2号 地址开始, 每个地址对应于数据区的簇号,FAT 表中的地址编号与数据区中的簇号相同。称FAT 中的这些四字节一组划分的项的地址为 FAT 表项FAT表项中记录的值为FAT 表项值。(簇编号与簇内内容关系如果 Map中的键-值 关系相同)

  当文件系统创建时(就是格式化操作时), 分配给FAT 区域的空间将会被清空, 在FAT1与FAT2 的0号和1号表项写入特定值。由于创建文件系统的同时,也会创建根目录, 也就是为根目录分配了一个簇空间,通常为2号簇,所以2号簇所对应的“2号FAT表项”也会被写入一个结束标记。

· 如果某个簇未被使用,他所对应的FAT 表项内的FAT 表项值即用0进行填充,表示该FAT 表项所对应的簇未被分配使用

· 当某个簇被分配使用时,那么他所对应的FAT表项的值为文件的下一个存储文件的簇号。 如果该文件结束于该簇,则在它的FAT表项中记录的是一个文件结束标记,对于FAT32 而言,代表文件结束的FAT表项值为0x0FFFFFFF。

· 如果某个簇存在坏扇区,则整个簇会用FAT 表项值0x0FFFFFF7 标记为坏簇, 不再使用,这个坏簇标记就记录在它所对应的FAT表项中。

· 由于簇号起始于2, 所以FAT表的0号表项与1号表项不予任何簇对应。 FAT32 的0号表项值总是“F8FFFF0F”

注意:可以搜索扇区偏移0字节处的该值(F8FFF0F)以查找FAT表。

· 1号表项可能被用于记录“脏标志”, 以说明文件系统没有被正常卸载或者磁盘表面存在错误。 不过此值似乎不重要,正常情况下,1号表项值“FFFFFFFF”或“FFFFFF0F”

项内容填写规则:

·在文件系统中新建文件时,如果新建的文件只有一个簇,为其分配的簇所对应的FAT表项将会被写入结束标记。如果新建的文件不只占用一个簇,则在其所占用的每个簇对应的FAT表项中写入为其分配的下一个簇的簇号,在最后一个簇对应的FAT 表项中写入结束标记。

· 新建目录时,只为其分配一个簇的空间,对应的FAT 表项中写入结束标记。当目录增大超过一个簇的大小时,将会在空闲空间中继续为其分配一个簇,并在FAT 表中为其建立FAT 表链以描述它所占用的簇的情况。

· 对文件或目录进行删除操作时,他们所对应的FAT 表项将会被清空,设置为0以表示其所对应的簇处于未分配的状态。

三、 FAT表的使用

一个文件的“起始簇号”记录在它的目录项中, 该文件的 "其他簇" 则用一个簇连结构记录在FAT 表中。

如果一个簇所对应的FAT表项的表项值为非零, 则表明该簇已经被分配使用了,但是这时表项值可能为两种情况,一个是一个文件的下一个簇号值,也有可能是一个文件的结束标记0x0FFFFFFF,或者是一个坏簇标记0x0FFFFFF7 。

如果要寻找一个文件的下一个簇,只需要查看该文件的目录项中描述的起始簇号所对应的FAT项,如果该文件只有一个簇,则此处的值为一个结束标记0x0FFFFFFF;如果该文件不只一个簇,则此处 的值是它的下一个簇的簇号。

>> 查询簇连接结构模拟步骤:

当我们要寻找某个文件时, 首先从该文件的“目录项”中获取该文件的第一“簇”的簇号,然后根据“第一簇”的“簇号 N”,然后根据N 从FAT区的FAT表找出N簇号所对应的FAT 表项,查看FAT 表项的内容:

若是文件结束,该表项值为 0x0FFFFFFF; 若是没有结束,而该文件的大小超出一个簇,则N所对应的FAT表项的表项值为该文件下一个簇的簇号,然后再找到下一个簇号N+1 所对应的FAT表项,查看其表项值,依次类推,就推出了一个文件在FAT表中的 簇链连接结构; 也或者表项值为 0xFFFFFFF7 坏簇标识。

>> 其他

查找FAT 表项:要找到一个簇的FAT 表项,只要用他的 簇号乘以每个FAT 表项的字节数即可 。对于FAT32而言,每个FAT 表项占用4个四字节, 如果我们寻找9号簇的表项位置,则用 4*9=36,也就是说位于FAT 表内偏移36(0x24)字节处。

注意:

WinHex 提供了直接跳转到某个指定FAT 表项的功能。

文件系统大小的上限值却绝育FAT 项的大小。 簇链中的每个FAT 项记录着下一个簇的簇地址,FAT 项所能表示的数字有一个上限,这个上限也就是文件系统中的最大簇号。 FAT 32文件系统的FAT 项只使用了32bit 中的28bit ,因此只能描述 268435456个簇(实际上还要考略小于这个值,因为这其中还包含了结束标志及坏簇标志的保留值)。

操作系统通过检测FAT 表中的表项来确定文件系统中的各个簇是否被分配使用。当我们在Windows 下右击某个FAT 分区查看其属性时,显示的已用空间和未用空间就是根据FAT 表统计而来的。

有时我们会遇到,查看属性时发现已用空间并没有减少,但存储的文件却不见了。这是因为某些病毒在某些文件的目录项中写入了删除标记,但并没有清楚FAT 表内的簇链所至。

FAT32文件系统的存储组织结构

对磁盘的物理结构,逻辑结构和存储结构有了比较深入的了解后,我们来仔细探讨FAT32文件系统的存储组织结构。说到文件系统的组织结构,我们应该马上意识到,这指的是文件系统在同一个分区内的组织结构,在这个话题上,我们完全可以不管分区之外的所有事情。

为了分析FAT32文件系统的存储组织结构,我们来建立一个实实在在的文件系统:将U盘插入电脑,将U盘格式化成FAT32分区格式:

格式化U盘

以建好的U盘FAT32文件系统为基础,下面从文件系统的各个组成来分别加以介绍。

分区引导扇区DBR

用winhex打开U盘显示如下:

分区引导DBR

这是FAT32分区引导记录,定义如下:

偏移00H: 3字节的 跳转指令  EB 58 90,跳过下面的BPB和扩展BPB部分

偏移03H:8字节的硬盘分区类型文本字符名:4D 53 44 4F 53 35 2E 30 即:MSDOS5.0

偏移0BH: 25字节的分区参数块(BPB),细分如下:

偏移0BH:扇区字节数:00 02 即0X0200,512字节

偏移0DH:每簇扇区数:08即每簇包括8个扇区

偏移0EH:保留扇区数:24 00即保留36个扇区

偏移10H:FAT表份数:02即两个FAT表

偏移11H:未用:00 00

偏移13H:未用:00 00

偏移15H:介质类型:F8即本地硬盘

偏移16H:未用:00 00

偏移18H:每磁道扇区数:3F 00 即每磁道63扇区 

偏移1AH:磁头数:FF 00即255个磁头

偏移1CH:隐藏扇区数:80 1F即8064个隐藏扇区

偏移20H:磁盘总扇区数 80 F0 77 00即总共7860352个扇区(7860352*512=4024500224,因为我的U盘是4G)

偏移24H:52字节的扩展分区参数块(扩展BPB),细分如下:

偏移24H:FAT表占用扇区数:EE 1D  00 00即FAT表占7662个扇区

偏移28H:未用:00 00 00 00

偏移2CH:根目录入口簇号:02 00 00 00即根目录从02号簇开始

偏移30H:文件系统信息扇区号:01 00即扇区1

偏移32H:备份引导扇区的位置 06 00即6号扇区(第7个扇区),从WINHEX中我们也可以看到,6号扇区的内容和0号引导扇区内容是一样的

偏移34H:未用:00 00 00 00 00 00 00 00 00 00 00 00

偏移40H:物理磁盘号:00

偏移41H:未用:00

偏移42H:扩展引导标志 29即0X29

偏移43H:磁盘序列号F1 2A 27 04通常为一随机数

偏移47H:卷标ASCII 4E 4F 20 4E 41 4D 45 20 20 20 20 即NO NAME

偏移52H:文件系统格式ASCII  46 41 54 33 32 20 20 20即FAT32

偏移5AH:分区引导代码 420字节:

:33C98ED1BCF47B8EC18ED9BD007C884E028A5640B408CD137305B9FFFF8AF166

0FB6C640660FB6D180E23FF7E286CDC0ED0641660FB7C966F7E1668946F8837E1

6007538837E2A007732668B461C6683C00CBB0080B90100E82B00E94803A0FA7DB

47D8BF0AC84C074173CFF7409B40EBB0700CD10EBEEA0FB7DEBE5A0F97DEBE0

98CD16CD196660663B46F80F824A00666A0066500653666810000100807E02000F8

52000B441BBAA558A5640CD130F821C0081FB55AA0F851400F6C1010F840D00FE4

602B4428A56408BF4CD13B0F96658665866586658EB2A6633D2660FB74E1866F7F1

FEC28ACA668BD066C1EA10F7761A86D68A56408AE8C0E4060ACCB80102CD13666

10F8254FF81C300026640490F8571FFC34E544C445220202020202000000000000000

00000000000000000000000000000000000000000000000000000000000000000000000

00000000000000D0A52656D6F7665206469736B73206F72206F74686572206D656469

612EFF0D0A4469736B206572726F72FF0D0A507265737320616E79206B657920746F2

0726573746172740D0A0000000000ACCBD80000

偏移1FEH:有效扇区结束标志 55 AA

到此分区引导扇区介绍结束。

文件分配表FAT

简介:

FAT表(文件分配表),是FAT文件系统中用于磁盘数据索引和定位而引进的一种链式结构。在FAT文件系统中,文件的存储依照FAT表制定的簇链式数据结构来进行。同时,FAT文件系统将组织数据时使用的目录也抽象为文件,以简化对数据的管理。

FAT1表位置的定位:

在我们前面介绍分区引导记录的时候提到,在偏移0EH处存储了保留扇区的个数,这个保留扇区数指的就是当前分区内DBR到FAT表之间的所有扇区的个数(包括DBR但不包括FAT表)。因此,我们可以定位FAT表所在的起始偏移位置了,即24H*200H=4800H。我们贴出4800H处得部分内容如下:

FAT1

显然没有错,这就是我们FAT1所存储的位置,只是当前没有存储文件,所以FAT比较简单罢了。

FAT2表位置的定位:

在我们前面介绍分区引导记录的时候提到,在偏移24H处存储了FAT表所占用的扇区个数,我们又知道FAT2是紧邻FAT1的,所以可以很容易得到FAT2的存储位置的偏移地址:FAT1的起始偏移地址+FAT1的大小=4800H+1DEEH*200H=3C2400H,我们贴出3C2400H处的部分内容如下:

FAT2

显然没有错,这就是我们FAT2所存储的位置,内容与FAT1相同。

FAT表的特性:

FAT表由一系列大小相等的FAT表项组成,它有如下特性:

FAT32中每个簇的簇地址,使用32bit(4个字节)记录在FAT表中。FAT表中的所有字节位置以4个字节为单位进行划分,并对所有划分后的位置由0进行地址编号。0 号地址与1号地址被系统保留并存储特殊标志内容。从2号地址开始,每个地址对应于数据区的簇号,FAT表中的地址编号与数据区中的簇号相同。我们称FAT中的这些地址为FAT表项,FAT表项中记录的值称为FAT表项值。

当文件系统被创建,也就是进行格式化操作时,分配给FAT区域的空间将会被清空,在FAT1与FAT2的0号表项与1号表项写入特定值。由于创建文件系统的同时也会创建根目录,也就是为根目录分配了一个簇空间,通常为2号簇,所以2号簇所对应的2号FAT表项也会被写入一个结束标记。

如果某个簇未被分配使用,它所对应的FAT表项内的FAT表项值即用0进行填充,表示该FAT表项所对应的簇未分配使用。

当某个簇已被分配使用时,则它对应的FAT表项值也就是该文件的下一个存储位置的簇号。如果该文件结束于该簇,则在它的FAT表项中记录的是一个文件结束标记,对于FAT32而言,代表文件结束的FAT表项值为0x0FFFFFFF。

如果某个簇存在坏扇区,则整个簇会用FAT表项值0x0FFFFFF7标记为坏簇,不再使用,这个坏簇标记就记录在它所对应的FAT表项中。

由于簇号起始于2,所以FAT表的0号表项与1号表项不与任何簇对应。FAT32的0号表项值总是“F8FFFF0F”。1号表项可能被用于记录脏标志,以说明文件系统没有被正常卸载或者磁盘表面存在错误。不过此值似乎并不重要,因此我们只要了解就可以。正常情况下,1号表项值为“FFFFFFFF”或“FFFFFF0F"。

在文件系统中新建文件时,如果新建的文件只占用一个簇,为其分配的簇所对应的FAT表项将会被写入结束标记。如果新建的文件不只占用一个簇,则在其所占用的每个簇对应的FAT表项中写入为其分配的下一簇的簇号,在最后一个簇对应的FAT表项中写入结束标记。

新建目录时,只为其分配一个簇的空间,对应的FAT表项中写入结束标记。当目录增大超出一个簇的大小时,将会在空闲空间中继续为其分配一个簇,并在FAT表中为其建立FAT表链以描述它所占用的簇情况。

对文件或目录进行删除操作时,它们所对应的FAT表项将会被清空,设置为0以表示其所对应的簇处于未分配状态。

根目录区

简介:

在FAT32文件系统中,根目录的位置不再硬性地固定,可以存储在分区内可寻址的任意簇内,不过通常根目录是最早建立的(格式化就生成了)目录表。所以,我们看到的情况基本上都是根目录首簇紧邻FAT2,占簇区顺序上的第1个簇(即2号簇)。同时,FAT32文件系统将根目录当做普通的数据文件来看,所有没有了目录项数的限制,在需要的时候可以分配空簇,存储更多的目录项。

起始偏移地址定位:

根目录起始扇区=保留扇区数+FAT×2+(起始簇-2)x每簇的扇区数,在我们前面介绍分区引导记录的时候提到,偏移2CH处保存了根目录起始簇号是2,所以求得根目录起始扇区是24H+1DEEH*2H+(2-2)*8H=3C00H,即求得偏移地址3C00H*200H=780000H,我们贴出780000H处的部分内容如下:

780000H

目录区的一个目录项占用32个字节,可以是长文件名目录项、文件目录项、子目录项等。

短文件名格式的目录项

对于短文件名格式的目录项。其参数意义如下:

短文件名

根据参数定义,我们来分析一下上图的目录项 54 45 53 54 5F 46 41 54 33 32 20 08 00 00 00 00 00 00 00 00 00 00 19 95 10 3F 00 00 00 00 00 00。其中起始11字节54 45 53 54 5F 46 41 54 33 32 20 是卷标TEST_FAT32;第12字节08指示当前目录项保存的是卷标;第23-24字节19 95即9519H,是最近修改时间:19点40分50秒;第25-26字节10 3F即3F10H,是最近修改日期:2011年8月16日;

长文件名格式的目录项

FAT32的一个重要的特点是完全支持长文件名。长文件名依然是记录在目录项中的。为了低版本的OS或程序能正确读取长文件名文件,系统自动为所有长文件名文件创建了一个对应的短文件名,使对应数据既可以用长文件名寻址,也可以用短文件名寻址。不支持长文件名的OS或程序会忽略它认为不合法的长文件名字段,而支持长文件名的OS或程序则会以长文件名为显式项来记录和编辑,并隐藏起短文件名。

当创建一个长文件名文件时,系统会自动加上对应的短文件名,其原则如下:

(1)、取长文件名的前6个字符加上"~1"形成短文件名,扩展名不变。

(2)、如果已存在这个文件名,则符号"~"后的数字递增,直到5。

长文件名的实现有赖于目录项第12字节属性字节,当此字节的值为0FH时,支持长文件名的系统会将其当做长文件名的依据,而只支持短文件名的系统会认为是异常而忽略掉。系统将长文件名以13个字符为单位进行切割,每一组占据一个目录项。所以可能一个文件需要多个目录项,这时长文件名的各个目录项按倒序排列在目录表中,以防与其他文件名混淆。

长文件名中的字符采用unicode形式编码,每个字符占据2字节的空间。其目录项定义如:

长文件名

下面是我建立的长文件名文件夹abcdefghijklmnopqrstuvwxyz1234567890的目录项:

长目录名

前面已经基于一个格式化的空U盘分析了一下FAT32文件系统存储的组织结构,下面我们从文件操作的角度来分析一下文件系统的运作机制。由于换了个U盘,所以仍然贴出刚格式化的空U盘的几个重要的数据区如下:

DBR

FDT_DN

根目录_DN

我们可以看出,在分区格式化的时候,系统将卷标TEST_FAT32存储在2号簇,即跟目录区,如上面根目录贴图所示。同时,在FDT区2号簇标记位置写入了文件结束符FF FF FF 0F。显然,FAT32文件系统将目录当做普通文件来处理的。

下面我们在根目录下新建一个文件夹TEST1,看会有什么变化:

建立了TEST1文件夹后,FDT变成如下:

FDT_TEST1

根目录变成如下:

根目录2

重新分配了3号簇:

3号簇_NEW

从上面的变化可以直观的看出,系统在新建文件夹时完成了如下动作:

a.在父目录所在簇上建立新的目录项,存储当前所建文件夹信息。

b.分配一个新簇,给新建的文件夹建立两个目录项:父目录和当前目录。

c.在FDT表中新分配的簇对应的位置上写下文件结束符。

d.建立各部分的链路关系:新建文件夹所对应的目录项的文件起始簇号字段写上新分配簇的簇号,新簇上的两个目录项的文件起始簇号字段分配写上父目录所在簇号(此处是0,本来我以为是2,即根目录所在簇,不知道为什么,可能特地用0指示根目录吧)和当前簇号(此处是3)。

为了验证我们上面分析的正确性,我们再在TEST1文件夹下建立新文件夹TEST11,看是否做了如下操作:

a.在父目录(即TEST1)所在簇(即3号簇)上建立新的目录项,存储TEST11文件夹信息。

b.分配一个新簇(应该是4号簇),给新建的文件夹(即TEST11)建立两个目录项:父目录和当前目录。

c.在FDT表中新分配的簇(应该是4号簇)对应的位置上写下文件结束符。

d.建立各部分的链路关系:新建文件夹(即TEST11)所对应的目录项的文件起始簇号字段写上新分配簇的簇号(应该是4号簇),,新簇上的两个目录项的文件起始簇号字段分配写上父目录所在簇号(3号簇)和当前簇号(应该是4号簇)。

新建TEST11文件夹后FDT变成:

FDT_TEST11

根目录没有变化:

根目录2

3号簇变成:

3号簇

新分配4号簇:

4号簇

显然我们的估计没有错的,也进一步证明我们前面的分析是正确的。

下面我们再分析建立文件的情况

我们先建立一个100字节的文件TEST.TXT,然后把这个文件拷贝到U盘的根目录下,FDT变成如下:

FDT

根目录变成:

根目录

新分配5号簇保存文件内容:

5号簇

从上面的变化可以直观的看出,系统新建文件和新建文件夹所完成的操作是一样一样的:

a.在父目录所在簇上建立新的目录项,存储当前所建文件信息。

b.分配一个新簇,存储新建的文件的内容。

c.在FDT表中新分配的簇对应的位置上写下文件结束符。

d.建立链路关系:新建文件所对应的目录项的文件起始簇号字段写上新分配簇的簇号。

结束总结:

1.在FAT32文件系统中,目录和文件的存储采用统一的方式。

2.文件系统的操作的单位是簇,每新建立一个文件或文件夹,至少会重新分配一个簇号。

3.如果一个文件或目录的内容要多个簇才能存储得下,则系统会分配多个簇来存储文件或目录的内容

4.当需要多个簇时,这些簇可能连续也可能不连续,但无论是连续或是不连续,系统都是采用FDT链表的形式来组织的。

FAT32系统中长文件名的存储

FAT32的一个重要的特点是完全支持长文件名。长文件名依然是记录在目录项中的。

为了低版本的OS或程序能正确读取长文件名文件,系统自动为所有长文件名文件创建了一个对应的短文件名,使对应数据既可以用长文件名寻址,也可以用短文件名寻址。不支持长文件名的OS或程序会忽略它认为不合法的长文件名字段,而支持长文件名的OS或程序则会以长文件名为显式项来记录和编辑,并隐藏起短文件名。

当创建一个长文件名文件时,系统会自动加上对应的短文件名,其一般有的原则:

(1)、取长文件名的前6个字符加上"~1"形成短文件名,扩展名不变。

(2)、如果已存在这个文件名,则符号"~"后的数字递增,直到5。

(3)、如果文件名中"~"后面的数字达到5,则短文件名只使用长文件名的前两个字母。通过数学操纵长文件名的剩余字母生成短文件名的后四个字母,然后加后缀"~1"直到最后(如果有必要,或是其他数字以避免重复的文件名)。

(4)、如果存在老OS或程序无法读取的字符,换以"_"

短文件格式的目录项。其参数意义见表14:

表14 FAT32短文件目录项32个字节的表示定义
字节偏移(16进制)字节数定义
0x0~0x78文件名
0x8~0xA3扩展名
0xB*1属性字节00000000(读写)
00000001(只读)
00000010(隐藏)
00000100(系统)
00001000(卷标)
00010000(子目录)
00100000(归档)
0xC1系统保留
0xD1创建时间的10毫秒位
0xE~0xF2文件创建时间
0x10~0x112文件创建日期
0x12~0x132文件最后访问日期
0x14~0x152文件起始簇号的高16位
0x16~0x172文件的最近修改时间
0x18~0x192文件的最近修改日期
0x1A~0x1B2文件起始簇号的低16位
0x1C~0x1F4表示文件的长度

* 此字段在短文件目录项中不可取值0FH,如果设值为0FH,目录段为长文件名目录段

长文件名的实现有赖于目录项偏移为0xB的属性字节,当此字节的属性为:只读、隐藏、系统、卷标,即其值为0FH时,DOS和WIN32会认为其不合法而忽略其存在。这正是长文件名存在的依据。

将目录项的0xB置为0F,其他就任由系统定义了,Windows9x或Windows 2000、XP通常支持不超过255个字符的长文件名。

系统将长文件名以13个字符为单位进行切割,每一组占据一个目录项。所以可能一个文件需要多个目录项,这时长文件名的各个目录项按倒序排列在目录表中,以防与其他文件名混淆。

长文件名中的字符采用unicode形式编码(一个巨大的进步哦),每个字符占据2字节的空间。其目录项定义如表15。

表15 FAT32长文件目录项32个字节的表示定义
字节偏移(16进制)字节数定义
0x01属性字节位意义7保留未用
61表示长文件最后一个目录项
5保留未用
4顺序号数值
3
2
1
0
0x1~0xA10长文件名unicode码①
0xB1长文件名目录项标志,取值0FH
0xC1系统保留
0xD1校验值(根据短文件名计算得出)
0xE~0x1912长文件名unicode码②
0x1A~0x1B2文件起始簇号(目前常置0)
0x1C~0x1F4长文件名unicode码③

系统在存储长文件名时,总是先按倒序填充长文件名目录项,然后紧跟其对应的短文件名。从表15可以看出,长文件名中并不存储对应文件的文件开始簇、文件大小、各种时间和日期属性。文件的这些属性还是存放在短文件名目录项中,一个长文件名总是和其相应的短文件名一一对应,短文件名没有了长文件名还可以读,但长文件名如果没有对应的短文件名,不管什么系统都将忽略其存在。所以短文件名是至关重要的。

在不支持长文件名的环境中对短文件名中的文件名和扩展名字段作更改(包括删除,因为删除是对首字符改写E5H),都会使长文件名形同虚设。

(长文件名如何与短文件名对应?仅靠她们之间的位置关系?)

长文件名和短文件名之间的联系光靠他们之间的位置关系维系显然远远不够。其实,长文件名的0xD字节的校验和起很重要的作用,此校验和是用短文件名的11个字符通过一种运算方式来得到的。系统根据相应的算法来确定相应的长文件名和短文件名是否匹配。这个算法不太容易用公式说明,我们用一段c程序来加以说明。

假设文件名11个字符组成字符串shortname[],校验和用chknum表示。得到过程如下:

int i,j,chknum=0;

for (i=11; i>0; i--)

chksum = ((chksum & 1) ? 0x80 : 0) + (chksum >> 1) + shortname[j++];

如果通过短文件名计算出来的校验和与长文件名中的0xD偏移处数据不相等。系统无论如何都不会将它们配对的。

依据长文件名和短文件名对目录项的定义,加上对簇的编号和链接,FAT32上数据的读取便游刃有余了。

本文出自数据恢复网(www.),疏漏在所难免,希望指正。若需转载请保留此信息;若需修改,请用以下方式与作者取得联系

1、http://www.

2、zymail@vip.sina.com

3、sjhf@

FAT文件系统总结

MBR:Master Boot Record (主引导记录)

DBR:DOS Boot Record (DOS引导记录,位于分区引导扇区)

BPB:BIOS Parameter Block (BIOS参数块)

FAT:File Allocation Table (文件分配表)

Sector:扇区

Cluster:簇

一、硬盘组织结构

下面是一个包含 4 个分区的硬盘结构示意图,其中分为 3 个基本分区和一个扩展分区。

FAT文件系统总结(一)

二、FAT文件系统结构

FAT 文件系统是由按照如下顺序排列的几个部分组成的:

0 – Reserved Region

1 – FAT Region

2 – Root Directory Region (FAT32没有这部分)

3 – File and Directory Data Region

FAT文件系统总结(一) 

FAT 系统的数据存储采用小端(Little Endian)方式,注意到这一点很重要,在使用大 端(Big Endian)的系统中,读取多字节数据的时候必须要经过转换,否则,读取到的数据是不正确的。

例如:一个 32-bit 数据 0x12345678 在 FAT 中的保存方式如下图所示:

FAT文件系统总结(一)

三、主引导扇区

硬盘主引导扇区 = 硬盘主引导记录(MBR)+硬盘分区表(DPT) 

MBR:扇区内偏移地址 0 ~ 0x1BD

DPT:扇区内偏移地址 0x1BE ~ 0x1FD,其中又分为 4 个分区表:

第一个分区表:0x1BE ~ 0x1CD

第二个分区表:0x1CE ~ 0x1DD

第三个分区表:0x1DE ~ 0x1ED

第四个分区表:0x1EE ~ 0x1FD

每个分区表的信息如下表所示:

分区表信息

字节位移

字段长度

字段名和定义

0x00

BYTE

引导指示符(Boot Indicator),指明该分区是否是活动分区,0x80=活动分区,0x00=非活动分区

0x01

BYTE

开始磁头(Starting Head)

0x02

6Bits

开始扇区(Starting Sector),只用了0~5位。后面的两位(第6位和第7位)被开始柱面字段所使用开始柱面(Starting Cylinder),除了开始扇区

0x03

10Bits

字段的最后两位外,还使用了1位来组成该柱面值。开始柱面是一个10位数,最大值为1023

0x04

BYTE

系统ID(System ID),定义了分区的类型,详见下表

0x05

BYTE

结束磁头(Ending Head)

0x06

6Bits

结束扇区(Ending Sector),只使用了0~5位。最后两位(第6、7位)被结束柱面字段所使用

0x07

10Bits

结束柱面(Ending Cylinder),除了结束扇区字段最后的两位外,还使用了1位,以组成该柱面值。结束柱面是一个10位的数,最大值为1023

0x08

DWORD

相对扇区数(Relative Sectors),从该磁盘的开始到该分区的开始的位移量,以扇区来计算

0x0C

DWORD

总扇区数(Total Sectors),该分区中的扇区总数

分区标志类型值及其含义

类型值(HEX)

含义

类型值(HEX)

含义

0

空。DOS或windows不允许使用,视为非法

5C

Priam Edisk

1

FAT12

61

Speed Stor

2

XENIX root

63

GNU HURD or Sys

3

XENIX usr

64

Novell Netware

6

FAT16分区小于32M时为0x04

65

Novell Netware

7

HPFS / NTFS

70

Disk Secure Mult

8

AIX

75

PC/IX

9

AIX bootable

80

Old Minix

0A

OS/2 Boot Manage

81

Minix/Old Linux

0B

Win95 FAT32

82

Linux swap

0C

Win95 FAT32

83

Linux

0E

Win95 FAT16

84

0s/2 hidden C:

0F

Win95 Extended(大于 8GB)

85

Linux extended

10

OPUS

86

NTFS volume set

11

Hidden FAT12

87

NTFS volume set

12

Compaq diagmost

93

Amoeba

14

Hidden FAT16<32MB

94

Amoeba BBT

16

HiddenFAT16

A0

IBM Thinkpad hidden

17

Hidden HPFS/NTFS

A5

BSD/386

18

AST Windows swap

A6

Open BSD

1B

Hidden FAT32

A7

NextSTEP

1C

Hidden FAT32 partition

B7

BSDI fs


(using LBA-mode INT 13 extensions)B8BSDI swap

1E

Hidden LBA VFAT partition

BE

Solaris boot partition

24

NEC DOS

C0

DR-DOS/Novell DOS secured partition

3C

Partition Magic

C1

DRDOS/sec

40

Venix 80286

C4

DRDOS/sec

41

PPC Perp Boot

C6

DRDOS/sec

42

NTFS动态分区

C7

Syrinx

4D

QNX4.x

DB

CP/M/CTOS

4E

QNX4.x 2nd part

E1

DOS access

4F

QNX4.x 3rd part

E3

DOS r/0

50

OnTrack DM

E4

Speedstor

51

OnTrack DM6 Aux

EB

BeoS fs

52

CP/M

F1

SpeedStor

53

OnTrack DM6 Aux

F2

DOS 3.3+secondary partition

54

OnTrack DM6

F4

SpeedStor

55

EZ-Drive

FE

LAN step

56

Golden Bow

FF

BBT

FAT文件系统总结(一)

主引导扇区

上面是从一张 SD 卡读到的主引导扇区信息。可以看出,MBR 区域数据全部为 0,这张 SD 卡只有一个分区,这个分区前的扇区数为 0x0000003F,所以这个分区的开始位置就是扇区0x0000003F,总扇区数为 0x000F1EC1(990913 个扇区)。

四、分区引导扇区

也常常称为启动扇区,Microsoft称它为 0 扇区(0th sector),通过前面的介绍我们知 道,称它为 0 扇区其实是不正确的,这样容易让人误解它为磁盘的最前面一个扇区,称它为 0 扇区只是表明它是FAT中扇区的参考点而已。

FAT文件系统总结(二)

该扇区中包含有我们关注的一个重要数据结构 BPB(BIOS Parameter Block)。以下表 格内容翻译自 Microsoft 的《Microsoft Extensible Firmware Initiative FAT32 File System Specification—version1.03》,其中包含 BPB 各项的描述。

NOTE:在以下的叙述中,名字以 BPB_开头的属于 BPB 部分,以 BS 开头的属于启动扇区(Boot Sector)部分,实际上并不属于BPB。


offset

长度

描述

BS_jmpBoot

0x00

3

跳转指令,指向启动代码

BS_OEMName

0x03

8

建议值为“MSWIN4.1”。有些厂商的 FAT 驱动可能会检测此项,所以设为“MSWIN4.1”可以尽量避免兼容性的问题

BPB_BytsPerSec

0x0b

2

每扇区的字节数,取值只能是以下几种:512,1024,2048 或是 4096。设为 512 会取得最好的兼容性,目前有很多 FAT 代码都是硬性规定每扇区的字节数为 512,而不是实际的检测此值。但微软的操作系统能够很好支持

BPB_SecPerClus

0x0d

1

1024,2048 或是 4096每簇的扇区数,其值必须中 2 的整数次方(该整数必须>=0),同时还要保证每簇的字节数不能超过 32K,也就是 1024*32 字节

BPB_RsvdSecCnt

0x0e

2

保留扇区的数目,此域不能为0,FAT12/FAT16 必须为1,FAT32 的典型值取为 32,,微软的系统支持任何非 0值

BPB_BumFATs

0x10

1

分区中 FAT 表的份数,,任何 FAT 格式都建议为 2

BPB_RootEntCnt

0x11

2

对于 FAT12 和 FAT16 此域包含根目录中目录的个数(每项长度为 32bytes),对于 FAT32,此项必须为 0。对于 FAT12 和 FAT16 , 此 数 乘 以 32 必 为BPB_BytesPerSec 的偶数倍,为了达到更好的兼容性,FAT12 和 FAT16 都应该取值为 512

BPB_ToSec16

0x13

2

早期版本中 16bit 的总扇区,这里总扇区数包括 FAT 卷上四个基本分区的全部扇区,此域可以为 0,若此域为 0,那么 BPB_ToSec32 必须为 0,对于 FAT32,此域必为 0。对于 FAT12/FAT16,此域填写总扇区数,如果该值小于0x10000 的话,BPB_ToSec32 必须为 0

BPB_Media

0x15

1

对于“固定”(不可移动)存储介质而言,0xF8 是标准值,对于可移动存储介质,经常使用的数值是 0xF0,此域合法的取值可以取 0xF0,0xF8,0xF9,0xFA,0xFC,0xFD,0xFE,0xFF。另外要提醒的是,无论此域写入什么数值,同时也必须在 FAT[0]的低字节写入相同的值,这是因为早期的 MSDOS 1.x 使用该字节来判定是何种存储介质

BPB_FATSz16

0x16

2

FAT12/FAT16 一个 FAT 表所占的扇区数,对于 FAT32来说此域必须为 0,在 BPB_FATZ32 中有指定 FAT 表的大小

BPB_SecPerTrk

0x18

2

每磁道的扇区数,用于 BIOS 中断 0x13,此域只对于有“特殊形状”(由磁头和柱面每分割为若干磁道)的存储介质有效,同时必须可以调用 BIOS 的 0x13 中断得到此数值

BPB_NumHeads

0x1A

2

磁头数,用于 BIOS 的 0x13 中断,类似于上面的 BPB_SecPerTrk,只对特殊的介质才有效,此域包含一个至少为 1 的数值,比如 1,4M 的软盘此域为 2

BPB_HidSec

0x1C

4

在此 FAT 分区之前所隐藏的扇区数,必须使得调用 BIOS的 0x13 中断可以得到此数值,对于那些没有分区的存储介质,此域必须为 0,具体使用什么值由操作系统决定

BPB_ToSec32

0x20

4

该卷总扇区数(32bit),这里的扇区总数包括 FAT 卷四个个基本分的全部扇区,此域可以为 0,若此域为 0,BPB_ToSec16 必须为非 0,对 FAT32,此域必须是非 0。对于 FAT12/FAT16 如果总扇区数大于或等于 0x10000的话,此域就是扇区总数,同时 BPB_ToSec16 的值为 0。

FAT32 的 BPB 的内容和 FAT12/16 的内容在地址 0x36 以前是完全一样的,从偏移量 0x36开始,他们的内容有所区别,具体的内容要看 FAT 类型为 FAT12/16 还是 FAT32,这点保证了 在启动扇区中包含一个完整的 FAT12/16 或 FAT32 的 BPB 的内容,这么做是为了达到最好的兼容性,同时也为了保证所有的 FAT 文件系统驱动程序能正确的识别和驱动不同的 FAT 格式,并 让他们良好地工作,因为他们包含了现有的全部内容从 offset 36 开始 FAT12/FAT16 的内容开始区别于 FAT32,下面分两个表格列出,下 表为 FAT12/FAT16 的内容下表为 FAT32 的内容

名称

offset

长度

描述

BPB_FATSz32

0x24

4

一个 FAT 表所占的扇区数,此域为 FAT32 特有,同时BPB_FATSz16 必须为 0

BPB_Flags

0x28

2

此域 FAT32 特有。Bits0-3:不小于 0 的 FAT(active FAT)数目,只有在镜像(mirrorig)禁止时才有效。

Bits 4-6: 保留

Bits 7: 0 表示 FAT 实时镜像到所有的 FAT 表中1 表示只有一个活动的 FAT 表。这个表就是Bits0-3 所指定的那个

Bits8-15: 保留

BPB_FSVer

0x2A

2

此域为 FAT32 特有,高位为 FAT32 的主版本号,低位为次版本号,这个版本号是为了以后更高级的 FAT 版本考虑,假设当前的操作系统只能支持的 FAT32 版本号为 0.0。那么该操作系统检测到此域不为 0 时,它便会忽略 FAT 卷,因为它的版本号比系统能支持的版式本要高

BPB_RootClus

0x2C

4

根目录所在第一个簇的簇号,通常该数值为 2,但不是必须为 2

磁盘工具在改变根目录位置时,必须想办法让磁盘上第一个非坏簇作为根目录的第一个簇(比如第 2 簇,除非它已经被标记为坏簇),这样的话,如果此域正好为 0 的话磁盘检测工具也能轻松的找到根目录所在簇的位置

BPB_FSIfo

0x30

2

保留区中 FAT32 卷 FSINFO 结构所占的扇区数,通常为1

在 Backup Boot 中会有一个 FSINFO 的备份,但该备份只是更新其中的指针,也就是说无论是主引导记录还是备份引导记录都是指向同一个 FSINFO 结构

BPB__BkBootSec

0x32

2

如果不为 0,表示在保留区中引导记录的备数据所占的扇区数,通常为 6。同时不建议使用 6 以外的其他数值

BPB_Reserved

0x34

12

用于以后 FAT 扩展使用,对 FAT32。此域用 0 填充

BS_DrvNum

0x40

1

与 FAT12/16 的定义相同,只不过两者位于启动扇区不同的位置而已

BS_Reserved1

0x41

1

与 FAT12/16 的定义相同,只不过两者位于启动扇区不同的位置而已

BS_BootSig

0x42

1

与 FAT12/16 的定义相同,只不过两者位于启动扇区不同的位置而已

BS_VolID

0x43

4

与 FAT12/16 的定义相同,只不过两者位于启动扇区不同的位置而已

BS_FilSysType

0x47

11

与 FAT12/16 的定义相同,只不过两者位于启动扇区不同的位置而已

BS_FilSysType

0x52

8

通常设置为“FAT32”,请参照 FAT12/16 此部分的陈述。

关于 FAT 启动扇区还有一点重要的说明,我们假设里面的内容是按字节排序的,那么扇区[510]的内容一定 0x55,扇区[511]的内容一定是 0xAA很多 FAT 资数文档会把 0xAA55 说成是“启动扇区最后两字节的内容”,这样的说法是正 确的,但仅仅适用于 BPB_BytsPerSec 值为 512 的情况。若 BPB_BytsSec 的值大于 512,该标记的位置并没有改变,虽然在启动扇区的最后两个字节写 0xAA55 并没有问题。

五、FAT类型识别

FAT 的字类型(FAT12/16/32)只能通过 FAT 卷中的簇(Cluster)数来判定,没有其 他的办法。

Cluster 总数的计算:RootDirSectors = (BPB_RootEntCnt*32) /BPB_BytsPerSec DataSect = TotSec – (BPB_RsvdSecCnt +(BPB_NumFATs * FATSz) + RootDirSectors) CountofClusters = DataSec / BPB_SecPerClus

If(CountofClusters < 4085) { /*卷类型是 FAT12 */

} else if(CountofClusters < 65525) { /* 卷类型是 FAT16 */

} else {

/* 卷类型是 FAT32 */ 

}

注意这里的簇数(count of Cluster)是指数据区所占簇的数量(the count of the data cluster),从簇 2 算起,而“最大可用簇数”(Maximun valid cluster number for the volume)是 CountofClusters +1,“包括保留簇的簇数”(count of cluster including the two reserved cluster)则为CountofClusters +2。根目录占据的 Sector 数:RootDirSectors = (BPB_RootEntCnt*32) /BPB_BytsPerSec   数据区(Cluster 2)的起始 Sector: FirstDataSector = BPB_EsvdSecCnt  +    (BPB_NumFATs * FATSz)   +     RootDirSectors    给一个合法的簇号 N,该簇的第一个扇区号由下式计算:FirstSectorofCluster =((N -2) * BPB_SecPerClust) + FirstDataSecot; 因为 BPB_SecPerClus 总是 2 的整数次方,这意味着 BPB_SecPerSlus 的乘法运算可 以通过移动来进行。

NOTE:这里所说的 Sector 号,指的是针对卷中包 BPB 的第一个扇区(DBR)的偏移量(DBR设为Sector 0)。

六、FAT各部分位置的计算

FAT文件系统总结(二)

DBR_Base:从DPT中偏移8Bytes的地址读取4Bytes数据就可以直接看作是DBR所在的 Sector。

RsvSectors = BPB_RsvdSecCnt。

FAT_Base = DBR_Base + RsvSectors FATSize = BPB_NumFATs * FATSz  

其中,FATSz = (FAT32?) BPB_FATSz32 : BPB_FATSz16 Root_Base:如果是FAT12/FAT16,则Root_Base = FAT_Base + FATSize

如果是FAT32,则Root_Base = BPB_RootClus RootSize :如果是FAT32,则没有限制;否则RootSize = (BPB_RootEntCnt*32) / BPB_BytsPerSec Data_Base:仅对于FAT12/FAT16,Data_Base = Root_Base + RootSize

七、FAT表结构

FAT 表(File Alloacation Table)是一一对应于数据簇号的列表。文件系统分配磁盘空间按簇来分配。因此,文件占有磁盘空间时,基本单位不是字节而是簇, 即使某个文件只有一个字节,操作系统也会给它分配一个最小单元:即一个簇。为了可以将磁盘 空间有序地分配给相应的文件,而读取文件的时候又可以从相应的地址读出文件,我们可以把数 据区空间分成 BPB_BytsPerSec*BPB_SecPerClus 字节长的簇来管理,FAT 表项的大小与 FAT 表的类型有关,FAT12 的表项为 12bit ,FAT16 为 16bit, 而 FAT32 则为 32bit。对 于大文件,需要分配多个簇。同一个文件的数据并不一定完整地存放在磁盘中一个连续地区域 内,而往往会分若干段,像链子一样存放。这种存储方式称为文件的链式存储。为了实现文件的 链式存储,文件系统必须准确地记录哪些簇已经被文件占用,还必须为每个已经占用的簇指明存 储后继内空的下一个簇的簇号,对于文件的最后一簇,则要指明本簇无后继簇。这些都是由 FAT 表来保存的,FAT 表的对应表项中记录着它所代表的簇的有关信息:诸如是空,是不是坏簇,是 否是已经是某个文件的尾簇等。

以 FAT16 为例说明 FAT 的结构如下:

表项

示例代码

描述


FFF8

磁盘标识字,必须为 FFF8

1

FFFF

第一簇已经被占用

2

0003

0000h :可用簇

3

0004

0002h - FFFEF :已用簇,表项中存放文件下个簇的簇号

……

……


N

FFFF

FFFF0h - FFFF6 :保留簇

N+1

0000

FFFF7h :坏簇

……

……

FFFF8h - FFFFFh :文件的最后一簇

FAT各系统记录项的取值含义(16进制)

FAT12记录项的取值

FAT16记录项的取值

FAT32记录项的取值

对应簇的表现情况



0

0

0

未分配的簇

002~FFF

0002~FFEF

00000002~FFFFFFEF

已分配的簇

FF0~FF6

FFF0~FFF6

FFFFFFF0~FFFFFFF6

系统保留

FF7

FFF7

FFFFFFF7

坏簇

FF8~FFF

FFF8~FFFF

FFFFFFF8~FFFFFFFF

文件结束簇

八、长文件名

长文件名是在原有的 FAT 系统上引入的,在只支持短文件名的系统上,长文件名就像是不存在一样。为了达到这个目标,长文件名通过在原有的目录项中引入新的属性字(Attribute)得以实现。

ATTR_LONG_NAME = ATTR_READ_ONLY |

ATTR_HIDDEN |

ATTR_SYSTEM |

ATTR_VOLUME_ID

判断一个目录项是否为长文件名,要通过下面 MASK 实现:

ATTR_LONG_NAME_MASK = ATTR_READ_ONLY |

ATTR_HIDDEN |

ATTR_SYSTEM |

ATTR_VOLUME_ID |

ATTR_DIRECTORY |

ATTR_ARCHIVE  

长文件名目录项数据结构如下:

Name

Offset(byte)

Size(bytes)

Description

LDIR_Ord

0x00

1

该项在这组长文件名中的序号。如果第 6 bit 为 1(Mask with 0x40),说明这是该组长文件名的最后一项。每一组有效的长文件名都必须有这个标志位。

LDIR_Name1

0x01

10

长文件名的第 1-5 个字符

LDIR_Attr

0x0B

1

属性字 – 必须为 0x0F(ATTR_LONG_NAME)

LDIR_Type

0x0C

1

如果是 0,则表示这个目录项是长文件名的一部分。非 0 数值为保留设置。

LDIR_Chksum

0x0D

1

对应的短文件名校验和(Checksum)

LDIR_Name2

0x0E

12

长文件名的第 6-11 个字符

LDIR_FstClusLO

0x1A

2

Must be ZERO. This is an artifact of the FAT“first cluster”and must be zero for compatibility with existing disk utilities.   It's meaningless in the context of a long dir entry.

LDIR_Name3

0x1C

4

长文件名的第 12-13 个字符

长文件名字符采用 Unicode 编码。

Checksum 算法,用 C 语言实现如下:

//------------------------------------------------------------------------

//ChkSum()

//Returns an unsigned byte checksum computed on an unsigned byte

//array. The array must be 11 bytes long and is assumed to contain

//a name stored in the format of a MS-DOS directory entry.

//

Passed: pFcbName

Pointer to an unsigned byte array assumed to be 11 bytes long.

// Returns: Sum

An 8-bit unsigned checksum of the array pointed

// to by pFcbName.

unsigned char ChkSum (unsigned char *pFcbName)

{

short FcbNameLen; unsigned char Sum;

Sum = 0;

for (FcbNameLen=11; FcbNameLen!=0; FcbNameLen--) {

// NOTE: The operation is an unsigned char rotate right Sum = ((Sum & 1) ? 0x80 : 0) + (Sum >> 1) + *pFcbName++;

}

return (Sum);

}

下面是一组长文件名目录项。

00 79 96 40 43 70 00 71 00 72 00 73 00 74 00 0F 00 52 2E 00 ...Cp.q.r.s.t R..

00 79 96 50 74 00 78 00 74 00 00 00 FF FF 00 00 FF FF FF FF t.x.t...……

00 79 96 60 02 63 00 64 00 65 00 66 00 67 00 0F 00 52 68 00 .c.d.e.f.g...Rh.

00 79 96 70 69 00 67 00 6B 00 6C 00 6D 00 00 00 6E 00 6F 00 i.g.k.l.m...n.o.

00 79 96 80 01 73 00 75 00 62 00 64 00 69 00 0F 00 52 72 00 .s.u.b.d.i...Rr.

00 79 96 90 66 00 69 00 6C 00 65 00 5F 00 00 00 61 00 62 00 f.i.l.e._...a.b.

00 79 96 A0 53 55 42 44 49 52 7E 31 54 58 54 20 00 22 2E 4F SUBDIR~1TXT .".O

00 79 96 B0 6C 3B 71 3B 00 00 E9 84 6D 3B 14 00 00 0E 00 00 l;q;..閯 m;......

可以看出,长文件名为“subdirfile_abcdefghigklmnopqrst.txt”,对应的短文件 名为“SUBDIR~1.TXT”。

下面是《Microsoft Extensible Firmware Initiative FAT32 File System

Specification—version1.03》中给出的示例:

假设创建一个名为“The quick brown.fox”的文件,系统将为它建立如下的目录项:

FAT文件系统总结(三)

九、目录结构

目录所在的扇区,都是以32 Bytes 划分为一个单位,每个单位称为一个目录项(Directory Entry),即每个目录项的长度都是 32 Bytes。

FAT32短文件目录项32个字节的表示定义FAT32 表每簇占用 4 Bytes,从 Sector 内偏移地址 50 读取 4 Bytes 数据为 0x15,表 示之后的数据占用簇0x15,簇0x15 在Sector 内偏移地址为0x54,从地址0x54 读取4 Bytes。数据为 0x16,依次类推,最后在偏移地址 0x68 读取 4 Bytes 数据为 0x0FFFFFFF,表示文 件在该簇就结束了。所以该文件占用的簇号依次为 0x14,0x15,0x16,0x17,0x18,0x19 和 0x20。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多