在 64 位硬件普及之后,处理更大地址空间(大于 232 字节)的需求变得显而易见。随着一些供应商现在提供配备 64TiB(或更多)内存的服务器,x86_64 和 arm64 现在允许寻址大于 248 字节的地址空间(通过默认的 48 位地址支持实现)。
x86_64 通过在硬件和软件中启用对五级页表的支持来解决这些用例。这使得能够寻址等于 257 字节的地址空间(详情请参阅 x86: 5 级分页启用,适用于 v4.12)。它将虚拟地址空间的限制提升至 128PiB,物理地址空间的限制提升至 4PiB。
arm64 通过引入两个新的架构扩展实现了同样的目标——ARMv8.2 LVA(大虚拟地址)和 ARMv8.2 LPA(大物理地址)。这些扩展允许 4PiB 的虚拟地址空间和 4 PiB 的物理地址空间(即,每个分别为 252 位)。
随着 ARMv8.2 架构扩展在新 arm64 CPU 中可用,这两个新的硬件扩展现在在开源软件中得到支持。
从 Linux 内核版本 5.4 开始,为 arm64 架构引入了 52 位(大)虚拟地址 (VA) 和物理地址 (PA) 支持。虽然 内核文档 描述了这些功能以及它们如何影响在新 CPU(不支持硬件中的 52 位 VA 扩展)和较新 CPU(支持硬件中的 52 位 VA 扩展)上运行的新内核,但对于普通用户来说,理解它们以及如何“选择加入”从 52 位空间接收 VA 可能很复杂。
因此,我将在本文中介绍这些相对较新的概念
- 在添加对这些功能的支持后,Arm64 的内核内存布局如何“翻转”
- 对用户空间应用程序的影响,特别是那些提供调试支持的应用程序(例如,kexec-tools、makedumpfile 和 crash-utility)
- 用户空间应用程序如何通过指定大于 48 位的 mmap 提示参数来“选择加入”从 52 位空间接收 VA
ARMv8.2 架构 LVA 和 LPA 扩展
ARMv8.2 架构提供了两个重要的扩展:大虚拟地址 (LVA) 和大物理地址 (LPA)。
当使用 64KB 转换粒度时,ARMv8.2-LVA 支持每个转换表基址寄存器的更大 VA 空间,最高可达 52 位。
ARMv8.2-LPA 允许
- 当使用 64KB 转换粒度时,中间物理地址 (IPA) 和 PA 空间更大,最高可达 52 位
- 如果实现支持 52 位 PA,则级别 1 块大小,其中块覆盖 64KB 转换粒度的 4TB 地址范围
请注意,这些功能仅在 AArch64 状态下受支持。
目前,以下 Arm64 Cortex-A 处理器支持 ARMv8.2 扩展
- Cortex-A55
- Cortex-A75
- Cortex-A76
有关更多详细信息,请参阅 Armv8 架构参考手册。
Arm64 上的内核内存布局
随着 ARMv8.2 扩展增加了对 LVA 空间的支持(仅在使用 64KB 页面大小时可用),描述符的数量在第一级转换中得到了扩展。
用户地址的位 63:48 设置为 0,而内核地址的相同位设置为 1。TTBRx 选择由虚拟地址的位 63 给出。swapper_pg_dir
仅包含内核(全局)映射,而用户 pgd
仅包含用户(非全局)映射。swapper_pg_dir
地址被写入 TTBR1,并且永远不会写入 TTBR0。
AArch64 Linux 内存布局,采用 64KB 页面和三级(52 位,具有硬件支持)
Start End Size Use
-----------------------------------------------------------------------
0000000000000000 000fffffffffffff 4PB user
fff0000000000000 fff7ffffffffffff 2PB kernel logical memory map
fff8000000000000 fffd9fffffffffff 1440TB [gap]
fffda00000000000 ffff9fffffffffff 512TB kasan shadow region
ffffa00000000000 ffffa00007ffffff 128MB bpf jit region
ffffa00008000000 ffffa0000fffffff 128MB modules
ffffa00010000000 fffff81ffffeffff ~88TB vmalloc
fffff81fffff0000 fffffc1ffe58ffff ~3TB [guard region]
fffffc1ffe590000 fffffc1ffe9fffff 4544KB fixed mappings
fffffc1ffea00000 fffffc1ffebfffff 2MB [guard region]
fffffc1ffec00000 fffffc1fffbfffff 16MB PCI I/O space
fffffc1fffc00000 fffffc1fffdfffff 2MB [guard region]
fffffc1fffe00000 ffffffffffdfffff 3968GB vmemmap
ffffffffffe00000 ffffffffffffffff 2MB [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
+-------------------------------------------------> [63] 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)
+-------------------------------------------------> [63] TTBR0/1

