1. 引言
FAT(File Allocation Table,文件分配表)文件系统是计算机历史上最具影响力的文件系统之一。自1980年代诞生以来,它凭借简单、兼容性强的特点,成为跨平台存储的"通用语言"。无论是早期的MS-DOS、Windows系统,还是如今的U盘、SD卡、数码相机,FAT文件系统都扮演着重要角色。
本文将深入剖析FAT文件系统的内部结构、工作原理、各版本差异,以及它在现代存储设备中的应用场景,帮助读者全面理解这一经典文件系统。
2. FAT文件系统的发展历史
2.1 诞生背景
1980年,微软为QDOS(Quick and Dirty Operating System)开发了FAT文件系统,随后被用于IBM PC上的MS-DOS。当时磁盘容量极小(通常只有几百KB),FAT的设计目标就是简单高效地管理这些小容量存储介质。
2.2 版本演进
FAT文件系统经历了多个版本的演进:
| 版本 | 推出时间 | 最大分区容量 | 主要应用 |
|---|---|---|---|
| FAT12 | 1980年 | 32MB | 软盘、早期MS-DOS |
| FAT16 | 1987年 | 2GB | 早期Windows、DOS |
| FAT32 | 1996年 | 2TB(实际受限制) | Windows 95 OSR2及以后 |
| exFAT | 2006年 | 128PB(理论) | 大容量U盘、SDXC卡 |
3. FAT文件系统的核心结构
3.1 整体布局
FAT文件系统将存储介质划分为几个关键区域:
#mermaid-svg-9MMu7hsQ2xmM2t82{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9MMu7hsQ2xmM2t82 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9MMu7hsQ2xmM2t82 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9MMu7hsQ2xmM2t82 .error-icon{fill:#552222;}#mermaid-svg-9MMu7hsQ2xmM2t82 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9MMu7hsQ2xmM2t82 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9MMu7hsQ2xmM2t82 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9MMu7hsQ2xmM2t82 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9MMu7hsQ2xmM2t82 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9MMu7hsQ2xmM2t82 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9MMu7hsQ2xmM2t82 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9MMu7hsQ2xmM2t82 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9MMu7hsQ2xmM2t82 .marker.cross{stroke:#333333;}#mermaid-svg-9MMu7hsQ2xmM2t82 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9MMu7hsQ2xmM2t82 p{margin:0;}#mermaid-svg-9MMu7hsQ2xmM2t82 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9MMu7hsQ2xmM2t82 .cluster-label text{fill:#333;}#mermaid-svg-9MMu7hsQ2xmM2t82 .cluster-label span{color:#333;}#mermaid-svg-9MMu7hsQ2xmM2t82 .cluster-label span p{background-color:transparent;}#mermaid-svg-9MMu7hsQ2xmM2t82 .label text,#mermaid-svg-9MMu7hsQ2xmM2t82 span{fill:#333;color:#333;}#mermaid-svg-9MMu7hsQ2xmM2t82 .node rect,#mermaid-svg-9MMu7hsQ2xmM2t82 .node circle,#mermaid-svg-9MMu7hsQ2xmM2t82 .node ellipse,#mermaid-svg-9MMu7hsQ2xmM2t82 .node polygon,#mermaid-svg-9MMu7hsQ2xmM2t82 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9MMu7hsQ2xmM2t82 .rough-node .label text,#mermaid-svg-9MMu7hsQ2xmM2t82 .node .label text,#mermaid-svg-9MMu7hsQ2xmM2t82 .image-shape .label,#mermaid-svg-9MMu7hsQ2xmM2t82 .icon-shape .label{text-anchor:middle;}#mermaid-svg-9MMu7hsQ2xmM2t82 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9MMu7hsQ2xmM2t82 .rough-node .label,#mermaid-svg-9MMu7hsQ2xmM2t82 .node .label,#mermaid-svg-9MMu7hsQ2xmM2t82 .image-shape .label,#mermaid-svg-9MMu7hsQ2xmM2t82 .icon-shape .label{text-align:center;}#mermaid-svg-9MMu7hsQ2xmM2t82 .node.clickable{cursor:pointer;}#mermaid-svg-9MMu7hsQ2xmM2t82 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9MMu7hsQ2xmM2t82 .arrowheadPath{fill:#333333;}#mermaid-svg-9MMu7hsQ2xmM2t82 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9MMu7hsQ2xmM2t82 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9MMu7hsQ2xmM2t82 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9MMu7hsQ2xmM2t82 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9MMu7hsQ2xmM2t82 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9MMu7hsQ2xmM2t82 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9MMu7hsQ2xmM2t82 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9MMu7hsQ2xmM2t82 .cluster text{fill:#333;}#mermaid-svg-9MMu7hsQ2xmM2t82 .cluster span{color:#333;}#mermaid-svg-9MMu7hsQ2xmM2t82 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9MMu7hsQ2xmM2t82 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9MMu7hsQ2xmM2t82 rect.text{fill:none;stroke-width:0;}#mermaid-svg-9MMu7hsQ2xmM2t82 .icon-shape,#mermaid-svg-9MMu7hsQ2xmM2t82 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9MMu7hsQ2xmM2t82 .icon-shape p,#mermaid-svg-9MMu7hsQ2xmM2t82 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9MMu7hsQ2xmM2t82 .icon-shape .label rect,#mermaid-svg-9MMu7hsQ2xmM2t82 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9MMu7hsQ2xmM2t82 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9MMu7hsQ2xmM2t82 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9MMu7hsQ2xmM2t82 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 引导扇区
Boot Sector
文件分配表
FAT区域
根目录区
Root Directory
数据区
Data Area
3.2 引导扇区(Boot Sector)
引导扇区位于分区的第一个扇区(第0扇区),包含文件系统的基本参数:
- 跳转指令:用于引导操作系统
- OEM标识:厂商信息
- BPB(BIOS Parameter Block):每扇区字节数、每簇扇区数、保留扇区数、FAT表数量等关键参数
- 引导代码:用于加载操作系统
3.3 文件分配表(FAT)
FAT是文件系统的核心,它是一个数组结构,每个表项对应数据区中的一个簇(Cluster)。表项的值表示:
- 0x0000:空闲簇
- 0x0002-0xFFEF:文件的下一个簇号
- 0xFFF0-0xFFF6:保留簇
- 0xFFF7:坏簇
- 0xFFF8-0xFFFF:文件结束标记(EOF)
3.4 根目录区
根目录区存储文件和子目录的目录项。每个目录项占用32字节,包含:
- 文件名(8字节)和扩展名(3字节)
- 文件属性(1字节)
- 保留字段(10字节)
- 创建时间(2字节)和创建日期(2字节)
- 最后访问日期(2字节)
- 起始簇号高16位(2字节)
- 最后修改时间(2字节)和日期(2字节)
- 起始簇号低16位(2字节)
- 文件大小(4字节)
3.5 数据区
数据区是实际存储文件内容的地方,以簇为单位进行分配。簇的大小由分区容量决定,容量越大,簇通常也越大。
4. FAT表项详解
4.1 FAT12
FAT12使用12位(1.5字节)表示一个簇号,最多管理4096个簇。由于表项不是整数字节,读取时需要特殊处理:
c
// FAT12表项读取示例
unsigned int read_fat12_entry(unsigned char *fat, unsigned int cluster) {
unsigned int offset = cluster + (cluster / 2);
unsigned int value;
if (cluster & 1) {
// 奇数簇号,取高12位
value = (fat[offset] >> 4) | (fat[offset + 1] << 4);
} else {
// 偶数簇号,取低12位
value = fat[offset] | ((fat[offset + 1] & 0x0F) << 8);
}
return value;
}
4.2 FAT16
FAT16使用16位(2字节)表示一个簇号,最多管理65536个簇。表项读取相对简单:
c
// FAT16表项读取示例
unsigned short read_fat16_entry(unsigned char *fat, unsigned int cluster) {
unsigned int offset = cluster * 2;
return (unsigned short)(fat[offset] | (fat[offset + 1] << 8));
}
4.3 FAT32
FAT32使用32位(4字节)表示一个簇号,但实际只使用低28位,最多管理约2.68亿个簇。FAT32还引入了根目录可扩展的特性,根目录不再固定大小,而是像普通文件一样存储在数据区。
5. 文件存储与链式结构
5.1 链式存储原理
FAT文件系统采用链式存储方式管理文件。与连续存储不同,一个文件的数据可以被分散存放在数据区中多个不连续的簇里,系统并不要求这些簇在物理上相邻。
那么FAT如何把这些分散的簇组织成一个完整的文件?关键就在于文件分配表(FAT):
- 目录项只记录起始簇号:当我们在目录区找到某个文件的目录项时,它保存的是该文件占用的第一个簇的编号,后续位置并不直接写在目录项中。
- FAT表项负责指向下一簇:数据区中的每个簇在FAT表中都有一个对应的表项。如果文件还需要继续往后延伸,该簇对应的FAT表项就保存"下一个簇号";如果该簇已经是文件最后一簇,FAT表项则填入EOF结束标记。
- 形成一条"簇链":从目录项给出的起始簇出发,沿着FAT表项不断跳转,就能按顺序找到文件占用的所有簇。
下面的流程图展示了一个占用4个簇的文件链式结构:
#mermaid-svg-tJmGAXpJftJVXr3L{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tJmGAXpJftJVXr3L .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tJmGAXpJftJVXr3L .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tJmGAXpJftJVXr3L .error-icon{fill:#552222;}#mermaid-svg-tJmGAXpJftJVXr3L .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tJmGAXpJftJVXr3L .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tJmGAXpJftJVXr3L .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tJmGAXpJftJVXr3L .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tJmGAXpJftJVXr3L .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tJmGAXpJftJVXr3L .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tJmGAXpJftJVXr3L .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tJmGAXpJftJVXr3L .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tJmGAXpJftJVXr3L .marker.cross{stroke:#333333;}#mermaid-svg-tJmGAXpJftJVXr3L svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tJmGAXpJftJVXr3L p{margin:0;}#mermaid-svg-tJmGAXpJftJVXr3L .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tJmGAXpJftJVXr3L .cluster-label text{fill:#333;}#mermaid-svg-tJmGAXpJftJVXr3L .cluster-label span{color:#333;}#mermaid-svg-tJmGAXpJftJVXr3L .cluster-label span p{background-color:transparent;}#mermaid-svg-tJmGAXpJftJVXr3L .label text,#mermaid-svg-tJmGAXpJftJVXr3L span{fill:#333;color:#333;}#mermaid-svg-tJmGAXpJftJVXr3L .node rect,#mermaid-svg-tJmGAXpJftJVXr3L .node circle,#mermaid-svg-tJmGAXpJftJVXr3L .node ellipse,#mermaid-svg-tJmGAXpJftJVXr3L .node polygon,#mermaid-svg-tJmGAXpJftJVXr3L .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tJmGAXpJftJVXr3L .rough-node .label text,#mermaid-svg-tJmGAXpJftJVXr3L .node .label text,#mermaid-svg-tJmGAXpJftJVXr3L .image-shape .label,#mermaid-svg-tJmGAXpJftJVXr3L .icon-shape .label{text-anchor:middle;}#mermaid-svg-tJmGAXpJftJVXr3L .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tJmGAXpJftJVXr3L .rough-node .label,#mermaid-svg-tJmGAXpJftJVXr3L .node .label,#mermaid-svg-tJmGAXpJftJVXr3L .image-shape .label,#mermaid-svg-tJmGAXpJftJVXr3L .icon-shape .label{text-align:center;}#mermaid-svg-tJmGAXpJftJVXr3L .node.clickable{cursor:pointer;}#mermaid-svg-tJmGAXpJftJVXr3L .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tJmGAXpJftJVXr3L .arrowheadPath{fill:#333333;}#mermaid-svg-tJmGAXpJftJVXr3L .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tJmGAXpJftJVXr3L .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tJmGAXpJftJVXr3L .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tJmGAXpJftJVXr3L .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tJmGAXpJftJVXr3L .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tJmGAXpJftJVXr3L .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tJmGAXpJftJVXr3L .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tJmGAXpJftJVXr3L .cluster text{fill:#333;}#mermaid-svg-tJmGAXpJftJVXr3L .cluster span{color:#333;}#mermaid-svg-tJmGAXpJftJVXr3L div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tJmGAXpJftJVXr3L .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tJmGAXpJftJVXr3L rect.text{fill:none;stroke-width:0;}#mermaid-svg-tJmGAXpJftJVXr3L .icon-shape,#mermaid-svg-tJmGAXpJftJVXr3L .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tJmGAXpJftJVXr3L .icon-shape p,#mermaid-svg-tJmGAXpJftJVXr3L .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tJmGAXpJftJVXr3L .icon-shape .label rect,#mermaid-svg-tJmGAXpJftJVXr3L .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tJmGAXpJftJVXr3L .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tJmGAXpJftJVXr3L .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tJmGAXpJftJVXr3L :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} FAT表项指向
FAT表项指向
FAT表项指向
簇5
起始簇
簇9
簇12
簇20
EOF标记
在这个例子中,目录项里记录的起始簇号是5;系统读取簇5后查询FAT表,发现簇5对应"下一簇为9";继续读簇9,又得知下一簇为12;直到簇20对应的FAT表项出现EOF标记,说明文件到此结束。
5.2 文件读取流程
完整的文件读取过程可以概括为"先找目录项,再沿簇链逐个读取":
- 定位目录项:根据文件路径,在目录区中找到该文件对应的32字节目录项。
- 获取起始簇号:从目录项中读取文件起始簇号(低16位,FAT32还可能需要结合高16位)。
- 读取簇数据:根据簇号计算出该簇在数据区中的地址,读取这一簇内容。
- 查询FAT表:在FAT表中找到当前簇对应的表项,获得下一簇号或EOF标记。
- 继续或结束:如果得到的是下一簇号,则回到第3步继续读取;如果遇到EOF标记,则文件读取完成。
下面举例说明。假设磁盘上有一个文件 REPORT.TXT,其目录项记录的起始簇号为5,FAT表中相关表项如下:
| 当前簇 | FAT表项值 | 含义 |
|---|---|---|
| 5 | 9 | 下一簇为9 |
| 9 | 12 | 下一簇为12 |
| 12 | 20 | 下一簇为20 |
| 20 | 0xFFFF | EOF,文件结束 |
系统读取该文件的完整过程为:
- 在目录区找到
REPORT.TXT的目录项,读取出起始簇号:5。 - 计算簇5在数据区的地址,读取出文件的第一段数据(簇5的内容)。
- 查询FAT表中簇5对应的表项,得到值 9,说明下一簇是9。
- 读取簇9的内容,再查FAT表,得到值 12,说明下一簇是12。
- 读取簇12的内容,再查FAT表,得到值 20,说明下一簇是20。
- 读取簇20的内容,再查FAT表,发现表项为 0xFFFF(EOF),表示文件全部读取完毕。
最终,系统按照 5 → 9 → 12 → 20 的顺序,把这4个簇的数据拼接起来,就还原了完整的 REPORT.TXT 文件内容。
5.3 碎片问题
链式存储的一个显著缺点是文件碎片化。随着文件的创建和删除,空闲簇变得分散,导致文件被分散存储在多个不连续的簇中。虽然FAT通过链式结构能够正确读取碎片化文件,但频繁的磁盘读写会降低性能。
6. 目录结构
6.1 目录项格式
目录项中与"定位文件"关系最密切的,每个目录项里对应一个文件,目录项里有对应文件的起始簇号。FAT的目录项是32字节的固定结构,具体字段如下:
| 偏移 | 长度 | 字段 | 说明 |
|---|---|---|---|
| 0x00 | 8 | 文件名 | 不足8字节用空格填充 |
| 0x08 | 3 | 扩展名 | 不足3字节用空格填充 |
| 0x0B | 1 | 属性 | 位标志 |
| 0x0C | 10 | 保留 | 通常为0 |
| 0x16 | 2 | 创建时间 | 时:分:秒 |
| 0x18 | 2 | 创建日期 | 年:月:日 |
| 0x1A | 2 | 最后访问日期 | 年:月:日 |
| 0x1C | 2 | 起始簇号高16位 | FAT32使用 |
| 0x1E | 2 | 最后修改时间 | 时:分:秒 |
| 0x20 | 2 | 最后修改日期 | 年:月:日 |
| 0x22 | 2 | 起始簇号低16位 | 所有版本使用 |
| 0x24 | 4 | 文件大小 | 字节数 |
目录区是什么:目录区是存放目录项的地方,一组连续存放的 32 字节目录项。系统找文件时,本质上就是在对应目录的目录区里逐个比对这些目录项。
目录区存在哪里:
- FAT12/FAT16:根目录区是一块独立、固定的区域,位于最后一个 FAT 表之后、数据区之前,大小在格式化时确定;子目录不放在这里,而是作为数据存储到数据区中。
- FAT32:根目录不再占用独立固定区域,根目录本身也像一个"目录文件"存储在数据区中,引导扇区的 BPB 参数会给出根目录的第一个簇号。
要理解根目录到底在哪里,关键一步是先回到分区最前面的引导扇区 读取 BPB 参数。下面用一个从引导扇区出发、最终打开 /DIR1/REPORT.TXT 的完整例子说明。
第一步:从引导扇区读取 BPB,确定根目录位置
系统先读第 0 扇区(引导扇区),取出 BPB 中的关键参数:
- 每扇区字节数(偏移
0x0B) - 保留扇区数(偏移
0x0E) - FAT 表数量(偏移
0x10) - 每个 FAT 表占用扇区数(FAT12/FAT16 在偏移
0x16,FAT32 在偏移0x24) - 根目录目录项数量(偏移
0x11,FAT32 中为 0) - FAT32 还会给出根目录首簇号(偏移
0x2C)
这里需要区分:FAT12/FAT16 的根目录没有"起始簇号"的概念,它是一块固定扇区区域;FAT32 的根目录才是通过"根目录首簇号"定位的目录内容。
-
FAT12/FAT16:根目录区是固定区域,起始扇区号可以计算:
根目录区起始扇区 = 保留扇区数 + FAT数量 × 每个FAT表扇区数例如保留扇区数为 1、FAT 数量为 2、每个 FAT 表占用 16 个扇区,则根目录区从
1 + 2 × 16 = 33号扇区开始。 -
FAT32 :根目录已经像普通目录一样放在数据区中,不需要用上面的固定区域公式,而是直接读取 BPB 扩展字段中的根目录首簇号 (偏移
0x2C,通常为 2),再按每簇扇区数换算成数据区中的实际地址。
第二步:从根目录开始,逐层查找 /DIR1/REPORT.TXT
拿到根目录位置后,再从根目录 / 开始查找:
- 读取根目录区 :FAT12/FAT16 直接到上面算出的根目录扇区读取目录项;FAT32 则到数据区读取根目录首簇对应的目录内容。逐个比对根目录里的目录项,找到目录项
DIR1。该目录项属性为"目录",记录的是DIR1子目录内容所在的起始簇号。 - 进入
DIR1的目录区 :根据DIR1目录项中的起始簇号,到数据区读取DIR1的目录内容。这里读出来的仍然是一组目录项,并不是文件正文。 - 在
DIR1目录区中查找REPORT.TXT:逐个比对 32 字节目录项,找到REPORT.TXT对应的目录项。 - 读取起始簇号字段 :
- FAT12/FAT16:直接读取偏移
0x22处的 2 字节作为起始簇号,此时偏移0x1C的高 16 位保留不用。 - FAT32:将偏移
0x1C的 2 字节作为高 16 位、偏移0x22的 2 字节作为低 16 位,组合成 32 位起始簇号:起始簇号 = (高16位 << 16) | 低16位。
- FAT12/FAT16:直接读取偏移
- 进入数据区读取文件正文 :根据起始簇号和 BPB 参数换算出第一个簇在数据区中的地址,读取文件的第一段内容;后续再沿 FAT 表链继续读取下一簇,直到遇到 EOF。
例如在 FAT32 分区中,REPORT.TXT目录项的两个起始簇号字段为:
- 偏移
0x1C:0x0001 - 偏移
0x22:0x0005
则起始簇号为 0x00010005,即文件从第 0x00010005 号簇开始存储。对于 FAT12/FAT16,由于高位字段固定为 0,起始簇号就只剩低 16 位参与计算,直接读 0x22 字段即可。
6.2 长文件名支持
传统的FAT使用8.3命名规则(8字符文件名+3字符扩展名),无法满足现代用户的需求。为此,FAT引入了VFAT(Virtual FAT)长文件名扩展:
- 使用多个连续的目录项存储长文件名
- 每个目录项可存储13个UTF-16字符
- 通过属性字段中的特殊标志(0x0F)识别
- 保持向后兼容性,旧系统仍可读取短文件名
6.3 目录层级
FAT12和FAT16的根目录位于固定位置且大小固定,而子目录则存储在数据区中,可以动态扩展。FAT32的根目录也支持动态扩展,与普通目录类似。
7. FAT32与exFAT的深入对比
7.1 FAT32的特点
- 最大分区容量:理论2TB,但Windows格式化工具限制为32GB
- 最大文件大小:4GB(受32位文件大小字段限制)
- 簇大小:随分区容量增大而增大,最小4KB
- 兼容性:几乎所有操作系统和设备都支持
7.2 exFAT的改进
exFAT(Extended File Allocation Table)是微软为闪存设备设计的现代文件系统:
- 最大文件大小:16EB(理论)
- 最大分区容量:128PB(理论)
- 支持权限控制:可选的访问控制列表
- 优化闪存性能:减少写入次数,延长寿命
- 支持大簇:最大32MB
7.3 选择建议
| 使用场景 | 推荐文件系统 | 原因 |
|---|---|---|
| 软盘、老旧设备 | FAT12/FAT16 | 兼容性最好 |
| U盘(≤32GB) | FAT32 | 兼容性广,系统支持好 |
| U盘(>32GB) | exFAT | 突破大小限制 |
| SD卡(SDXC) | exFAT | 标准要求 |
| 跨平台移动存储 | exFAT | Windows/macOS/Linux均支持 |
8. FAT文件系统的优缺点
8.1 优点
- 简单高效:结构清晰,实现简单,资源占用少
- 兼容性极强:几乎所有操作系统、嵌入式设备、相机、游戏机都支持
- 轻量级:元数据开销小,适合小容量存储
- 易于恢复:结构简单,数据恢复相对容易
8.2 缺点
- 无日志功能:突然断电可能导致文件系统损坏
- 碎片化严重:链式存储导致文件碎片
- 无权限控制:不支持文件级权限和加密
- 单文件大小限制:FAT32最大4GB
- 可靠性较低:FAT表损坏可能导致整个文件系统不可用
9. 实际应用与工具
9.1 常见应用场景
- 可移动存储:U盘、SD卡、移动硬盘
- 嵌入式系统:数码相机、MP3播放器、车载系统
- 双系统共享:Windows与Linux之间的数据交换
- 引导分区:部分系统使用FAT作为EFI系统分区
9.2 常用工具
- Windows:磁盘管理、format命令
- Linux:mkfs.vfat、fsck.vfat
- macOS:磁盘工具
- 跨平台:Rufus、balenaEtcher
10. 总结
FAT文件系统虽然诞生于40多年前,但凭借其简单性和无与伦比的兼容性,至今仍在存储领域占据重要地位。从FAT12到exFAT,它不断演进以适应存储技术的发展。
理解FAT文件系统的内部结构,不仅有助于我们更好地使用存储设备,还能在数据恢复、嵌入式开发、底层系统编程等场景中发挥重要作用。尽管现代文件系统(如NTFS、ext4、APFS)在功能和性能上更为强大,但FAT系列依然是跨设备数据交换的"通用语言"。
希望本文能帮助你全面理解FAT文件系统。如果你有任何疑问或想深入了解某个方面,欢迎在评论区留言讨论!