APFS 文件系统探究

本文的创作初衷是因为我发现从底层详解 APFS 的资料很少,所以自己来进行了一些探究和整理。

一点说明

如果你在看 APFS 的文档或者其他内容,不要把高层级的分区理解成 Windows 中的分区。因为 APFS 里卷(Volume)才是显示在“访达”中的,在硬盘和卷之间还有一个容器的概念,一个容器可以包含多个卷,这不好和 Windows 中的分区概念对应。

APFS大致结构

APFS 内部结构大致如下:

APFS大致结构

上图只有一个卷,然后描述了这个卷中的各部分信息。其中“Storage for objects and file data”中的“objects”就包含了我们在访达中看到的那个存储硬盘,也就是一个卷。

“GUID partition table”的缩写便是很多人在 Windows 上熟知的 GPT(不是那个聊天机器人),而 GPT 分区则是 EFI 技术的一部分(EFI 又名为 UEFI,是 Intel 发明的)。

APFS 的每个容器是按照顺序排列在硬盘里的,并不会有空隙,这与 Windows 的 GPT 分区不一样。

APFS 文件系统探究_第1张图片

这也是为什么 Windows 上你只能扩展或调整最后一个分区的大小,而不能调整前面的盘(APFS 的空闲空间是在各个卷之间共同分享的,所以扩展起来很容易,如下图)。以及为什么 macOS 上新建 APFS 分区的时候需要很久很久(因为要把其他分区的文件挪到合适的地方)。

但系统是怎么知道硬盘上有这个容器的呢?顺序读取的话,第一个容器按顺序读下去就行了,但是第二个呢?

首先,系统不是通过顺序读取来发现容器的。而这些工作是硬盘最开始的 GUID partition table 的任务了,这部分会包含各个分区的起始和终止地址,用的时候跳转就行了,是一种目录式的结构。如下图(源自官方标准《GUID Partition Table (GPT) Disk Layout》):
APFS 文件系统探究_第2张图片

可以看到通过主要分区表或者备用分区表划分分区以及进行跳转。

如果你查看 APFS 格式硬盘的十六进制的话,就可以看到如下情况(需要注意“GPT Header”部分是从200那行开始的):

APFS 文件系统探究_第3张图片

可以看到内部开头的结构与其他 UEFI 格式的差不多。

Protective MBR

APFS 与其他一些 GPT 分区的文件系统不同的是:不论是不是启动盘,都会有 Protective MBR 部分。

Protective MBR 必须是使用 GPT 分区的硬盘上第一个逻辑块。这个分区的存在使得计算机认为这个硬盘是可以使用的,并且已被使用,就不会弹出弹窗问你需不需要进行分区、格式化等操作,EFI 本身是不会使用这个块的。

Protective MBR 部分如下:
APFS 文件系统探究_第4张图片

各部分内容含义如下(偏移量是从这部分开头开始算,而且也是十六进制的):

部分 字节偏移量 字节长度 内容
启动码 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 各部分如下:

APFS 文件系统探究_第5张图片

各部分内容含义如下(偏移量是从这部分开头开始算):

部分 字节偏移量 字节长度 内容
签名 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 分区实例数组中的第一个实例:

APFS 文件系统探究_第6张图片

部分 字节偏移量 字节长度 内容
分区类型 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》。

《数据恢复技术深度揭秘(第二版)》刘伟编著。

希望能帮到有需要的人~

你可能感兴趣的:(目前无法分类的小探究,macos,APFS)