📄 fat32中文版.txt
字号:
FirstSectorofCluster = ((N – 2) * BPB_SecPerClust) + FirstDataSector;
NOTE: 因为 BPB_SecPerClus 总是 2 的整数次方(1,2,4,8,……)这意味这 BPB_SecPerClus 的
乘除法运算可以通过移位(SHIFT)来进行。在当前 Intel x86 架构 2 进制的机器上乘法(MULT)和除法
(DIV)的机器指令非常的繁杂和庞大,而使用移位来运算则会相对的快许多。
第 7 页
FAT: General Overview of On-Disk Format
FAT 类型辨别(FAT Type Determination)
这是一个经常产生错误的地方,并且常常会出现诸如 “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_BytsPerSec – 1)) / BPB_BytsPerSec;
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);
计算簇数:
CountofClusters = DataSec / BPB_SecPerClus;
请记住计算结果四舍五入。
现在我们就可以判定 FAT 的类型了,这部分请仔细阅读,否则会导致 off-by-one 的错误
在下面的程序中, “<”和 “<=”是不一样的,另外请注意数字不要弄错。
If (CountofCluster < 4085) {
/* FAT 类型是 FAT12 */
}
Else if (CountofCluster < 65525) {
/* FAT 类型是 FAT16 */
}
Else {
/* FAT 类型是 FAT32*/
}
这是检测 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。
第 8 页
FAT: General Overview of On-Disk Format
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_BytsPerSec);
ThisFATEntOffset = REM (FATOffset / BPB_BytsPerSec);
REM(…)为求余操作符,就是求 FATOffset 除以 BPB_BytsPerSec 的余数。ThisFATSecNum 是 FAT 表中
包含簇 N 的扇区数,如果你想得到第二个 FAT 表中的扇区数,只要加上 FATSz 就是,如果想的到第三个 FAT
表中的扇区数,只需要加上 FATSz * 2,依次类推。
现在你得到扇区数 ThisFATSecNum(记住这是针对 FAT 卷扇区 0 的偏移量),假设把该值读入到一个指
定的 8-bit SecBuff,同时假定数据类型 WORD 是一个 16-bit 的不无符号类型,而 DWORD 是一个 32-bit 的
无符号类型。
If (FATType == TAF16)
FAT16ClusEntryVal = *((WORD *) & SecBuff[ThisFATEntOffset]);
Else
FAT32ClusEntryVal = (*((DWORD *) & SecBuff[ThisFATEntOffset]) & 0x0FFFFFF;
取得该扇区的内容。
设置该扇区的值使用如下算式:
If (FATType == FAT16)
*((WORD *) &SecBuff[ThisFATEntOffset]) = FAT16ClusEntryVal;
Else {
FAT32ClusEntryVal = FAT32ClusEntryVal & 0x0FFFFFFF;
*((DWORD *) & SecBuff[ThisFATEntOffset]) =
(*((DWORD *) & SecBuff[ThisFATEntOffset])) & 0xF0000000;
*((DWORD *) & SecBuff[ThisFATEntOffset]) =
(*((DWORD *) & SecBuff[ThisFATEntOffset])) | FAT32ClusEntryVal;
}
我们看看上述FAT 代码是如何工作的,实际上每个FAT32的FAT表项只有28-bit可以使用,它的高4位
保留,这4位只有在被格式化的时候会被使用到,在格式化时整个FAT32单元的32-bit都被设为0,包括高位
的4-bit。
另外要说明的一点,这也是经常被混淆的地方,因为FAT32表项实际上被使用的只有28-bit而不是
32-bit。比如,以下几个FAT32簇的值为0x10000000,0xF0000000和0x00000000都表示该簇为空,因为程序
忽略了高位4-bit的值。如果当前簇的值为0x30000000,你想要把数值0x0FFFFFF7写入当前簇来标记坏簇,
那么当你的操作结束后该簇的值实际上是0x3FFFFFF7,因为你必须舍去0x0FFFFFF7这个坏簇标记高位的
4-bit.
因为BPB_BytsPerSec的值一定能够被2和4整除,对于FAT16/FAT32来说,你不必担心元素会超越扇区
的边界,但对于FAT12,你就必须小心了。
FAT12的代码会显得复杂一点,因为它每个元素占1.5个字节(12-bit)。
第 9 页
FAT: General Overview of On-Disk Format
If (FATType == FAT12)
FATOffset = N + (N / 2);
/* 注意算式并没有乘以浮点数1.5。除以2的值四舍五入 */
ThisFATSecNum = BPB_RsvdSecCnt + (FATOffset / BPB_BytsPerSec);
ThisFATEntOffset = REM(FATOffset / BPB_BytsPerSec);
现在我们必须考虑扇区边界的情况。
If (ThisFATEntOffset == (BPB_BytsPerSec – 1)) {
/* 这个簇跨越了FAT的扇区边界,处理这个问题有很多方法, */
/* 其中最简单的一种就是当FAT类型为FAT12则同时读取两个 */
/* 扇区的内容到内存中(如果你想读取扇区N,同时你把扇区 */
/* N+1的内容也读入内存中来,除非扇区N是FAT的最后一个扇 */
/* 区)这样做实际上是避免扇区的边界检测 */
}
现在我们可以象FAT16一样使用WORD数据类型来对FAT12的数据进行读取,但仍需要注意,如果簇号为
偶数,我们取16-bit中的低12-bit,如果是奇数则取高12-bit,
FAT12ClusEntryVal = *((WORD *) & SecBuff[ThisFATEntOffset]);
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 *) &SecBuff[ThisFATEntOffset])) | FAT12ClusEntryVal;
NOTE: 这里的 >>为右移操作符,并往高 4 位填充 0; <<为左移操作符,同时低 4 位用 0 填充。
数据区中的文件是按以下方式与 FAT 表相对应的:数据区中文件存放的第一个簇的簇号被记录在目录
项中,文件就是根据这个簇号和 FAT 表相关联,数据区中文件的位置由前面讨论的 FirstSectorofCluster
来计算。
对于一个文件大小为 0 —— 一个没有数据的文件 —— 在目录项中分配的第一个簇的簇号为 0,此
簇(参见前面讨论的 ThisFATSecNum 和 ThisFATEntOffset)的内容要么是一个 EOC 标记(End of Cluster
chain 簇链表结束标记)要么就是该文件下一个簇的簇号。EOC 的值和 FAT 类型有关(假设 FATContent 是
需要检测看是否包含 EOC 标记的簇的内容)
isEOF = FALSE;
if (FATType == FAT12) {
if (FATContent >= 0x0FF8)
isEOF = TRUE;
}
Else if (FATType == FAT16) {
If (FATContent >= 0xFFF8)
isEOF = TRUE;
第 10 页
FAT: General Overview of On-Disk Format
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -