1、前言
本篇文章主要是介绍FAT格式分区数据在硬盘中是如何存储的,FAT分区格式是Microsoft最早支持的分区格式,依据FAT 表中每个簇链的所占位数(有关概念,后面会讲到)分为FAT12、FAT16、 FAT32 三种格式"变种",但其基本存储方式是相似的。我们在嵌入式系统中使用的最多是FAT32,所以后面主要介绍的是FAT32。
2、FAT文件系统结构
FAT文件系统结构图如下所示:
2.1 主引导扇区(MBR)
主引导扇区:硬盘的第一个扇区,由主引导记录(MBR)+ 主分区表(DPT)+ 引导扇区标记(Boot Record ID / Signature)组成,该区域是完成系统BIOS向操作系统交接的重要入口。该扇区在进行硬盘分区时产生。
主引导扇区组成
其中的MBR具体内容我们暂时不需要关心,重点要关心硬盘分区表DPT的数据,即上图中红色划线部分(重点关注蓝色和绿色方框数据),其各个字节表示的作用如下:
对于常见硬盘来说,我们主要关注相对扇区数和总扇区数的值,结合原始数据和分区表含义可以得到重点数据:
1、相对扇区数:保留区1 N扇区 = 0x800 = 2048扇区,也可以得到DBR的地址 = 2048*512 = 0x10 0000 Bytes。
2、总扇区数:对于只有一个分区的U盘来说,该数据表示分区卷的总容量(不包含相对扇区数)
2.2 操作系统引导扇区(DBR)
操作系统引导扇区DBR,也称为引导扇区。包含BIOS参数块,该参数块存储有关卷布局和文件系统结构的信息,以及加载windows的引导代码(暂时不需要关心引导代码)。
下面的示例演示了FAT32卷上引导扇区的十六进制数据:
下面两个表说明了FAT32卷的BPB和拓展BPB的布局,示例值对应FAT32卷上引导扇区的数据。
FAT32分区的BPB字段 | |||
字节位移 | 字段长度 (字节) | 对应取值 | 名称和定义 |
0x0B | 2 | 0x0200 | 扇区字节数(Bytes Per Sector) 硬件扇区的大小。 本字段合法的十进制值有 512、1024、2048 和 4096。对大多数磁盘来说,本字段的值为512。 |
0x0D | 1 | 0x10 | 每簇扇区数(Sectors Per Cluster),一簇中的扇区数。 一个卷默认的簇大小取决于该卷的大小。本字段的合法十进制值有 1、2、4、8、16、32、64 和 128。 |
0x0E | 2 | 0x0C80 | 保留扇区数(Reserved Sector) 第一个 FAT 开始之前的扇区数,包括引导扇区。 (相当于保留区2) |
0x10 | 1 | 0x02 | FAT 数(Number of FAT) 该分区上 FAT 表的个数。本字段的值一般为 2 |
0x11 | 2 | 0x0000 | 根目录项数(Root Entries) 只有 FAT12/FAT16 使用此字段。对 FAT32 分区而言,本字段必须设置为 0 |
0x13 | 2 | 0x0000 | 小扇区数(Small Sector) (只有 FAT12/FAT16 使用此字段)对 FAT32 分区而言,本字段必须设置为 0 |
0x15 | 1 | 0xF8 | 媒体描述符( Media Descriptor) 提供有关媒体被使用的信息。值 0xF8 表示硬盘,0xF0 表示高密度的 3.5寸软盘。 |
0x16 | 2 | 0x0000 | 每 FAT 扇区数(Sectors Per FAT) 只被 FAT12/FAT16 所使用,对 FAT32 分区而言,本字段必须设置为 0 |
0x18 | 2 | 0x003F | 每道扇区数(Sectors Per Track) 包含使用 INT 13h 的磁盘的“每道扇区数”几何结构值。 该分区被多个磁头的柱面分成了多个磁道 |
0x1A | 2 | 0x00FF | 磁头数(Number of Head) 本字段包含使用 INT 13h 的磁盘的“磁头数”几何结构值。 |
0x1C | 4 | 0x00000800 | 隐藏扇区数(Hidden Sector) 该分区上引导扇区之前的扇区数。在引导序列计算到根目录的数据区的绝对位移的 (相当于保留区1) |
0x20 | 4 | 0x01CE7800 | 总扇区数(Large Sector) 本字段表示FAT32分区(不包含保留区1)中总的扇区数 |
0x24 | 4 | 0x000039C0 | 每 FAT 扇区数(Sectors Per FAT) (只被 FAT32 使用)该分区每个 FAT 所占的扇区数。计算机利用这个数和 FAT 数以及保留扇区数(本表中所描述的)来决定根目录从哪里开始。该计算机还可以从目录中的项数决定该分区的 用户数据区从哪里开始 |
0x28 | 2 | 0x0000 | 扩展标志(Extended Flag) (只被 FAT32 使用)该两个字节结构中各位的值为: 位 7:0值意味着在运行时FAT被映射到所有的FAT,1值表示只有一个 FAT是活动的 |
0x2A | 2 | 0x0000 | 文件系统版本(File ystem Version) 只供 FAT32 使用,高字节是主要的修订号,而低字节是次要的修订号。 本字段支持将来对该 FAT32 媒体类型进行扩展。 |
0x2C | 4 | 0x00000002 | 根目录簇号(Root Cluster Number) (只供 FAT32 使用) 根目录第一簇在FAT表的簇号。 本字段的值一般为 2,但不总是如此 |
0x30 | 2 | 0x0001 | 文件系统信息扇区号(File System Information Sector Number) (只供 FAT32 使用)FAT32 分区的保留区中的文件系统信息(File System Information, FSINFO)结构的扇区号。其值一般为 1。在备份引导扇区(Backup |
0x32 | 2 | 0x0006 | 备份引导扇区(Backup Boot Sector) 非“0”表示存储引导扇区副本的扇区号。该字段的值通常为6。 |
0x34 | 12 | 12 个字节均为 0x00 | 保留(Reserved) (只供 FAT32 使用)供以后扩充使用的保留空间。本字段的值总为 0 |
FAT32分区的拓展BPB字段 | |||
字节位移 | 字段长度 (字节) | 对应取值 | 名称和定义 |
0x40 | 1 | 0x80 | 物理驱动器号( Physical Drive Number) 与 BIOS 物理驱动器号有关。软盘驱动器被标识为 0x00,物理硬盘被标识为 0x80,而与物理磁盘驱动器无关。一般地,在发出一个 INT13hBIOS 调用之前设置该值,具体指定所访问的设备。只有当该设备是一个引导设备时,这个值才有意义 |
0x41 | 1 | 0x00 | 保留(Reserved) FAT32 分区总是将本字段的值设置为 0 |
0x42 | 1 | 0x29 | 扩展引导标签(Extended Boot Signature) 本字段必须要有能被 Windows 2000 所识别的值 0x28 或 0x29 |
0x43 | 4 | 0xB2DC3889 | 分区序号(Volume Serial Number) 在格式化磁盘时所产生的一个随机序号,它有助于区分磁盘 |
0x47 | 11 | "NO NAME "(ASCII编码) | 卷标(Volume Label) 本字段只能使用一次,它被用来保存卷标号。现在,卷标被作为一个特殊文件保存在根目录中 |
0x52 | 8 | "FAT32 "(ASCII编码) | 系统 ID(System ID) FAT32文件系统中一般取为"FAT32" |
BPB对我们比较重要的数据已经用蓝色标记出来了,可以了解到:
- 扇区字节数和每簇扇区数
硬件扇区的大小为512Bytes,每个簇占用扇区数为16个扇区。
- 总扇区数
该数据只表示分区卷所占用的扇区数,即 保留区2 + 文件分区表 + 数据区,不包含保留区1,所以要计算U盘总容量需要加上隐藏扇区(即保留区1)的大小,即:
总容量=总扇区数+隐藏扇区:( 0x01CE7800+0x800 ) * 512 = 15,518,924,800 Bytes。
- FAT开始地址
我们可以通过保留扇区数+ 隐藏扇区找到FAT表的起始位置:(0xC80+0x800 ) * 512 = 0x290000 Bytes。
- 根目录开始地址
根目录开始地址 = (隐藏扇区 + 保留扇区 + 每FAT扇区数 * FAT数 )* 扇区字节数
= ( 0x800 + 0xC80 + 0x39C0 * 2)* 512 Bytes = 0x1100000 Bytes
2.3 FSINFO信息扇区
FAT32在保留区2中中增加了一个FSINFO信息扇区,用以记录文件系统中空闲簇的数量以及下一个可用簇的簇号等信息,以供操作系统作为参考。FSINFO信息扇区一般位于文件系统的1号扇区。(此处是逻辑上,而非物理上的1号扇区!!),
内容划分如下:
2.4 FAT32根目录(Root Folder)
为了更好的理解FAT表,我们需要先讲一下根目录。
根文件夹描述根分区中的文件和文件夹。目录所在的扇区,都是以32Byte为一个单位划分的,每个单位称为一个目录项。根目录由若干个目录项组成。目录项有两种格式:短文件格式和长文件格式。
2.4.1 短文件格式
1、对于短文件名,系统将文件名分成两部分进行存储,即主文件名+扩展名。0x0~0x7字节记录文件的主文件名,0x8~0xA记录文件的扩展名,取文件名中的ASCII码值。不记录主文件名与扩展名之间的"."
,主文件名不足 8 个字符以空白符(20H)填充,扩展名不足 3 个字符同样以空白符(20H)填充。0x00 偏移处的取值若为 00H,表明目录项为空;若为E5H,表明目录项曾被使用,但对应的文件或文件夹已被删除。(这也是误删除后恢复的理论依据)。文件名中的第一个字符若为“.”
或“..”
表示这个簇记录的是一个子目录的目录项。“.”
代表当前目录;“..”
代表上级目录。
2、0xB的属性字段:可以看作系统将 0xB的一个字节分成 8 位,用其中的一位代表某种属性的有或无。如 00000101 就表示这是个文件,属性是只读、系统。
3、0x0E~0x0F文件创建时间字段
Bit 0~4:为文件创建时间字段秒/2的值;
Bit 5~10:为文件创建时间字段分钟的值;
Bit 11~15:为文件创建时间字段小时的值;
4、0x10~0x11文件创建日期字段:
Bit 0~4:为文件创建日期字段日期数的值;
Bit 5~8:为文件创建日期字段月份的值;
Bit 9~15:为文件创建时间字段年号-1980的值
5、普通子目录的寻址过程也是通过其父目录中的目录项来指定的,与数据文件(指非目录文件)不同的是目录项偏移 0xB的第 4 位置 1,而数据文件为0。
具体FAT32短文件格式表格如下:
2.4.2 长文件格式
FAT32 的一个重要的特点是完全支持长文件名。长文件名依然是记录在目录项中的。为了低版本的 OS 或程序能正确读取长文件名文件,系统自动为所有长文件名文件创建了一个对应的短文件名,使 对应数据既可以用长文件名寻址,也可以用短文件名寻址。不支持长文件名的 OS 或程序会忽略它认为不合法的长文件名字段,而支持长文件名的 OS 或程序则会以长文件名为显式项来记录和编辑,并隐藏起短文件名。
当创建一个长文件名文件时,系统会自动加上对应的短文件名,其一般有的原则:
- 取长文件名的前 6 个字符加上
"~1"
形成短文件名,扩展名不变。 - 如果已存在这个文件名,则符号
"~"
后的数字递增,直到 5。 - 如果文件名中
~
后面的数字达到 5,则短文件名只使用长文件名的前两个字母。通过数学操纵长文件名的剩余字母生成短文件名的后四个字母,然后加后缀~1
直到最后。 - 如果存在老 OS 或程序无法读取的字符,换以
"_"。
长文件名的实现有赖于目录项偏移为 0xB 的属性字节,当此字节的属性为:只读、隐藏、系统、卷标,即其值为 0FH 时,DOS 和 WIN32 会认为其不合法而忽略其存在。系统将长文件名以 13 个字符为单位进行切割,每一组占据一个目录项。所以可能一个文件需要多个目录项,这时长文件名的 各个目录项按倒序排列在目录表中,以防与其他文件名混淆。长文件名中的字符采用 unicode 形式编码,每个字符占据 2 字节的空间。
注意:系统在存储长文件名时,总是先按倒序填充长文件名目录项,然后紧跟其对应的短文件名。从以上的表格可以看出,长文件名中并不存储对应文件的文件开始簇、文件大小、各种时间和日期属性。文件的这些属性还是存放在短文件名目录项中,一个长文件名总是和其相应的短文件名一 一对应,短文件名没有了长文件名还可以读,但长文件名如果没有对应的短文件名,不管什么系统都将忽略其存在。所以短文件名是至关重要的。
2.4.3 实例解析
由于根目录和子目录在目录项上有一点区别,所以在此处对其分开讲解。
根目录
我们对U盘进行格式化后,在根目录下新建文件结构。
在winhex查看:
可以看到,两者有一点区别,在winhex多了一部分文件夹和文件:新建文件夹和新建文本文档。不必担心,这是由于电脑在创建如”001.txt“文件时,会先创建”新建文件文本文档.txt“,然后如果在你修改文件名时,操作系统会把原来的文件删除,然后才创建新的文件
下面通过实例:文件夹:System Volume Information来解析里面的数据,如下:
通过上面实例表格可了解到重要信息总结如下:
文件名称和属性
文件名:System Volume Information
属性:目录、系统、隐藏
文件创建时间
文件创建时间为0x73B8,按照前面介绍的公式解析时间为14:29:48,解析过程如下:
文件创建日期
文件创建日期为0x5908,按照前面介绍的公式解析时间为2024/08/08,解析过程如下:
文件起始簇号
文件起始簇号的值为0x00000003,说明该文件的起始地址为:
文件地址 = 根目录地址 + (文件起始簇号 -根目录簇号)* 每簇扇区数 * 扇区字节数
= 0x1100000 + (3 - 2)*16 * 512 = 0x 1102000 Byte
子目录
根目录文件夹和子目录文件夹在目录项组成上有一点区别,所以需要单独拿出来讲一讲,我们继续以前面讲的System Volume Information为例,我们可以看到该文件夹中目录项组成如下:
可以明显的看到,相比于根目录,子目录中多了当前目录和上一级目录的目录项,其他组成基本一致。我们可以通过各目录项的起始簇号跳转到对应文件位置。
注意:如果上一级目录是根目录时,其文件起始簇有点特殊,值为0x00000000。我们以test_dir_111111111文件夹里面新建文件夹hello文件夹和hello.txt为例:
我们可以明显看到,该示例中文件夹簇号链:根目录(0x00000000)-》test_dir_111111111(0x00000006)-》hello文件夹(0x0000001C)
文件
最后我们来讲一讲文件目录项,以根目录下的001.txt为例:
可以看到文件的起始簇号为0x00000008,按照之前的计算公式可以得到文件地址为0x110C000,跳转到该地址可以看到文件内容:
2.5 FAT表
FAT表记录了磁盘数据文件的存储链表,对于数据的读取而言是极其重要的,以至于Microsoft为其开发的FAT文件系统中的FAT表创建了一份备份,就是我们看到的FAT2。FAT2 与FAT1 的内容通常是即时同步的,也就是说如果通过正常的系统读写对FAT1 做了更改,那么FAT2 也同样被更新。
FAT表工作的原理:当存储一个大的文件时,会以簇为单位拆分成多个簇存放在磁盘,它们的存放的簇号并不一定是连续的,所以这里用到了FAT表,将该文件所用到的簇号串联起来。
例如截图如下,绿色的部分为存储文件的数据,FAT表查询顺序如下:
1、从根目录(Root folder)找到该文件的起始簇号 12,并且读取簇号 12地址的文件数据。
2、从FAT表中,找到簇号12存放的下一个簇号为13,并且读取簇号 13地址的文件数据。以此类推到簇号65。
3、从FAT表中,找到簇号65存放的下一个簇号为87,并且读取簇号 87地址的文件数据。下一次FAT表查询直接从簇号65跳转到簇号 87。
4、经过上面的连续查询FAT,直到读取FF标记,代表文件读取完成。
我们通过之前获取到的地址0x290000查看FAT表数据,如下:
内容解析:
接下来我们以 根目录文件:stm32l4xx_hal_spi.c为例来介绍FAT表的使用:
文件起始簇号:
文件起始簇号为0x0000000C,说明该文件的起始地址为:
文件地址 = 根目录地址 + (文件起始簇号 -根目录簇号)* 每簇扇区数 * 扇区字节数
= 0x1100000 + (12 - 2)*16 * 512 = 0x 1114000 Byte
可以看到在该位置处果然找到了文件内容。
文件大小:
文件大小为0x24D95(150,933),在winhex中可以查看到文件大小确实是
注意:系统读取磁盘的单位以簇为单位,存放数据不满一簇时也将占用这整个簇(其他文件也无法使用该簇多余的空间),如下:
文件内容为12字节,但是不满1簇,系统读取时还是读一簇的数据(16*512 = 8192 Byte)。
读取完文件起始簇号存放的文件数据后,就查询下一个簇号存放数据。 FAT表以簇为单位,标识分区中空间的使用情况(每个标识占4字节)。FAT前2簇为保留簇(簇0和簇1),不分配给文件使用,其内容含义如下所示:
- FAT16:F8 FF FF FF
- FAT32:F8 FF FF 0F FF FF FF FF
一个FAT表项值表明了文件占用的一个簇号并指明下一簇号的位置,说明读取完簇号0x0C(12)后读下一个簇号0x0D(13),结合FAT表数据可以知道,文件的簇号链接为:
12 -> 13 -> 14 ->15 -> 16 ->17 ->18 -> 19 ->20 ->21 ->22 ->23 ->24 ->25 ->26 -> 27 -> 28 -> 29 -> 30
FAT表按顺序依次记录了该盘各簇的使用情况,是一种位示图法。每簇的使用情况用32位二进制填写,未被分配的簇相应位置写零;坏簇相应位置填入特定值;已分配的簇相应位置填入非零值,具体为:如果该簇是文件的最后一簇,填入的值为FFFFFF0FH(即0x0FFFFFFF),如果该簇不是文件的最后一簇,填入的值为该文件占用的下一个簇的簇号,这样,正好将文件占用的各簇构成一个簇链,保存在FAT表中。0000000H、00000001H两簇号不使用,其对应的两个DWORD位置(FAT表开头的8个字节)用来存放该盘介质类型编号。
FAT表的大小就由该逻辑盘数据区共有多少簇所决定,取整数个扇区。
当文件系统被创建,也就是进行格式化操作时,分配给FAT区域的空间将会被清空,在FAT1与FAT2的0号表项与1号表项写入特定值。由于创建文件系统的同时也会创建根目录,也就是为根目录分配了一个簇空间,通常为2号簇,所以2号簇所对应的2号FAT表项也会被写入一个结束标记,如上图所示。
如果某个簇存在坏扇区,则整个簇会用FAT表项值0xFFFFFF7标记为坏簇,不再使用,这个坏簇标记就记录在它所对应的FAT表项中。
在文件系统中新建文件时,如果新建的文件只占用一个簇,为其分配的簇对应的FAT表项将会写入结束标记。如果新建的文件不只占用一个簇,则在其所占用的每个簇对应的FAT表项中写入为其分配的下一簇的簇号,在最后一个簇对应的FAT表象中写入结束标记。
新建目录时,只为其分配一个簇的空间,对应的FAT表项中写入结束标记。当目录增大超出一个簇的大小时,将会在空闲空间中继续为其分配一个簇,并在FAT表中为其建立FAT表链以描述它所占用的簇情况。
对文件或目录进行操作时,他们所对应的FAT表项将会被清空,设置为0以表示其所对应的簇处于未分配状态。
3、winhex的使用手册
我们在分析FAT32文件系统时,需要用到winhex工具查看磁盘上的原始数据,操作流程如下:
3.1 WinHex工具下载安装
下载链接:https://www.x-ways.net/winhex/index-m.html
安装完成后的工具图标如下所示:
3.2 打开WinHex工具
该软件需要以”管理员身份运行“。
3.3 打开磁盘
选择Tools
—> Open Disk
打开磁盘
3.4 选择需要打开的磁盘
3.5 查看磁盘数据
选择分区1,然后鼠标键右击选择模板,可以查看DBR数据
3.6 查看FAT32 DBR数据
FAT32 DBR数据详细分析可以查看前面的解析,也可以用winhex软件查看(点击模板后后会自动弹出)。
3.7 查看FAT32分区数据
鼠标双击分区1,进入FAT32根目录,然后点击根目录文件夹(注意此时的offset是相对偏移,不是绝对偏移)。
3.8 绝对位置查看数据
为了更好的学习FAT32,推荐使用绝对地址导航来查看数据。先退出分区界面,然后点击导航-》跳转到偏移量,然后设置如下:
4、 参考资料
本文全部内容都来自下面两篇优秀文章,结合了自我理解后对其进行了整合。
https://blog.csdn.net/ZHONGCAI0901/article/details/115196445