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 | sudo dd if=/dev/disk1 skip=4340 count=1 bs=512 | hexdump -C |
可以用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
- 0x000-0x1BD 446 bytes boot启动代码
- 0x1C0-0x1FD 64 bytes 分区表,共有四项,最多支持四个分区
- 0x1FE-0x1FF 2 bytes 总是0x55AA,标识位
16bytes分区表需要关注
- 0x4 类型代码,fat32 总是0x0B
- 0x8-0xB LBA起始地址
下面以一个u盘为例说明一下
1 | sudo dd if=/dev/disk1 skip=0 count=1 bs=512 | hexdump -C |
1 | 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| |
可以看到分区表只要一个表项,其中第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 | 0 1 2 3 4 5 6 7 8 9 A B C D E F |
下面是各重要表项的意义
| 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 | 0 1 2 3 4 5 6 7 8 9 A B C D E F |
每一项是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
若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏
扫描二维码,分享此文章