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
请注意,此选项仅用于调试应用程序,不应在生产中使用。