硬盘 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 也不会夹带操作系统的性质。具有公共引导的特性。
我们来分析一段 mbr 。下面是用 winhex 查看的一块希捷 120GB 硬盘的 mbr 。
你的硬盘的 MBR 引导代码可能并非这样。不过即使不同,所执行的功能大体是一样的。 这里找 wowocock 关于磁盘 mbr 的反编译 ,已加了详细的注释,感兴趣可以细细研究一下。
我们看 DPT 部分。操作系统为了便于用户对磁盘的管理。加入了磁盘分区的概念。即将一块磁盘逻辑划分为几块。磁盘分区数目的多少只受限于 C ~ Z 的英文字母的数目,在上图 DPT 共 64 个字节中如何表示多个分区的属性呢 ?microsoft 通过链接的方法解决了这个问题。在 DPT 共 64 个字节中,以 16 个字节为分区表项单位描述一个分区的属性。也就是说,第一个分区表项描述一个分区的属性,一般为基本分区。第二个分区表项描述除基本分区外的其余空间,一般而言,就是我们所说的扩展分区。这部分的大体说明见表 1 。
注:上表中的超过 1 字节的数据都以实际数据显示,就是按高位到地位的方式显示。存储时是按低位到高位存储的。两者表现不同,请仔细看清楚。以后出现的表,图均同。
也可以在 winhex 中看到这些参数的意义:
说明: 每个分区表项占用 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
3.2 扩展分区
扩展分区中的每个逻辑驱动器都存在一个类似于 MBR 的扩展引导记录 ( Extended Boot Record, EBR) ,也有人称之为虚拟 mbr 或扩展 mbr ,意思是一样的。扩展引导记录包括一个扩展分区表和该扇区的标签。扩展引导记录将记录只包含扩展分区中每个逻辑驱动器的第一个柱面的第一面的信息。一个逻辑驱动器中的引导扇区一般位于相对扇区 32 或 63 。但是,如果磁盘上没有扩展分区,那么就不会有扩展引导记录和逻辑驱动器。第一个逻辑驱动器的扩展分区表中的第一项指向它自身的引导扇区。第二项指向下一个逻辑驱动器的 EBR 。如果不存在进一步的逻辑驱动器,第二项就不会使用,而且被记录成一系列零。如果有附加的逻辑驱动器,那么第二个逻辑驱动器的扩展分区表的第一项会指向它本身的引导扇区。第二个逻辑驱动器的扩展分区表的第二项指向下一个逻辑驱动器的 EBR 。扩展分区表的第三项和第四项永远都不会被使用。
通过一幅 4 分区的磁盘结构图可以看到磁盘的大致组织形式。如图 5 :
关于扩展分区,如图 6 所示,扩展分区中逻辑驱动器的扩展引导记录是一个连接表。该图显示了一个扩展分区上的三个逻辑驱动器,说明了前面的逻辑驱动器和最后一个逻辑驱动器之间在扩展分区表中的差异。
除了扩展分区上最后一个逻辑驱动器外,表 2 中所描述的扩展分区表的格式在每个逻辑驱动器中都是重复的:第一个项标识了逻辑驱动器本身的引导扇区,第二个项标识了下一个逻辑驱动器的 EBR 。最后一个逻辑驱动器的扩展分区表只会列出它本身的分区项。最后一个扩展分区表的第二个项到第四个项被使用。
扩展分区表项中的相对扇区数字段所显示的是从扩展分区开始到逻辑驱动器中第一个扇区的位移的字节数。总扇区数字段中的数是指组成该逻辑驱动器的扇区数目。总扇区数字段的值等于从扩展分区表项所定义的引导扇区到逻辑驱动器末尾的扇区数。
有时候在磁盘的末尾会有剩余空间,剩余空间是什么呢?我们前面说到,分区是以 1 柱面的容量为分区粒度的,那么如果磁盘总空间不是整数个柱面的话,不够一个柱面的剩下的空间就是剩余空间了,这部分空间并不参与分区,所以一般无法利用。照道理说,磁盘的物理模式决定了磁盘的总容量就应该是整数个柱面的容量,为什么会有不够一个柱面的空间呢。在我的理解看来,本来现在的磁盘为了更大的利用空间,一般在物理上并不是按照外围的扇区大于里圈的扇区这种管理方式,只是为了与操作系统兼容而抽象出来 CHS 。可能其实际空间 zymail@vip.sina.com
四、 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
图 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 。
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 大同小异的。
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
上面是我们对文件存储的一种假设,也该揭开谜底的时候了。上面的思想其实就是 fat 文件系统的思想的精髓 ( 但并不是,尤其像具体的参数的意义与我们所举的例子是完全不同的。请忘掉上边细节,努力记忆下边 ) 。
★ FAT16 存储原理 :
当把一部分磁盘空间格式化为 fat 文件系统时, fat 文件系统就将这个分区当成整块可分配的区域进行规划,以便于数据的存储。一般来讲,其划分形式如图 7 所示。我们把 FAT16 部分提取出来,详细描述一下:
FAT16 是 Microsoft 较早推出的文件系统,具有高度兼容性,目前仍然广泛应用于个人电脑尤其是移动存储设备中, FAT16 简单来讲由图 4.3.12 所示的 6 部分组成 ( 主要是前 5 部分 ) 。引导扇区 (DBR) 我们已经说过 ,FAT16 在 DBR 之后没有留有任何保留扇区,其后紧随的便是 FAT 表。 FAT 表是 FAT16 用来记录磁盘数据区簇链结构的。像前面我们说过的例子一样, FAT 将磁盘空间按一定数目的扇区为单位进行划分,这样的单位称为簇。通常情况下,每扇区 512 字节的原则是不变的。簇的大小一般是 2n (n 为整数 ) 个扇区的大小,像 512B,1K,2K,4K,8K,16K,32K , 64K 。实际中通常不超过 32K 。 之所以簇为单位而不以扇区为单位进行磁盘的分配,是因为当分区容量较大时,采用大小为 512b 的扇区管理会增加 fat 表的项数,对大文件存取增加消耗,文件系统效率不高。分区的大小和簇的取值是有关系的,见表 9
注意:少于 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
看一幅在 winhex 所截 FAT16 的文件分配表,图 10 :
如图, FAT 表以 "F8 FF FF FF" 开头,此 2 字节为介质描述单元,并不参与 FAT 表簇链关系。小红字标出的是 FAT 扇区每 2 字节对应的簇号。
相对偏移 0x4~0x5 偏移为第 2 簇 ( 顺序上第 1 簇 ) ,此处为 FF, 表示存储在第 2 簇上的文件 ( 目录 ) 是个小文件,只占用 1 个簇便结束了。
第 3 簇中存放的数据是 0x0005 ,这是一个文件或文件夹的首簇。其内容为第 5 簇,就是说接下来的簇位于第 5 簇 ?D?D 〉 FAT 表指引我们到达 FAT 表的第 5 簇指向,上面写的数据是 "FF FF", 意即此文件已至尾簇。
第 4 簇中存放的数据是 0x0006 ,这又是一个文件或文件夹的首簇。其内容为第 6 簇,就是说接下来的簇位于第 6 簇 ?D?D 〉 FAT 表指引我们到达 FAT 表的第 6 簇指向,上面写的数据是 0x0007 ,就是说接下来的簇位于第 7 簇 ?D?D 〉 FAT 表指引我们到达 FAT 表的第 7 簇指向 …… 直到根据 FAT 链读取到扇区相对偏移 0x1A~0x1B ,也就是第 13 簇,上面写的数据是 0x000E ,也就是指向第 14 簇 ?D?D 〉 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 中的一些取值进行说明:
(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.13 标出了 FAT32 分区的基本构成。
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 :
FAT32 簇的取值意义和 FAT16 类似,不过是位数长了点罢了,比较见表 13 :
FAT32 的另一项重大改革是根目录的文件化,即将根目录等同于普通的文件。这样根目录便没有了 FAT16 中 512 个目录项的限制,不够用的时候增加簇链,分配空簇即可。而且,根目录的位置也不再硬性地固定了,可以存储在分区内可寻址的任意簇内,不过通常根目录是最早建立的 ( 格式化就生成了 ) 目录表。所以,我们看到的情况基本上都是根目录首簇占簇区顺序上的第 1 个簇。在图 4.3.12 中也是按这种情况制作的画的。
FAT32 对簇的编号依然同 FAT16 。顺序上第 1 个簇仍然编号为第 2 簇,通常为根目录所用 ( 这和 FAT16 是不同的, FAT16 的根目录并不占簇区空间, 32 个扇区的根目录以后才是簇区第 1 个簇 )
FAT32 的文件寻址方法与 FAT16 相同,但目录项的各字节参数意义却与 FAT16 有所不同,一方面它启用了 FAT16 中的目录项保留字段,同时又完全支持长文件名了。
对于短文件格式的目录项。其参数意义见表 14 :
说明:
(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 可以看出,长文件名中并不存储对应文件的文件开始簇、文件大小、各种时间和日期属性。文件的这些属性还是存放在短文件名目录项中,一个长文件名总是和其相应的短文件名一一对应,短文件名没有了长文件名还可以读,但长文件名如果没有对应的短文件名,不管什么系统都将忽略其存在。所以短文件名是至关重要的。在不支持长文件名的环境中对短文件名中的文件名和扩展名字段作更改 ( 包括删除,因为删除是对首字符改写 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 上数据的读取便游刃有余了。