1. MBR
主引导记录(英语:Master Boot Record,缩写:MBR),又叫做主引导扇区,是计算机开机后访问硬盘时所必须要读取的首个扇区。MBR的一般构成如下:
-
GRUB占用446byte,一般包含以下字段
偏移(字节) 长度(字节) 描述 0 440 代码区 440 4 选用磁盘标志 444 2 一般为0x0000 -
分区表占用64byte,包含4个分区项,每个分区项占用16byte。单个分区项包含以下字段
偏移(字节) 长度(字节) 描述 0 1 分区状态:0x80->活动,0x00->非活动 1 1 分区起始磁头号 2 2 02H的0-5位 - 分区起始扇区号
02H的6-7位以及03H - 分区的起始柱面4 1 分区类型(文件系统类型) 5 1 分区结束磁头号 6 2 06H的0-5位 - 分区结束扇区号
06H的6-7位以及07H - 分区结束柱面8 4 本分区前扇区数量 12 4 本分区大小(扇区数量) -
结束标志占用2byte,固定为0x55AA
1.1. 实例
文中所有实例测试均基于以下环境:
硬件:8G Sandisk SD卡,格式化为FAT32
系统:Windows10
软件:WinHex
2. FAT文件系统简介
起源与发展:由微软为MS-DOS开发的文件系统,支持所有非NT核心Windows系统,因结构简单成为软盘和存储卡主流格式
设计目标:优先考虑早期计算机性能限制,采用低复杂度结构实现跨平台兼容性
目前有三种FAT上盘格式:FAT12、FAT16、FAT32
3. FAT文件系统整体布局
3.1. 卷结构
一个FAT文件系统的卷结构由四个区域组成:
- 保留区(Reserved Region)
- FAT区(FAT Region)
- 根目录区(Root Directory Region)
- 数据区(FILE & Directory Region)
FAT文件系统最初是为IBM PC架构开发的,所以FAT卷上的磁盘数据结构都是采用小端存储。
3.2. 保留区
3.2.1 DBR扇区
BIOS参数块(BPB)位于FAT卷保留区中的第一个扇区,该扇区也称为“boot sector”或者“0th sector”或者DBR(DOS Boot Recorder,DOS引导扇区)。DBR扇区包含的字段及其含义如下,从偏移字节36开始(下面称为扩展的BPB结构),FAT12/FAT16与FAT32的解析不同。
3.2.1.1. FAT12/FAT16/FAT32共有的BPB结构
字段 | 偏移(字节) | 长度(字节) | 描述 |
---|---|---|---|
BS_jmpBoot | 0 | 3 | 跳转指令 |
BS_OEMName | 3 | 8 | OEM名称标识符。可以通过FAT设置为任何所需的值。通常表明系统格式化了卷 |
BPB_BytsPerSec | 11 | 2 | 每个扇区的字节数,只能是下列数值:512,1024,2048,4096 |
BPB_SecPerClus | 13 | 1 | 每个簇的扇区数量。该值必须是2^n,通常为1、2、4、8、16、32、64和128 |
BPB_RsvdSecCnt | 14 | 2 | 保留区包含扇区的数量。该字段值不能为0,其典型用法是使数据区的起始扇区地址(簇2)与簇大小对齐 |
BPB_NumFATs | 16 | 1 | 卷上文件分配表的数量。尽管值1是可以接受的,但建议值为2 |
BPB_RootEntCnt | 17 | 2 | 对于FAT12/FAT16,该字段指示根目录中32字节目录条目的计数,为了最大程度兼容,FAT16中该字段需要设置为512;对于FAT32,该字段必须为0 |
BPB_TotSec16 | 19 | 2 | 卷的扇区总数。对于FAT32,使用BPB_TotSec32字段指示扇区总数,该字段必须为0;对于FAT12/FAT16,如果总扇区数小于0x10000,则使用该字段,否则也使用BPB_TotSec32字段 |
BPB_Media | 21 | 1 | 介质描述符。0xF8是“固定”(不可移动)媒体的标准值。对于可移动媒体,经常使用0xF0 |
BPB_FATSz16 | 22 | 2 | 一个文件分配表所占扇区的数量。FAT32使用BPB_FATSz32指示,该字段会为0 |
BPB_SecPerTrk | 24 | 2 | 每磁道扇区数 |
BPB_NumHeads | 26 | 2 | 磁头数 |
BPB_HiddSec | 28 | 4 | FAT分区前的隐藏扇区数 |
BPB_TotSec32 | 32 | 4 | 卷的扇区总数。如果该字段非0,那么BPB_TotSec16字段应为0;如果BPB_TotSec16字段非0,那么该字段应为0 |
注意:Microsoft在2000年发布的版本中规定,对于FAT12和FAT16卷,BPB_RsvdSecCnt字段值永远不应为1以外的任何值。对于FAT32卷,此值通常为32。而在2005年发布的版本中,对于FAT12和FAT16卷,没有再强制规定BPB_RsvdSecCnt字段值必须为1.
3.2.1.2. FAT12/FAT16的扩展BPB结构
字段 | 偏移(字节) | 长度(字节) | 描述 |
---|---|---|---|
DS_DrvNum | 36 | 1 | 中断 0x13 驱动器编号。字段设置为 0x80 或 0x00 |
BS_Reserved1 | 37 | 1 | 固定为0x00 |
BS_BootSig | 38 | 1 | 扩展引导签名。如果以下两个字段中的任何一个非零,则将值设置为0x29。这是一个签名字节,表示引导扇区中的以下三个字段是存在的 |
BS_VollD | 39 | 4 | 卷序列号。该字段与 BS_VolLab 一起支持可移动媒体上的卷跟踪。这些值使 FAT 文件系统驱动程序能够检测到在可移动驱动器中插入了错误的磁盘。此 ID 应通过将当前日期和时间组合成一个 32 位值来生成 |
BS_VolLab | 43 | 11 | 卷标。该字段与根目录中记录的 11 字节卷标相匹配。注意:FAT 文件系统驱动程序必须确保在根目录中的卷标文件名称被更改或创建时更新此字段。当没有卷标时,字段的设置为字符串“NO NAME ” |
BS_FilSysType | 54 | 8 | 文件系统类型。字段被设置为“FAT12”、“FAT16”、“FAT”三种字符串之一 |
- | 62 | 448 | 固定为0x00 |
Signature_word | 510 | 2 | 结束标志,固定位0x55AA |
- | 512 | DBR扇区剩余所有字节 | 固定为0x00 |
3.2.1.3. FAT32的扩展BPB结构
字段 | 偏移(字节) | 长度(字节) | 描述 |
---|---|---|---|
BPB_FATSz32 | 36 | 4 | 指示一个FAT表所占扇区数量 |
BPB_ExtFlags | 40 | 2 | 位 0-3 – 基于零的活动 FAT 编号。仅在禁用镜像时有效 位 4-6 – 保留 位 7 – 0 表示 FAT 在运行时被镜像到所有 FAT;-- 1 表示只有一个 FAT 是活动的;它是位 0-3 中引用的那个 位 8-15 – 保留 |
BPB_FSVer | 42 | 2 | 固定为0x0 |
BPB_RootClus | 44 | 4 | 此值设置为根目录第一个簇的簇编号。该值应为 2 或之后第一个可用的(非坏)簇 |
BPB_FSInfo | 48 | 2 | FAT32 卷保留区域中 FSINFO 结构的扇区编号。通常为 1 |
BPB_BkBootSec | 50 | 2 | 设置为 0 或 6。如果非零,表示卷的保留区域中引导记录副本的扇区编号。 |
BPB_Reserved | 52 | 12 | 固定为0x00 |
DS_DrvNum | 64 | 1 | 中断 0x13 驱动器编号。字段设置为 0x80 或 0x00 |
BS_Reserved1 | 65 | 1 | 固定为0x00 |
BS_BootSig | 66 | 1 | 扩展引导签名。如果以下两个字段中的任何一个非零,则将值设置为0x29。这是一个签名字节,表示引导扇区中的以下三个字段是存在的 |
BS_VollD | 67 | 4 | 卷序列号。该字段与 BS_VolLab 一起支持可移动媒体上的卷跟踪。这些值使 FAT 文件系统驱动程序能够检测到在可移动驱动器中插入了错误的磁盘。此 ID 应通过将当前日期和时间组合成一个 32 位值来生成 |
BS_VolLab | 71 | 11 | 卷标。该字段与根目录中记录的 11 字节卷标相匹配。注意:FAT 文件系统驱动程序必须确保在根目录中的卷标文件名称被更改或创建时更新此字段。当没有卷标时,字段的设置为字符串“NO NAME ” |
BS_FilSysType | 82 | 8 | 固定为字符串“FAT32” |
- | 90 | 420 | 固定为0x00 |
Signature_word | 510 | 2 | 结束标志,固定为0x55AA |
- | 512 | DBR扇区剩余所有字节 | 固定为0x00 |
3.2.1.4. 实例
下图是FAT32文件系统保留区的0号扇区,即DBR扇区。
从上图获取到的DBR扇区相关参数如下(小端存储):
[11:12] BPB_BytsPerSec 0x0200
[13:13] BPB_SecPerClus 0x08
[14:15] BPB_RsvdSecCnt 0x20
[16:16] BPB_NumFATs 0x02
[17:18] BPB_RootEntCnt 0x00
[32:35] BPB_TotSec32 0x0CF000
[36:39] BPB_FATSz32 0x0340
[44:47] BPB_RootClus 0x00
另外,有观察到保留区中的6号扇区保存了DBR的备份
3.2.2. FSInfo扇区
文件系统信息(FSInfo)结构通过包含卷上空闲簇的数量以及第一个可用(空闲)簇的簇号,帮助优化文件系统驱动程序的实现。
FSInfo结构仅存在于格式化为FAT32的卷中。该结构必须在卷初始化(格式化)期间保留在介质上。该结构必须位于扇区#1,紧接在包含BPB的扇区之后。扇区#7维护该结构的副本。
注意:该结构中包含的信息仅应视为建议,文件系统驱动程序的实现必须在卷挂载时验证这些值。文件系统驱动程序的实现不要求确保结构中的信息保持一致,尽管协议建议这样做。
下面的表格描述了FSInfo结构包含的字段及其含义:
字段 | 偏移(字节) | 长度(字节) | 描述 |
---|---|---|---|
FSI_LeadSig | 0 | 4 | 标志FSInfo扇区的开始,固定为0x41615252 |
FSI_Resvered1 | 4 | 480 | 固定为0x00 |
FSI_StrucSig | 484 | 4 | 一种更局部的签名,固定为0x61417272 |
FSI_FreeCount | 488 | 4 | 文件系统的空簇数(不一定正确,需进行范围检查) |
FSI_NxtFree | 492 | 4 | 指示驱动从哪个簇号开始查找空闲簇。如果该值为 0xFFFFFFFF,则没有提示,驱动应从簇 2 开始查找 |
FSI_Reserved2 | 496 | 12 | 保留,固定为0x00 |
FSI_TrailSig | 508 | 4 | 结束标志,0x55AA |
3.3. FAT区
文件分配表的每个条目指示根目录区域和数据区域中簇的状态。文件分配表单条目包含的比特数如下:
- FAT12,单条目长度为12 bits
- FAT16,单条目长度为16 bits
- FAT32,单条目长度为32 bits
文件分配表的条目数可能会大于根目录区域和数据区域中簇的数目,那些额外的条目的状态值应为0。条目的状态值应遵循下表:
FAT32条目中的高四位是保留位,修改FAT32条目时应确保高四位不变。在格式化卷时,整个FAT表会被重置为0,此时高四位也会被清除。后续在谈论FAT32条目的最高位时都是指排除高四位保留位后的部分。
3.3.1. 根据簇号计算FAT条目偏移
T h i s F A T S e c N u m = B P B _ R e s v d S e c C n t + ( F A T O f f s e t / B P B B y t s P e r S e c ) F A T O f f s e t = { N ∗ 2 , F A T T y p e = F A T 16 N ∗ 4 , F A T T y p e = F A T 32 ThisFATSecNum\ =\ BPB\_ResvdSecCnt\ +\ (FATOffset\ /\ BPB_BytsPerSec)\\ FATOffset\ =\ \begin{cases} N*2,\ FATType=FAT16 \\ N*4,\ FATType=FAT32 \end{cases} ThisFATSecNum = BPB_ResvdSecCnt + (FATOffset / BPBBytsPerSec)FATOffset = {N∗2, FATType=FAT16N∗4, FATType=FAT32
ThisFATSecNum是当簇号#N所对应的条目位于第一个FAT表时,条目所在的分区卷的扇区编号。
3.3.2. 保留条目
在文件分配表中,FAT[0]和FAT[1]是保留条目,不与某个簇对应。FAT[0]的低8位与BPB_Media字段值一致,其他位全1。例如,如果BPB_Media的值为0xF8,那么
- FAT12,FAT[0] = 0x0FF8
- FAT16,FAT[0] = 0xFFF8
- FAT32,FAT[0] = 0x0FFFFFF8
在FAT16和FAT32系统中,Microsoft Windows的文件系统驱动程序可能会使用FAT[1]条目的高两位来表示脏卷标志(dirty volume flags)。这代表文件系统可能由于错误操作而处于不一致的状态;这些脏标志用于指示卷在最后一次关闭时未正常结束。
- 最高位ClnShutBitMask
如果位为1,则卷是“干净”的,可以进行访问挂载。如果位为0,则卷是“脏”的,表示FAT文件系统驱动程序在之前的挂载操作中无法正确卸载卷。应该检查卷的内容,以确认文件系统元数据是否有损坏。 - 次高位HrdErrBitMask
如果位为1,则没有遇到磁盘读写错误。如果位为0,则文件系统驱动程序在上次挂载卷时遇到了磁盘I/O错误,这表明某些扇区可能出现了故障。应该使用磁盘修复工具对卷的内容进行扫描,该工具会进行表面分析,以查找新的坏扇区。
最后一个FAT扇区并不一定完全属于FAT。FAT在最后一个FAT扇区中对应于(CountOfClusters + 1)簇号的条目处停止,而这个条目不一定位于最后一个FAT扇区的末尾。FAT的实现不能对(CountOfClusters + 1)FAT条目之后的最后一个FAT扇区的内容进行任何假设。在初始化(格式化)卷时,必须将此最后FAT条目之后的所有字节置为0。
为每个FAT预留的扇区数(在BPB_FATSz16或BPB_FATSz32字段中的扇区计数)可能大于实际存放整个FAT所需的扇区数。因此,在卷的FAT区域的每个FAT末尾可能会有完全未使用的FAT扇区。每个实现必须使用CountOfClusters确定FAT中最后一个有效扇区的值(FAT中最后一个有效扇区是包含编号为CountOfClusters + 1的FAT条目的那个扇区)。
在卷初始化/格式化过程中,FAT中最后一个有效扇区(定义为包含最后一个簇的FAT条目的那个)之后预留的所有扇区必须设置为0x0。
3.3.3. 实例
下图是FAT区中FAT1的0号扇区。
可以跟踪到簇号#29(0x1D)~#92(0x5C)都属于UBOOT.BIN文件,然后#92簇连接的下一个簇号为#410(0x19A),为了找到UBOOT.BIN文件的结束簇号,继续通过WinHex跟踪#410簇号之后的内容,最后发现#417(0x1A1)是UBOOT.BIN文件的结束簇号。
由此可以计算UBOOT.BIN文件实际占用的大小:
size = ((92 - 29 + 1) + (417 - 410 + 1)) * 8 * 512 = 288 KB
3.4. 根目录区
对于FAT12和FAT16卷,根目录位于磁盘上一个固定位置,紧接在FAT区之后,并且其大小是固定的。对于FAT32,根目录可以是可变大小的,并且是一个簇链,就像其他任何目录一样。
3.5. 文件和目录区(数据区)
3.5.1. FAT短目录结构
FAT目录是一种特殊的文件类型。目录充当其他文件和子目录的容器。目录内容(数据)是一系列32字节的目录项。每个目录项通常表示一个包含的文件或子目录。以下表格描述了每个目录项所包含的字段:
字段 | 偏移(字节) | 长度(字节) | 描述 |
---|---|---|---|
DIR_Name | 0 | 11 | 8字符文件名+3字符扩展名 |
DIR_Attr | 11 | 1 | 文件属性 |
DIR_NTRes | 12 | 1 | 保留给Windows NT使用。创建文件时将该值设置为0,之后不要修改或查看它 |
DIR_CrtTimeTenth | 13 | 1 | 文件创建时的毫秒时间戳。该字段实际上包含的是十分之一秒的计数。DIR_CrtTime的秒部分的粒度为2秒,因此该字段是十分之一秒的计数,有效值范围为0-199(包括0和199) |
DIR_CrtTime | 14 | 2 | 文件创建的时间戳 |
DIR_CrtDate | 16 | 2 | 文件创建的日期 |
DIR_LstAccDate | 18 | 2 | 上一次访问(读/写)的日期 |
DIR_FstClusHI | 20 | 2 | 该目录项第一个簇号的高字(对于FAT12/FAT16,该字段时钟为0) |
DIR_WrtTime | 22 | 2 | 上一次写的时间 |
DIR_WrtDate | 24 | 2 | 上一次写的日期 |
DIR_FstClusLO | 26 | 2 | 该目录项第一个簇号的低字 |
DIR_FileSize | 28 | 4 | 文件大小,以字节为单位 |
DIR_Name字段包含了与相应条目相关的文件或子目录的11个字符(“短”)名称。DIR_Name字段逻辑上由两个部分组成:
- 8个字符的名称主体
- 3个字符的扩展名
如果需要,上述两个部分都会进行“尾部空格填充”(使用值:0x20)。请注意以下几点:
- 如果文件名超过8个字符,会被截断,截断的方法是取文件名前6个字符加上“1”(如果有同名文件,则符号“”后的数字递增)作为名称主体。
- 一个隐含的‘.’字符将名称主体与扩展名分开。“.”分隔符字符不会存储在DIR_Name字段中。
- DIR_Name[0] = 0xE5表示该目录条目是空闲的(可用)。然而,0xE5是日本字符集中有效的KANJI前导字节值。对于基于KANJI字符集的名称,如果需要,DIR_Name[0]会存储值0x05来表示0xE5。如果FAT文件系统实现读取到DIR_Name[0] = 0x05,并且使用的字符集是KANJI,则必须在返回名称给应用程序之前在内存中执行适当的替换。
- DIR_Name[0] = 0x00也表示该目录条目是空闲的(可用)。然而,DIR_Name[0] = 0x00是一个额外的指示,表示当前空闲条目后面的所有目录条目也都是空闲的。
- DIR_Name[0]不能等于0x20(换句话说,名称不能以空格字符开头)。
- 目录中的所有名称必须是唯一的。
名称组成字符的限制:
- 不允许使用小写字母。
- 名称中字符的非法值如下:
- 小于0x20的值(除了前面提到的DIR_Name[0]中的特殊情况0x05)
- 0x22, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D和0x7C
如果文件名不足8个字符,DIR_Name字段未使用字节会用0x20进行填充;如果文件名超过8个字符,会被截断,截断的方法是取文件名前6个字符加上“1”(如果有同名文件,则符号“”后的数字递增)。
3.5.2. FAT长目录结构
长目录项所包含的字段及其含义:
字段 | 偏移(字节) | 长度(字节) | 描述 |
---|---|---|---|
LDIR_Ord | 0 | 1 | 长文件名目录项的序列号。关联某个短目录项一个长目录项集合,其编号从1开始递增 |
LDIR_Name1 | 1 | 10 | 长文件名的第 1~5 个字符。长文件名使用 Unicode 码,每个字符需要两个字节的空间。如果文件名结束但还有未使用的字节,则会在文件名后先填充两个字节的“ 00”,然后开始使用 0xFF 填充 |
LDIR_Attr | 11 | 1 | 长目录项的属性标志,固定是 0x0F。系统通过该字段判断当前目录项是长目录项还是短目录项 |
LDIR_Type | 12 | 1 | 如果为零,表示该目录项是长名称的一个子组件。注意:其他值保留用于未来扩展。非零值意味着其他目录项类型 |
LDIR_Chksum | 13 | 1 | 校验和。如果一个文件的长文件名需要几个长文件名目录项进行存储,则这些长文件名目录项具有相同的校验和 |
LDIR_Name2 | 14 | 12 | 文件名的第 6~11 个字符,未使用的字节用 0xFF 填充 |
LDIR_FstClusLO | 26 | 2 | 固定为0 |
LDIR_Name3 | 28 | 4 | 文件名的第 12~13 个字符,未使用的字节用 0xFF 填充 |
长目录项总是紧接在其关联的短目录项之前,并且与之物理相连。文件系统还会进行一些其他检查,以确保一组长目录项集合实际上与一个短目录项相关联。
长目录项集合中的每个成员都有一个唯一的编号,集合的最后一个成员会与0x40进行或运算,以指示它实际上是集合中的最后一个成员。LDIR_Ord 字段用于确定这一点。集合的第一个成员的 LDIR_Ord 值为 1。集合中的第 n 个长成员的值为n (如果是最后一个长目录项,则为“n | 0x40”)。请注意,LDIR_Ord 字段不能具有 0xE5 或 0x00 的值。这两个值一直被文件系统用于表示“空闲”目录项或集群中的“最后”目录项。LDIR_Ord 的值范围不得包含这两个值。如果不符合上述要求,长目录项将被视为“损坏”,并被文件系统视为孤儿。
在创建短目录项和长目录项时,会对短目录项中包含的名称计算一个 8 位校验和。短目录项中的 11 个字符全部用于校验和的计算。该校验和会放置在每个长目录项中。如果长目录项集合中的任何一个校验和与短目录项中包含的名称计算出的校验和不一致,那么这些长目录项将被视为孤儿。这种情况可能发生在包含长目录项和短目录项的磁盘被带到先前版本的 MS-DOS/Windows 上,并且仅重命名了具有长目录项的文件或目录的短名称时。校验和的计算方法如下:
对于没有内容的文件(即零长度文件),其在目录项中记录的第一个簇号会被设置为 0。这表示该文件并未占用任何存储空间,因为它没有实际的数据。
3.5.3. 实例
3.5.3.1. 目录项实例
短文件名,DIR_Name字段的8字符名称主体空闲字节用0x20填充。
同时可以看出U-BOOT.BIN文件的起始簇号为#29(0x1D),对应的逻辑扇区号为:
sector = 0x20 + 0x340 * 2 + (29-2) * 0x08 = 1912
WinHex跳转到#29号簇查看信息如下:
长文件名,DIR_Name字段的8字符名称主体用前文件名前6个字符加上“~1”填充。
3.5.3.2. 创建目录/文件
在根目录下创建子目录“FAT_TEST0”,系统会为新创建的子目录分配一个起始簇(通过“.
”目录下可知簇号为#3),在子目录的起始簇,前两个目录项为“.
”目录项和“..
”目录项。其中“.
”目录项记录当前子目录的信息,“..
”目录项记录子目录的父目录的信息。对于在根目录下创建的子目录,“..
”目录项记录的起始簇号为0。
然后在“FAT_TEST0”目录下再创建子目录“FAT_TEST0_1”,系统会在“FAT_TEST0”的簇中更新“FAT_TEST0_1”子目录的信息,并且为其分配一个起始簇(簇号为#5)。#5簇的前两个目录项也为“.
”目录项和“..
”目录项。
再在“FAT_TEST0”目录下创建文件“FILE0.txt”,系统会在“FAT_TEST0”的簇中更新“FILE0.txt”文件的信息,并且为其分配一个起始簇(簇号为#6)。#6簇存储着文件内容。
当然,执行上述操作也会更新FAT表的条目,如下图。
3.5.3.3. 删除目录/文件
删除文件“FILE0.txt”
- DIR_Name[0] = 0xE5表示该目录条目是空闲的(可用)
- FAT表对应簇#6的状态为全0,表示簇#6是空闲的,未被占用
- 被删除文件的数据没有被立即擦除
删除子目录“FAT_TEST0_1”
- DIR_Name[0] = 0xE5表示该目录条目是空闲的(可用)
- FAT表对应簇#5的状态为全0,表示簇#5是空闲的,未被占用
- “
.
”和“..
”目录项没有被立即擦除
参考资料
- Microsoft FAT Specification
- https://blog.youkuaiyun.com/weixin_51624616/article/details/139134492
- https://www.jianshu.com/p/c57c4e688da6