Changchun Master Li

小学生也能轻松理解的文件系统fat32

2019-11-26

Introduction

相对于用树实现的 ext2 等,fat32 是一种结构非常简单的文件系统。据传最初的fat文件系统是1977年比尔盖茨发明的,在多年以后的当下仍然是一个常用的文件系统格式,经久不衰。本文的讨论只限于当前仍在广泛使用的 fat32 ,旨在利用直白的语言和dd命令示例来帮助大家完全理解 fat32。

prerequisite knowledge

Diploma

请先获得全日制小学毕业证或其同等学力证书

Linear Block Address

在过去,定位读写的地址非常麻烦,需要对外存的结构有理解。例如 CHS 模式,需通过 磁柱-磁头-扇区 来合成最终的地址。

万幸现在已经有了LBA,它引入block的概念,磁盘都是由很多个block组成,每一个block有512个bytes,从第一个block开始,通过LBA=0, 1, 2 …作为地址索引

Allocation Table

分配表是FAT最为核心的概念,分配表是存在磁盘上的一个数组,是磁盘中一段连续的空间。分配表分配的是cluster(fat32文件系统分配容量的最小单位,一个cluster一般是64个block,即32KB)。分配表的序号与cluster相对应,通过此方式实现单链表,当单个cluster 存储的文件或者目录项超过cluster的容量时,将多个cluster链接起来,存到下一个cluster当中。外存容量大,分配表就大。

dd utility

如果手头有一个u盘或者sd卡用来实验可以获得更深入的理解,可以用dd命令方便的存取、查看任意block的每一个byte,例如
读取 disk1 的第4340个block

1
2
sudo dd if=/dev/disk1 skip=4340 count=1 bs=512 | hexdump -C
sudo dd if=/dev/disk1 skip=4340 count=1 bs=512 > block_4340

可以用vi来编辑binary

转为hex file

1
%!xxd

编辑后再转回binary file

1
%!xxd -r

写回 disk1 的第4340个block

1
sudo dd if=block_4340 of=/dev/disk1 seek=4340 count=1 bs=512

Parts

fat32文件系统包括如下几个部分

Master boot record

LBA_MBR = 0

第一个block是MBR

  1. 0x000-0x1BD 446 bytes boot启动代码
  2. 0x1C0-0x1FD 64 bytes 分区表,共有四项,最多支持四个分区
  3. 0x1FE-0x1FF 2 bytes 总是0x55AA,标识位

16bytes分区表需要关注

  1. 0x4 类型代码,fat32 总是0x0B
  2. 0x8-0xB LBA起始地址

下面以一个u盘为例说明一下

1
sudo dd if=/dev/disk1 skip=0 count=1 bs=512 | hexdump -C
1
2
3
4
5
6
7
8
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fe |................|
000001c0 ff ff 0b fe ff ff 02 00 00 00 fe d7 d1 01 00 00 |................|
000001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200

可以看到分区表只要一个表项,其中第0个表项为

1
00 fe ff ff  0b fe ff ff  02 00 00 00  fe d7 d1 01

LBA起始地址为 02 00 00 00,因为是小端模式,高字节保存在高地址,所以LBA=0x02

Volume ID

LBA_VID_0 = 0x02

上节我们定位到第一个分区的首地址是 0x02,这是分区的第一个block,叫做 Volume ID。
操作系统首先会读这个block来获取有关这个分区的文件系统的信息。

1
sudo dd if=/dev/disk1 skip=0x02 count=1 bs=512 | hexdump -C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
           0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
00000000 eb 58 90 42 53 44 20 20 34 2e 34 00 02 10 20 00 |.X.BSD 4.4... .|
00000010 02 00 00 00 00 f8 00 00 20 00 ff 00 02 00 00 00 |........ .......|
00000020 fe d7 d1 01 2d 3a 00 00 00 00 00 00 02 00 00 00 |....-:..........|
00000030 01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 80 00 29 05 08 86 fb 54 45 53 54 20 20 20 20 20 |..)....TEST |
00000050 20 20 46 41 54 33 32 20 20 20 fa 31 c0 8e d0 bc | FAT32 .1....|
00000060 00 7c fb 8e d8 e8 00 00 5e 83 c6 19 bb 07 00 fc |.|......^.......|
00000070 ac 84 c0 74 06 b4 0e cd 10 eb f5 30 e4 cd 16 cd |...t.......0....|
00000080 19 0d 0a 4e 6f 6e 2d 73 79 73 74 65 6d 20 64 69 |...Non-system di|
00000090 73 6b 0d 0a 50 72 65 73 73 20 61 6e 79 20 6b 65 |sk..Press any ke|
000000a0 79 20 74 6f 20 72 65 62 6f 6f 74 0d 0a 00 00 00 |y to reboot.....|
000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200

下面是各重要表项的意义

