理解 Arm64 内核中的 52 位虚拟地址支持
在 64 位硬件可用后,处理更大地址空间(大于 232 字节)的需求变得明显。随着一些供应商现在提供具有 64TiB(或更多)内存的服务器,x86_64 和 arm64 现在允许寻址地址空间大于 248 字节(在默认的 48 位地址支持下可用)。 x86_64 通过在硬件和软件中支持五级页表来解决这些用例。这使得寻址地址空间等于 257 字节(有关详细信息,请参阅x86:v4.12的 5 级分页启
在 64 位硬件可用后,处理更大地址空间(大于 232 字节)的需求变得明显。随着一些供应商现在提供具有 64TiB(或更多)内存的服务器,x86_64 和 arm64 现在允许寻址地址空间大于 248 字节(在默认的 48 位地址支持下可用)。
x86_64 通过在硬件和软件中支持五级页表来解决这些用例。这使得寻址地址空间等于 257 字节(有关详细信息,请参阅x86:v4.12的 5 级分页启用)。它将限制提高到 128PiB 的虚拟地址空间和 4PiB 的物理地址空间。
arm64 通过引入两个新的架构扩展——ARMv8.2 LVA(大型虚拟寻址)和 ARMv8.2 LPA(大型物理寻址)实现了同样的目标。这些允许 4PiB 的虚拟地址空间和 4PiB 的物理地址空间(即,分别为 252 位)。
更多 Linux 资源
-
Linux 命令备忘单
-
高级 Linux 命令备忘单
-
免费在线课程:RHEL 技术概述
-
Linux 网络备忘单
-
SELinux 备忘单
-
Linux常用命令备忘单
-
什么是 Linux 容器?
-
我们最新的 Linux 文章
借助新的 arm64 CPU 中提供的 ARMv8.2 架构扩展,开源软件现在支持这两个新的硬件扩展。
从 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)。
ARMv8.2-LVA 在使用 64KB 转换颗粒时,每个转换表基址寄存器支持更大的 VA 空间,最高可达 52 位。
ARMv8.2-LPA 允许:
-
使用 64KB 转换颗粒时更大的中间物理地址(IPA)和高达 52 位的 PA 空间
-
如果实现支持 52 位 PA,则块覆盖 64KB 转换颗粒的 4TB 地址范围的 1 级块大小
请注意,这些功能仅在 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 位,硬件支持):
开始 结束 尺寸 使用
0000000000000000 000fffffffffffff 4PB 用户
fff0000000000000 fff7ffffffffffff 2PB 内核逻辑内存映射
fff8000000000000 \
fffda00000000000 ffff9fffffffffff 512TB kasan 影子区域
ffffa00000000000 ffffa00007ffffff 128MB bpf jit 区域
ffffa00008000000 模块
ffffa00010000000 fffff81ffffeffff ~88TB vmalloc
fffff81fffff0000 ~3TB
fffffc1ffe590000 fffffc1ffe9fffff 4544KB 固定映射
fffffc1ffea00000 \
fffffc1ffec00000 fffffc1fffbfffff 16MB PCI I/O 空间
fffffc1fffc00000 \
fffffc1ffffe00000 3968GB
ffffffffffe00000 \
使用 4KB 页面查找翻译表:
+--------+--------+--------+--------+--------+---- ----+--------+--------+
|63 56|55 48|47 40|39 32|31 24|23 16|15 8|7 0|
+--------+--------+--------+--------+--------+---- ----+--------+--------+
| | | | | |
| | | | | v
| | | | | [11:0] 页内偏移
| | | | +-> [20:12] 用于索引
| | | +-----------> [29:21] L2 索引
| | +---------------------> [38:30] L1 索引
| +-------------------------------> [47:39] L0 索引
+-------------------------------------------------- > [63] TTBR0/1
使用 64KB 页面查找翻译表:
+--------+--------+--------+--------+--------+---- ----+--------+--------+
|63 56|55 48|47 40|39 32|31 24|23 16|15 8|7 0|
+--------+--------+--------+--------+--------+---- ----+--------+--------+
| | | | |
| | | | v
| | | | [15:0] 页内偏移
| | | +----------> [28:16] L3 索引
| | +-------------------------> [41:29] L2 索引
| +-------------------------------> [47:42] L1 索引(48 位)
| [51:42] L1 索引(52 位)
+-------------------------------------------------- > [63] TTBR0/1
图片来源:
开源网站
内核中的 52 位 VA 支持
由于支持 LVA 的较新内核应该在较旧的 CPU(不支持硬件中的 LVA 扩展)和较新的 CPU(支持硬件中的 LVA 扩展)上运行良好,因此选择的设计方法是使用一个支持52 位(如果硬件功能不存在,则必须能够在早期启动时回退到 48 位)。也就是说,VMEMMAP 的大小必须足够大以容纳 52 位 VA,并且还必须足够大以容纳固定的PAGE_OFFSET
。
这种设计方法要求内核为新的虚拟地址空间支持以下变量:
VA_BITS 常量 *maximum* VA 空间大小
vabits_actual 变量 *actual* VA 空间大小
因此,虽然VA_BITS
表示最大 VA 空间大小,但实际支持的 VA 空间(取决于引导时进行的切换)由vabits_actual
指示。
翻转内核内存布局
保留单个内核二进制文件的设计方法要求内核 .text 位于较高的地址中,这样它们对于 48/52 位 VA 是不变的。由于 Kernel Address Sanitizer (KASAN) 影子是整个内核 VA 空间的一小部分,对于 48 位和 52 位,KASAN 影子的末尾也必须位于内核 VA 空间的上半部分。 (从 48 位切换到 52 位,KASAN 影子的结尾是不变的并且依赖于~0UL
,而起始地址将向低地址“增长”)。
为了优化phys_to_virt()
和virt_to_phys()
,PAGE_OFFSET
保持恒定在0xFFF0000000000000
(对应 52 位),这样就不需要额外的变量读取。physvirt
和vmemmap
偏移量在早期启动时计算以启用此逻辑。
考虑以下物理与虚拟 RAM 地址空间转换:
/*
* 线性内核范围从虚拟地址的底部开始
* 空间。测试区域开始的最高位是
* 充分检查并避免担心标签。
*/
#define virt_to_phys(addr) ({ \
if (!(((u64)addr) & BIT(vabits_actual - 1))) \
(((地址) & ~PAGE_OFFSET) + PHYS_OFFSET)
})
#define phys_to_virt(addr) ((unsigned long)((addr) - PHYS_OFFSET) | PAGE_OFFSET)
在哪里:
PAGE_OFFSET - 线性映射开始的虚拟地址,在
TTBR1 地址空间的开始,
PHYS_OFFSET - 内存开始的物理地址,和
vabits_actual - *actual* VA 空间大小
对用于调试内核的用户空间应用程序的影响
几个用户空间应用程序用于调试运行/实时内核或分析崩溃系统的 vmcore 转储(例如,确定内核崩溃的根本原因):kexec-tools、makedumpfile 和 crash-utility。
当这些用于调试 Arm64 内核时,也会对它们产生影响,因为 Arm64 内核内存映射会“翻转”。这些应用程序还需要执行转换表遍历以确定对应于虚拟地址的物理地址(类似于在内核中的完成方式)。
因此,必须修改用户空间应用程序,因为它们在内核内存映射中引入“翻转”后被上游破坏。
我在三个受影响的用户空间应用程序中提出了修复建议;虽然有些已被上游接受,但其他一些仍在等待中:
-
建议 makedumpfile 上游修复
-
建议 kexec-tools 上游修复
-
修复在崩溃实用程序中接受
除非在用户空间应用程序中进行这些更改,否则它们将在调试运行/实时内核或分析崩溃系统的 vmcore 转储时保持中断。
52 位用户空间 VA
为了保持与依赖 ARMv8.0 VA 空间最大大小为 48 位的用户空间应用程序的兼容性,默认情况下,内核会将虚拟地址从 48 位范围返回给用户空间。
用户空间应用程序可以通过指定大于 48 位的 mmap 提示参数“选择加入”从 52 位空间接收 VA。
例如:
.mmap_high_addr.c
----
也许_high_address \u003d 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 的旧用户空间应用程序将继续按原样工作,而较新的用户空间应用程序可以通过指定 mmap 提示参数“选择加入”从 52 位空间接收 VAs大于 48 位。
本文借鉴了AArch64 Linux上的内存布局和Linux内核文档v5.9.12。两者都在 GPLv2.0 下获得许可。
更多推荐
所有评论(0)