MBR 分区结构

访问内存时,字节是访问的最小单位。但是由于硬盘容量大,所以一般以512个字节为一组(一个块),以块为单位访问。

逻辑块是硬盘的最小访问单位,通用的访问方式是 LBA28/48。

常见的分区格式有古典的 MBR 以及流行的 GPT 两种。

在经典BIOS下,对硬盘的要求只有一个:0号块的最后两个字节必须是 0x55、0xAA,其他并没有要求。但是在 UEFI 固件下就不一样了。UEFI 标准要求硬盘必须按照 GPT 格式分区,否则无法启动计算机。

MBR 管理分区的秘密就藏在这64字节的标准 MBR 分区表规划里。

如果我们对某个磁盘进行分区操作,选择 MBR 分区表的话,第0号块会被磁盘管理工具按照 MBR 格式填充相应的内容。MBR 分区表使用 16 个字节来描述一个分区的入口,最多可以分成4个区。这 16 个字节的含义如下:

例如 00 02 03 00 0c 8c 0a 82 80 00 00 00 00 00 20 00

FAT32 对应的文件系统标志位即 0x0c。

对于分区其实相对扇区号,例中给出的是 0x80000000,那么对应的小端字节为0x00000080。这个才是

FAT32 文件系统

FAT32 记录的是磁盘分区的空间分配情况。簇是一个新单位,每个簇由若干个块组成。一般为 8 个块。

FAT32 就是用 4 个字节,32 位来标记每一个簇的状态。一般情况下,一个簇 8 个块,一个块有 512 字节,即一簇 4096 字节;簇号最大是 2322^{32} ,那么,在 FAT32 文件系统下,一个分区的最大空间是 232×4096=162^{32} \times 4096 = 16 TB。

ps:在服务器领域,还有 ZFS 这种文件系统,使用 128 位来标记每一个簇。

另外,文件系统还受制于分区格式。比如 MBR 分区格式,使用 4 个字节来记录每个分区的总大小,单位是块。那么, 每个分区的最大空间是 232÷8×4096=22^{32} \div 8 \times 4096 = 2 TB。

这也就是为什么 UEFI 固件出现之前能支持的最大硬盘是 2TB 的原因。

FAT32 文件系统格式:

FAT 头主要记录 FAT32 文件系统的整体信息,比如文件系统总块数每个簇的块数每个块的字节数FAT 保留块数分区前已使用块数FAT 表个数根目录簇号等等。它占据的空间就是 FAT 保留块数再乘以每个块的字节数。

FAT 表记录的数据区的空间分配情况。使用 4 个字节,32 位来标记每一个簇。若是空闲,则 32 位全是0。FAT 分配表一般有 2 张,主要是用 FAT1,FAT2则作为备份。它占据的总大小是 FAT表个数 * FAT表大小,单位是块。

一般来说分区除去 FAT 头和 FAT 表后剩余的空间都分配给数据区,数据区最顶层实际上是一个大文件夹,称之为根目录,其他的文件或者文件夹都是它的子项,根目录一般位于数据区的开头。

如何找到文件

  1. 将文件 Initial.bin 放到一个活动分区的根目录下,即分区表入口第一个字节为 0x80。BootLoader 找到其所在的活动分区后,就可以知道分区的起始块号。从这个块号开始,就是 FAT32 文件系统。

  2. Initial.bin 文件的实际位置,肯定是在 FAT 数据区。所以 BootLoader 第二步,就是要找到 FAT 数据区的起始块号。算法就是计算数据区前面那些数据所占的空间。文件起始块号 = 分区前面已使用的块数 + FAT保留块数(FAT 头?) + FAT表个数*表大小。所有数据都可以在 FAT 头里面找到。

  3. 文件在 FAT 数据区的保存机制,是记录文件的文件名及起始簇号。注意,只有数据区是以簇为单位管理的。根据文件名获取簇号后,从起始簇号开始的块,就是文件的实际内容。每个文件条目会占据 32 个字节,包括文件名、后缀名、起始簇号等。只要 32 字节一组去匹配前 12 个字节就可以精确查找文件。

  4. 根据文件起始簇号,计算文件起始块号。簇的相对起始位置是数据区开始位置,不是从硬盘 0 号块开始。文件的块号 = 数据区起始块号 + (文件起始簇号-2) * 每簇块数。减 2 的原因是,簇号从 2 开始。

  1. 把 Initial.bin 读取到内存然后运行。这里需要用到另外一个参数即文件长度,它的单位是字节。所以要先获取文件长度,然后除以 512 得到以块为单位的长度。

实操找到文件的过程

将虚拟磁盘分区如下所示:

使用 WinHex 打开磁盘,可以看到其 MBR 分区表如下所示:

红色方框中的16字节表示第一个分区(H盘),绿色方框中的16字节表示第二个分区(I盘)。

对照硬盘分区结构信息表,第一个分区的起始扇区起始相对扇区号为 0x00000080。

进入第一个分区,如所示:

这90个字节包含了要计算的所有信息。

分区前已使用的块数在偏移 0x1c - 0x1f 这四个字节,值为 0x00000080;FAT 保留块数在 0x0e - 0x0f 这两个字节,值为 0x184e;FAT 表个数在偏移 0x10 字节出,值为 0x0002;表大小在偏移 0x24 - 0x27 这4个字节,值为0x000003d9;每簇的块数,在0x0d字节处,值为0x04;每块的字节数,在0x0b - 0x0c 处,值为0x0200,也就是512字节。

数据起始块号=0x80 + 0x184e + 0x02 * 0x3d9 = 0x2080 = 128 + 6222 + 2 * 985 = 8320

在 WinHex 中按 Ctrl + G 跳转到 8320 块处,这里即是第一簇(2号簇),也是根目录所在地。

计算 Initial.bin 的起始块号。

起始簇号由 4 个字节,32位表示,但却并不是连续的。偏移 0x1a - 0x1b 是低 16 位,值为 0x0006,偏移 0x14 - 0x15 是高 16 位,值为 0x0000。

即,起始簇号 = 0x06
相对起始块号 = (6-2) * 4 = 16
文件起始块号 = 8320 + 16 = 8336

计算以块为单位的文件长度,最后 4 个字节 0x00000500 表示文件长度,单位为字节。 0x50 = 1280, 1280÷512=21280 \div 512 = 2 余 256。因此文件占 3 个块。

按住 Ctrl + G 跳转到 8336 块处

肉眼可见,该文件占了3个块。