由于文件的大小不同和不停的增删操作,同一个文件的数据往往不能完整地存放在磁盘的一个连续的区域内,而会分成若干段,像一条链子一样存放,这种存储方式称为文件的链式存储。为了实现文件的链式存储,磁盘上必须准确地记录哪个文件占用了哪些扇区,哪些扇区还没有被占用等信息。用来记录磁盘中文件如何被分散存储在不同扇区的信息的表格就叫 FAT(File Allocation Table,文件分配表)。可以采用多种办法记录文件占用存储单元的信息,FAT 表响应的也有很多种格式,3.5 英寸软盘采用的是 FAT12 格式。那么这个 FAT12 格式是怎样的呢?它的 FAT 表放在哪里呢?
前面说过 3.5 英寸软盘的 0 柱面、0 磁头、1 扇区是为引导扇区,紧接着引导扇区的就是 FAT 了,而且是连续两个完全相同的 FAT(之所以要两个完全相同的 FAT,是出于系统冗余考虑)。那么 FAT 表是怎么起作用的呢?FAT 表中每个项的值代表的就是某文件占用的下一个扇区号,如果值是 4087,表示它是一个坏簇;如果值大于或等于 4088,则表示当前扇区已经是本文件的最后一个扇区(为什么是4087、4088 呢?看下去就知道了)。由于 3.5 英寸软盘有 2880 个扇区,需要 12 个位(bit)来计数(2^11 = 2047,小于2880),所以每个 FAT 表项占 12 bit(这就是 FAT12 名字的由来)。但是 2^12 = 4096,又远大于 2880,实际上 FAT 表还记录了一些别的信息:第 1 字节的值代表存储介质(0f0h代表软盘,0f8代表硬盘);第 2、3 两个字节都是 0ffh,代表了FAT文件分配表标识符;从第 4 个字节开始才真正与数据区所有的扇区一一对应。由于 FAT 表前面 3 个字节占用了2 个 序号(3 * 8 = 24 bit),所以数据区的第 1 个扇区的序号是 2,而不是 0 —— 不然就不是一一对应了。
那么到底是哪个文件呢?那个文件的第 1 个扇区号又是多少呢?数据区的第一个扇区又是第几号逻辑扇区呢?答案在磁盘结构的下一部分——根目录中。
根目录位于第二个 FAT 表之后。根目录由若干个条目组成(最多 224 个),每个条目对应一个文件,记录了该文件的文件名、文件属性和占用的第一个扇区号。根目录每个条目的格式是这样的:
名称 | 偏移 | 长度 | 描述 |
DIR_Name | 0 | 0xB | 文件名8字节,扩展名3字节 |
DIR_Attr | 0xB | 1 | 文件属性 |
0xC | 10 | 保留 | |
DIR_WrtTime | 0x16 | 2 | 最后修改时间 |
DIR_WrtDate | 0x18 | 2 | 最后修改日期 |
DIR_FstClus | 0x1A | 2 | 此条目对应的开始扇区号 |
DIR_FileSize | 0x1C | 4 | 文件大小 |
可以算出来,根目录占用 14 个扇区(最多有 224 个条目,每个条目占用 32 字节,224 * 32 / 512 = 14),每个 FAT 表占用 9 个扇区(假设每个 FAT 表占用 X 个扇区,2 个 FAT 表共占用 2 * X 个扇区,则((总扇区数 2880 - (引导扇区数 1 + FAT表占用扇区数 2 * X + 根目录占用扇区数 14)) * 每项占用位数 12 / 每字节位数 8 + 存储其他信息占用字节数 3 = FAT 表占用扇区数2 * X * 每扇区字节数 512,解方程 x = 8.35,取整就是 9),根目录从 19 号逻辑扇区开始(前面有 1 个引到扇区 + 2个 FAT * 每个 FAT 占用 9 个扇区 = 19 个扇区) 。
根目录后面就是数据区了,显然数据区从 33 号逻辑扇区开始( 引到扇区 1 + FAT表 18 + 根目录 14 = 33)。
现在好办了,用前两天写的读写扇区的那个函数,直接通过根目录条目找到文件的第 1 个扇区,再从 FAT 表中找到其他的扇区,文件就能被读写了!
那么,操作系统又是怎么知道 FAT12 有 2 个FAT,每个 FAT 占用 9 扇区,根目录最多有 224 个条目的呢?——因为这些信息都记录在磁盘第一个扇区里了!第一个扇区的 512 字节,每个都有明确的用途,百度一下 FAT12 就能找到下面这个表格:
名称 | 偏移 | 长度 | 内容 | 软盘参考值 |
BS_jmpBoot | 0 | 3 | 跳转指令,指向程序入口 |
jmp _main nop |
BS_OEMName | 3 | 8 | 厂商名 | 自己定义 |
BPB_BytsPerSec | 11 | 2 | 每扇区字节数 | 512 |
BPB_SecPerClus | 13 | 1 | 每簇扇区数 | 1 |
BPB_RsvdSecCnt | 14 | 2 | 保留扇区数(用作引导) | 1 |
BPB_NumFATs | 16 | 1 | FAT 表数 | 2 |
BPB_RootEntCnt | 17 | 2 | 根目录中能容纳文件的最大数量 | 224 |
BPB_TotSec16 | 19 | 2 | 扇区总数(FAT12、16 用) | 2880 |
BPB_Media | 21 | 1 | 介质描述符 |
0xF0 |
BPB_FATSz16 | 22 | 2 | 每 FAT 表占用扇区数 | 9 |
BPB_SecPerTrk | 24 | 2 | 每磁道扇区数 | 18 |
BPB_NumHeads | 26 | 2 | 磁头数 | 2 |
BPB_HiddSec | 28 | 4 | 隐藏扇区数 | 0 |
BPB_TotSec32 | 32 | 4 | 扇区总数(FAT32 用) | 2880 |
BS_DrvNum | 36 | 1 | 驱动器号 | 0 |
BS_Reserved1 | 37 | 1 | 未使用 | 0 |
BS_BootSig | 38 | 1 |
扩展引导标记 |
0x29 |
BS_VolD | 39 | 4 | 卷标序列号 | 0 |
BS_VolLab | 43 | 11 | 卷标 | 自己定义 |
BS_FileSysType | 54 | 8 | 文件系统类型 | ‘FAT12‘ |
引导代码 | 62 | 448 | 引导代码、数据及其他填充字符 | |
结束标志 | 510 | 2 | 0xAA55 |