AArch64 Linux 上的内存布局

作者:Catalin Marinas <catalin.marinas@arm.com>

本文档描述了 AArch64 Linux 内核使用的虚拟内存布局。该架构允许使用 4KB 页大小最多 4 级转换表,以及使用 64KB 页大小最多 3 级转换表。

AArch64 Linux 使用 4KB 页配置的 3 级或 4 级转换表,允许用户和内核分别使用 39 位(512GB)或 48 位(256TB)虚拟地址。对于 64KB 页,仅使用 2 级转换表(允许 42 位(4TB)虚拟地址),但内存布局相同。

ARMv8.2 增加了对大虚拟地址空间的可选支持。这仅在使用 64KB 页大小时可用,并扩展了第一级转换中的描述符数量。

TTBRx 选择由虚拟地址的第 55 位给出。swapper_pg_dir 仅包含内核(全局)映射,而用户 pgd 仅包含用户(非全局)映射。swapper_pg_dir 地址写入 TTBR1,并且永远不会写入 TTBR0。

AArch64 Linux 内存布局,4KB 页 + 4 级(48 位)

 Start                 End                     Size            Use
 -----------------------------------------------------------------------
 0000000000000000      0000ffffffffffff         256TB          user
 ffff000000000000      ffff7fffffffffff         128TB          kernel logical memory map
[ffff600000000000      ffff7fffffffffff]         32TB          [kasan shadow region]
 ffff800000000000      ffff80007fffffff           2GB          modules
 ffff800080000000      fffffbffefffffff         124TB          vmalloc
 fffffbfff0000000      fffffbfffdffffff         224MB          fixed mappings (top down)
 fffffbfffe000000      fffffbfffe7fffff           8MB          [guard region]
 fffffbfffe800000      fffffbffff7fffff          16MB          PCI I/O space
 fffffbffff800000      fffffbffffffffff           8MB          [guard region]
 fffffc0000000000      fffffdffffffffff           2TB          vmemmap
 fffffe0000000000      ffffffffffffffff           2TB          [guard region]

AArch64 Linux 内存布局,64KB 页 + 3 级(具有硬件支持的 52 位)

 Start                 End                     Size            Use
 -----------------------------------------------------------------------
 0000000000000000      000fffffffffffff           4PB          user
 fff0000000000000      ffff7fffffffffff          ~4PB          kernel logical memory map
[fffd800000000000      ffff7fffffffffff]        512TB          [kasan shadow region]
 ffff800000000000      ffff80007fffffff           2GB          modules
 ffff800080000000      fffffbffefffffff         124TB          vmalloc
 fffffbfff0000000      fffffbfffdffffff         224MB          fixed mappings (top down)
 fffffbfffe000000      fffffbfffe7fffff           8MB          [guard region]
 fffffbfffe800000      fffffbffff7fffff          16MB          PCI I/O space
 fffffbffff800000      fffffbffffffffff           8MB          [guard region]
 fffffc0000000000      ffffffdfffffffff          ~4TB          vmemmap
 ffffffe000000000      ffffffffffffffff         128GB          [guard region]

4KB 页的转换表查找

+--------+--------+--------+--------+--------+--------+--------+--------+
|63    56|55    48|47    40|39    32|31    24|23    16|15     8|7      0|
+--------+--------+--------+--------+--------+--------+--------+--------+
          |        |         |         |         |         |
          |        |         |         |         |         v
          |        |         |         |         |   [11:0]  in-page offset
          |        |         |         |         +-> [20:12] L3 index
          |        |         |         +-----------> [29:21] L2 index
          |        |         +---------------------> [38:30] L1 index
          |        +-------------------------------> [47:39] L0 index
          +----------------------------------------> [55] TTBR0/1

64KB 页的转换表查找

+--------+--------+--------+--------+--------+--------+--------+--------+
|63    56|55    48|47    40|39    32|31    24|23    16|15     8|7      0|
+--------+--------+--------+--------+--------+--------+--------+--------+
          |        |    |               |              |
          |        |    |               |              v
          |        |    |               |            [15:0]  in-page offset
          |        |    |               +----------> [28:16] L3 index
          |        |    +--------------------------> [41:29] L2 index
          |        +-------------------------------> [47:42] L1 index (48-bit)
          |                                          [51:42] L1 index (52-bit)
          +----------------------------------------> [55] TTBR0/1

当使用没有虚拟化主机扩展的 KVM 时,虚拟机管理程序将 EL2 中的内核页映射到线性映射的固定(并且可能是随机的)偏移量。有关更多详细信息,请参阅 kern_hyp_va 宏和 kvm_update_va_mask 函数。MMIO 设备(例如 GICv2)会映射到 HYP idmap 页旁边,当为特定 CPU 启用 ARM64_SPECTRE_V3A 时,向量也会映射到该页旁边。

当使用带有虚拟化主机扩展的 KVM 时,不会创建额外的映射,因为主机内核直接在 EL2 中运行。

内核中的 52 位 VA 支持

如果存在 ARMv8.2-LVA 可选功能,并且我们使用 64KB 页大小运行;则可以将 52 位地址空间用于用户空间和内核地址。但是,任何支持 52 位的内核二进制文件也必须能够在早期启动时在硬件功能不存在的情况下回退到 48 位。

这种回退机制要求内核 .text 位于较高的地址,以便它们对于 48/52 位 VA 保持不变。由于 kasan 阴影是整个内核 VA 空间的一部分,因此 kasan 阴影的末尾也必须位于 48/52 位的内核 VA 空间的较高一半。(从 48 位切换到 52 位,kasan 阴影的末尾是不变的,并且取决于 ~0UL,而起始地址将“增长”到较低的地址)。

为了优化 phys_to_virt 和 virt_to_phys,PAGE_OFFSET 保持恒定为 0xFFF0000000000000(对应于 52 位),这避免了读取额外变量的需要。physvirt 偏移量和 vmemmap 偏移量在早期启动时计算,以启用此逻辑。

由于单个二进制文件将需要支持 48 位和 52 位 VA 空间,因此 VMEMMAP 的大小必须足够大,以容纳 52 位 VA,并且其大小也必须足够大,以容纳固定的 PAGE_OFFSET。

内核中的大多数代码都不需要考虑 VA_BITS,对于确实需要知道 VA 大小的代码,变量定义如下

VA_BITS 常数,最大 VA 空间大小

VA_BITS_MIN 常数,最小 VA 空间大小

vabits_actual 变量,实际 VA 空间大小

最大和最小大小可用于确保缓冲区的大小足够大,或者地址的位置足够接近“最坏”情况。

52 位用户空间 VA

为了保持与依赖于 ARMv8.0 VA 空间最大大小(48 位)的软件的兼容性,默认情况下,内核将从 48 位范围向用户空间返回虚拟地址。

软件可以通过指定大于 48 位的 mmap 提示参数来“选择”接收来自 52 位空间的 VA。

例如

maybe_high_address = mmap(~0UL, size, prot, flags,...);

还可以通过启用以下内核配置选项来构建从 52 位空间返回地址的调试内核

CONFIG_EXPERT=y && CONFIG_ARM64_FORCE_52BIT=y

请注意,此选项仅用于调试应用程序,不应在生产中使用。