| Field | OFFSET | SIZE | Value |
| —————————- | —— | —- | ———- | ——-
| Bytes_Per_Sector | 0x0B | 2B | 0x0200 | block大小(单位byte)(总是512)
| Sectors_Per_Cluster | 0x0D | 1B | 0x10 | cluster大小(单位block),这里是16,即一个cluster是8KB。通常,一个cluster最大是32KB
| Number_of_Reserved_Sectors | 0x0E | 2B | 0x0020 | 保留空间大小(单位block),通常是0x20,32个block
| Number_of_FATs | 0x10 | 1B | 0x02 | 总是2个fat,两个fat数据完全相同,以防磁盘损坏
| Sectors_Per_FAT | 0x24 | 4B | 0x00003a2d | 每个fat大小(单位block),取决与磁盘大小
| Root_Directory_First_Cluster | 0x2C | 4B | 0x00000002 | root目录位于第几个cluster,总是0x2
| Signature | 0x1FE | 2B | 0x55aa | 标识,和MBR相同

Reserved Sectors

LBA = [0x02, 0x22)

接下来是保留空间,Number_of_Reserved_Sectors = 0x22,从Volume ID的block开始 到 Reserved_Sectors 结束共32个block

File Allocation Table

LBA = [0x22, 29820)

Number_of_FATs * Sectors_Per_FAT = 2 * 0x00003a2d = 29786

接下来是fat,从[0x22, 29820)存储两个冗余的fat,共29786个block。
值得一提的是,fat大小取决与磁盘大小。每个fat的表项大小为4Byte,每个表项依次对应每个cluster。
因此(0x00003a2d512/4) = 1906304 个表项。每个表项对应的cluster为8KB,因此我做实验的分区有19063048KB这么大空间,大约14GB

之所以需要fat,是因为当储存一个大于单个cluster容量的文件时,我们需要连接多个cluster以提供足够的容量

其中,cluster从下标2开始,cluster_0和cluster_1是保留的。00 00 00 00代表该cluster未使用,FF FF FF FF代表该cluster是链表最后一个cluster。

单链表虽然结构简单,但是它容易产生碎片,空间利用效率低。linux倾向于使用树结构的ext文件系统

Clusters

LBA = 29820

在fat之后就是为cluster分配的巨大的空间了。切记,LBA[29820, 29820+16KB),也就是第一个cluster,它的下标是2。它对应着跟文件系统

我们不妨看一下里面是什么

1
sudo dd if=/dev/disk2 skip=29820 count=1 bs=512 | hexdump -C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
           0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
00000000 54 45 53 54 20 20 20 20 20 20 20 28 00 00 00 00 |TEST (....|
00000010 00 00 00 00 00 00 d4 4e 37 50 00 00 00 00 00 00 |.......N7P......|
00000020 41 2e 00 5f 00 2e 00 54 00 72 00 0f 00 7f 61 00 |A.._...T.r....a.|
00000030 73 00 68 00 65 00 73 00 00 00 00 00 ff ff ff ff |s.h.e.s.........|
00000040 7e 31 20 20 20 20 20 20 54 52 41 22 00 c6 58 04 |~1 TRA"..X.|
00000050 79 4e 3b 50 00 00 58 04 79 4e 04 00 00 10 00 00 |yN;P..X.yN......|
00000060 50 52 49 4e 54 20 20 20 20 20 20 10 08 0e 7c 63 |PRINT ...|c|
00000070 f8 4e f8 4e 01 00 7c 63 f8 4e fc 56 00 00 00 00 |.N.N..|c.N.V....|
00000080 41 2e 00 54 00 72 00 61 00 73 00 0f 00 25 68 00 |A..T.r.a.s...%h.|
00000090 65 00 73 00 00 00 ff ff ff ff 00 00 ff ff ff ff |e.s.............|
000000a0 54 52 41 53 48 45 7e 31 20 20 20 12 00 c6 58 04 |TRASHE~1 ...X.|
000000b0 79 4e 79 4e 00 00 58 04 79 4e 03 00 00 00 00 00 |yNyN..X.yN......|
000000c0 4c 4f 53 54 20 20 20 20 44 49 52 10 00 00 3a b4 |LOST DIR...:.|
000000d0 2c 4f 2c 4f 00 00 3a b4 2c 4f 06 00 00 00 00 00 |,O,O..:.,O......|
......

每一项是32个bytes,

32 BYTES Directory Structure:

Field offset Size
Short Filename 0x00 11 Bytes
Attrib Byte 0x0B 1 Byte
First Cluster High 0x14 2 Byte
First Cluster Low 0x1A 2 Byte
File Size 0x1C 4 Byte

1 BYTE Attrib Structure:

Attrib Bit Function Comment
0 Read Only 禁写
1 Hidden 隐藏
2 System 系统
3 Volume ID 文件名是卷名
4 Directory 子目录
5 Archive 已经被改变
6 Ununsed Should be zero
7 Ununsed Should be zero

以最后两行为例,文件名为LOST,后缀为DIR。First Cluster为 00 00 00 06,文件大小是0,文件类型为子目录

Limitations

众所周知,fat32文件最大限制是4g,原因就是因为在 32 BYTES Directory Structure 中file size只有32个bits,最大能表示4G。。。。就文件分配表来说,是不存在这个限制的

Experiment

现在,我们已经完全掌握了fat32的底层知识,留一个思考题:

怎样通过dd命令,实现一个递归目录?怎样实现一个无限长的文件?

reference

https://www.pjrc.com/tech/8051/ide/fat32.html

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章