本文的创作初衷是因为我发现从底层详解 APFS 的资料很少,所以自己来进行了一些探究和整理。
一点说明
如果你在看 APFS 的文档或者其他内容,不要把高层级的分区理解成 Windows 中的分区。因为 APFS 里卷(Volume)才是显示在“访达”中的,在硬盘和卷之间还有一个容器的概念,一个容器可以包含多个卷,这不好和 Windows 中的分区概念对应。
APFS大致结构
APFS 内部结构大致如下:
上图只有一个卷,然后描述了这个卷中的各部分信息。其中“Storage for objects and file data”中的“objects”就包含了我们在访达中看到的那个存储硬盘,也就是一个卷。
“GUID partition table”的缩写便是很多人在 Windows 上熟知的 GPT(不是那个聊天机器人),而 GPT 分区则是 EFI 技术的一部分(EFI 又名为 UEFI,是 Intel 发明的)。
APFS 的每个容器是按照顺序排列在硬盘里的,并不会有空隙,这与 Windows 的 GPT 分区不一样。
这也是为什么 Windows 上你只能扩展或调整最后一个分区的大小,而不能调整前面的盘(APFS 的空闲空间是在各个卷之间共同分享的,所以扩展起来很容易,如下图)。以及为什么 macOS 上新建 APFS 分区的时候需要很久很久(因为要把其他分区的文件挪到合适的地方)。
但系统是怎么知道硬盘上有这个容器的呢?顺序读取的话,第一个容器按顺序读下去就行了,但是第二个呢?
首先,系统不是通过顺序读取来发现容器的。而这些工作是硬盘最开始的 GUID partition table 的任务了,这部分会包含各个分区的起始和终止地址,用的时候跳转就行了,是一种目录式的结构。如下图(源自官方标准《GUID Partition Table (GPT) Disk Layout》):
可以看到通过主要分区表或者备用分区表划分分区以及进行跳转。
如果你查看 APFS 格式硬盘的十六进制的话,就可以看到如下情况(需要注意“GPT Header”部分是从200
那行开始的):
可以看到内部开头的结构与其他 UEFI 格式的差不多。
Protective MBR
APFS 与其他一些 GPT 分区的文件系统不同的是:不论是不是启动盘,都会有 Protective MBR 部分。
Protective MBR 必须是使用 GPT 分区的硬盘上第一个逻辑块。这个分区的存在使得计算机认为这个硬盘是可以使用的,并且已被使用,就不会弹出弹窗问你需不需要进行分区、格式化等操作,EFI 本身是不会使用这个块的。
Protective MBR 部分如下:
各部分内容含义如下(偏移量是从这部分开头开始算,而且也是十六进制的):
部分 | 字节偏移量 | 字节长度 | 内容 |
---|---|---|---|
启动码 | 0 | 440 | EFI不会使用这部分,440 的十六进制就是1b8 ,所以可以看到上图中1b8 和之前的部分全部是00 |
唯一MBR硬盘签名 | 440 | 4 | 没有被使用,设置为0 |
未知 | 444 | 2 | 没有被使用,设置为0 |
分区记录 | 446 | 16*4 | 四个MBR分区记录的数组。MBR最多就 4 个分区。 |
签名 | 510 | 2 | 设置为0xAA55 ,也就是图中的1f0 那行最后的55 aa |
保留块 | 512 | 逻辑块大小-512 | 逻辑块的剩余保留部分,设置为0 |
如果你对上面左侧的序号有疑问,需要记住这是十六进制的。比如说,最后的512
的十六进制便是200
。
GPT Header
GPT Header 是 UEFI 技术和 GPT 分区的核心部分,这部分存储了很多信息。GPT Header 各部分如下:
各部分内容含义如下(偏移量是从这部分开头开始算):
部分 | 字节偏移量 | 字节长度 | 内容 |
---|---|---|---|
签名 | 0 | 8 | 这部分是 ASCII 格式的字符串“EFI PART”,用 64 位编码,也就是上图中的45 46 49 20 50 41 52 54 |
修正版本 | 8 | 4 | GPT Header 的修正版本数,这个版本不等于 UEFI 指定版本。上文中的00 00 01 00 表示是版本 1.0 |
Header 尺寸 | 12 | 4 | GPT Header 的尺寸,单位是字节。这部分必须大于等于 92,小于等于逻辑块大小。上面的5C 00 00 00 表示 92 个字节,也就是最小尺寸。上图只有 60 个字节,是因为省略了最后的几个空行 |
Header的CRC32 | 16 | 4 | GPT Header 结构的 CRC32 校验和。先将这个值设置为 0,然后计算 GPT Header 结构的 32 位 CRC32 校验和,这里的校验和是15 51 0f ff |
保留部分 | 20 | 4 | 这部分必须全部为 0 |
MyLBA部分 | 24 | 8 | 这个 LBA(逻辑块地址)包含数据结构,验证 GPT 的时候会检查 MyLBA 实例指向的 GUID Partition Table 中的 LBA,上图中为01 00 00 00 00 00 00 00 |
AlternateLBA | 32 | 8 | 备用 GPT Header 的 LBA 地址,上图中为ff ff bf 46 07 00 00 00 |
第一个可使用LBA | 40 | 8 | GUID Partition Entry描述的一个分区的第一个被使用的可使用逻辑块,上图中为22 00 00 00 00 00 00 00 |
最后一个可使用 | 48 | 8 | GUID Partition Entry描述的一个分区的最后一个被使用的可使用逻辑块,上图中为de ff bf 46 07 00 00 00 |
硬盘GUID | 56 | 16 | 标识硬盘的 GUID,上图中为84 ec 00 4a 8e 8c fd 47 8a 78 25 48 bf a4 90 99 |
分区实例 LBA | 72 | 8 | GUID分区实例数组的开始 LBA,上图中为02 00 00 00 00 00 00 00 |
分区实例的数量 | 80 | 4 | GUID分区实例数组中分区实例的数量,上图中为80 00 00 00 ,也就是 8 个 |
分区实例的尺寸 | 84 | 4 | GUID分区实例数组中GUID分区实例结构体的尺寸,这部分应该设置成128 x 2n 的大小,n 是大于等于0 的整数,这部分一般是 128、256 等,但是早期的版本允许8 的任意倍数。上图中的80 00 00 00 实际上就是十六进制的80 ,也就是 128 个字节 |
分区实例数组的CRC32 | 88 | 4 | GUID分区实例数组的 CRC32 |
保留 | 92 | 剩下所有 | 保留块,全部为 0 |
GPT Partition Entry Array
GPT 分区实例数组(GPT Partition Entry Array)包含一个数组,这个数组存储了 GPT 分区实例。依旧先说明每个部分是什么,下面是 GPT 分区实例数组中的第一个实例:
部分 | 字节偏移量 | 字节长度 | 内容 |
---|---|---|---|
分区类型 GUID | 0 | 16 | 定义分区目标和类型的唯一ID。如果分区没有被使用,那么这部分为 0。上图中为:28 73 2a c1 1f f8 d2 11 ba 4b 00 a0 c9 3e c9 3b ,关于这部分的每个比特背后的含义后面细说 |
唯一分区 GUID | 16 | 16 | 每个分区实例唯一的 GUID。每个分区被创建时都会有这样一个唯一的 GUID,这个 GUID 也必须在 GPT 分区实例创建时分配值。上图中分配的为f8 56 1c 99 8d 6f 9e 4f bb fa 01 ca 52 57 cc 05 |
起始 LBA | 32 | 8 | 这个实例中定义的分区起始 LBA |
终点 LBA | 40 | 8 | 这个实例中定义的分区终点 LBA |
属性 | 48 | 8 | 这些属性位全部被 UEFI 保留,所以这里全部都是00 00 00 00 00 00 00 00 |
分区名称 | 56 | 72 | 一个包含人类可读名称的无终止符字符。由于这是一个 EFI 分区,所以显示的是EFI System Partition |
保留区域 | 128 | 之前设置的大小 ~ 128 | 这部分必须为 0 |
根据操作系统的不同、分区的不同,分区类型 GUID 的值也是不同的。比如说上面的这个 EFI 系统分区的 GUID 值就是28 73 2a c1 1f f8 d2 11 ba 4b 00 a0 c9 3e c9 3b
。有时你在其他资料里会看到是这样的:C12A7328-F81F-11D2-BA4B-00A0C93EC93B
,这里每个-
分隔开每个部分,加之上图中的值为小端数,你可以对应看看是一样的。
而 APFS 分区的分区类型 GUID 是ef 57 34 7c 00 00 aa 11 aa 11 00 30 65 43 ec ac
,或者说7C3457EF-0000-11AA-AA11-00306543ECAC
上面“属性”中每部分的含义如下:
位 | 名称 | 含义 |
---|---|---|
0 | 请求分区 | 如果设置了这一位,那么必须有这个分区才能使平台运行,删除或修改这个分区的内容可能会导致平台功能丢失、无法启动或运行。所以除非操作系统、软件或固件能识别这个分区,否则不应该删除或修改这个分区。 |
1 | 没有块IO传输协议 | 如果设置了此位,那么固件不能为此分区生成EFI_BLOCK_IO_PROTOCOL部分,不设置这部分,那么就不会在 UEFI 中为该分区创建文件系统的映射 |
2 | 传统 BIOS 可启动 | 这位留给具有传统 PC-AT BIOS 固件的系统实现通知特定受限的、特殊目标的软件运行在一个可用 GPT 分区启动的系统上 |
3~47 | 未定义,必须为 0,保留给未来的 UEFI 规范的扩展 | |
48~63 | 保留给 GUID 特定用途使用。这些位的使用根据分区类型 GUID 而设定。如果修改了 0~47 位中的任何一位,那么都必须保留这些位 |
可以看到,在 GPT 分区实例数组中的每个实例中,都标出了起始和终止点,于是就通过这些部分进行跳转和获取。
参考资料和扩展阅读
如果你想尝试查看上面的内容,那么可以按照我的这篇博客进行操作:《如何在Mac终端上,用十六进制查看某个硬盘(使用dd和hexdump,所以其他系统也可以使用这个方法)》。
关于 APFS 格式的实现细节可以参阅苹果对这篇文档:《Apple File System Reference》(“Apple File System”就是 APFS 的全称)。
UEFI 官方文档为:《GUID Partition Table (GPT) Disk Layout》。
《数据恢复技术深度揭秘(第二版)》刘伟编著。
希望能帮到有需要的人~