opensource.com
内核中的 52 位 VA 支持
由于具有 LVA 支持的较新内核应该在较旧的 CPU(不支持硬件中的 LVA 扩展)和较新的 CPU(支持硬件中的 LVA 扩展)上良好运行,因此选择的设计方法是拥有一个支持 52 位的单一二进制文件(并且如果硬件功能不存在,则必须能够在早期启动时回退到 48 位)。也就是说,VMEMMAP 的大小必须足够大,以容纳 52 位 VA,并且大小也必须足够大,以容纳固定的 PAGE_OFFSET
。
这种设计方法要求内核支持新虚拟地址空间的以下变量
VA_BITS constant the *maximum* VA space size
vabits_actual variable the *actual* VA space size
因此,虽然 VA_BITS
表示最大 VA 空间大小,但实际支持的 VA 空间(取决于启动时进行的切换)由 vabits_actual
指示。
翻转内核内存布局
保持单一内核二进制文件的设计方法要求内核 .text 位于较高的地址中,以便它们对于 48/52 位 VA 是不变的。由于内核地址清理器 (KASAN) 阴影是整个内核 VA 空间的一小部分,因此 KASAN 阴影的末尾也必须位于内核 VA 空间的较高一半,对于 48 位和 52 位都是如此。(从 48 位切换到 52 位,KASAN 阴影的末尾是不变的,并且取决于 ~0UL
,而起始地址将“增长”到较低的地址)。
为了优化 phys_to_virt()
和 virt_to_phys()
,PAGE_OFFSET
保持恒定在 0xFFF0000000000000
(对应于 52 位),这消除了对额外变量读取的需求。physvirt
和 vmemmap
偏移量在早期启动时计算,以启用此逻辑。
考虑以下物理与虚拟 RAM 地址空间转换
/*
* The linear kernel range starts at the bottom of the virtual address
* space. Testing the top bit for the start of the region is a
* sufficient check and avoids having to worry about the tag.
*/
#define virt_to_phys(addr) ({ \
if (!(((u64)addr) & BIT(vabits_actual - 1))) \
(((addr) & ~PAGE_OFFSET) + PHYS_OFFSET)
})
#define phys_to_virt(addr) ((unsigned long)((addr) - PHYS_OFFSET) | PAGE_OFFSET)
where:
PAGE_OFFSET - the virtual address of the start of the linear map, at the
start of the TTBR1 address space,
PHYS_OFFSET - the physical address of the start of memory, and
vabits_actual - the *actual* VA space size
对用于调试内核的用户空间应用程序的影响
几个用户空间应用程序用于调试正在运行/活动的内核或分析来自崩溃系统的 vmcore 转储(例如,确定内核崩溃的根本原因):kexec-tools、makedumpfile 和 crash-utility。
当这些应用程序用于调试 Arm64 内核时,由于 Arm64 内核内存映射被“翻转”,因此也会对它们产生影响。这些应用程序还需要执行转换表遍历,以确定与虚拟地址对应的物理地址(类似于内核中的做法)。
因此,用户空间应用程序必须进行修改,因为在内核内存映射中引入“翻转”后,它们在上游被破坏。
我已经在这三个受影响的用户空间应用程序中提出了修复;虽然一些已被上游接受,但其他一些仍在等待中
除非在用户空间应用程序中进行这些更改,否则它们在调试正在运行/活动的内核或分析来自崩溃系统的 vmcore 转储时仍然会损坏。
52 位用户空间 VA
为了保持与依赖 ARMv8.0 VA 空间最大大小为 48 位的用户空间应用程序的兼容性,内核默认情况下将从 48 位范围向用户空间返回虚拟地址。
用户空间应用程序可以通过指定大于 48 位的 mmap 提示参数来“选择加入”从 52 位空间接收 VA。
例如
.mmap_high_addr.c
----
maybe_high_address = mmap(~0UL, size, prot, flags,...);
也可以通过启用以下内核配置选项来构建返回 52 位空间地址的调试内核
CONFIG_EXPERT=y && CONFIG_ARM64_FORCE_52BIT=y
请注意,此选项仅用于调试应用程序,不应在生产环境中使用。
结论
总结一下
- 从 Linux 内核版本 5.14 开始,新的 Armv8.2 硬件扩展 LVA 和 LPA 现在在 Linux 内核中得到了很好的支持。
- 用于调试内核的用户空间应用程序(如 kexec-tools 和 makedumpfile)现在已损坏,正在等待上游修复被接受。
- 依赖于 Arm64 内核为其提供 48 位 VA 的旧版用户空间应用程序将继续按原样工作,而较新的用户空间应用程序可以通过指定大于 48 位的 mmap 提示参数来“选择加入”从 52 位空间接收 VA。
本文参考了 AArch64 Linux 上的内存布局 和 Linux 内核文档 v5.9.12。两者均根据 GPLv2.0 获得许可。
评论已关闭。