了解 Arm64 内核中的 52 位虚拟地址支持

64 位硬件的引入增加了处理更大地址空间的需求。
55 位读者喜欢这篇文章。
Puzzle pieces coming together to form a computer screen

Opensource.com

在 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 可能很复杂。

因此,我将在本文中介绍这些相对较新的概念

  1. 在添加对这些功能的支持后,Arm64 的内核内存布局如何“翻转”
  2. 对用户空间应用程序的影响,特别是那些提供调试支持的应用程序(例如,kexec-tools、makedumpfile 和 crash-utility)
  3. 用户空间应用程序如何通过指定大于 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 位),这消除了对额外变量读取的需求。physvirtvmemmap 偏移量在早期启动时计算,以启用此逻辑。

考虑以下物理与虚拟 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

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

结论

总结一下

  1. 从 Linux 内核版本 5.14 开始,新的 Armv8.2 硬件扩展 LVA 和 LPA 现在在 Linux 内核中得到了很好的支持。
  2. 用于调试内核的用户空间应用程序(如 kexec-tools 和 makedumpfile)现在已损坏,正在等待上游修复被接受。
  3. 依赖于 Arm64 内核为其提供 48 位 VA 的旧版用户空间应用程序将继续按原样工作,而较新的用户空间应用程序可以通过指定大于 48 位的 mmap 提示参数来“选择加入”从 52 位空间接收 VA。

本文参考了 AArch64 Linux 上的内存布局Linux 内核文档 v5.9.12。两者均根据 GPLv2.0 获得许可。

接下来阅读
标签
User profile image.
- 我是红帽内核团队的成员。 - 过去 14 年一直在研究引导加载程序和内核。 - 我为以下开源项目做出贡献:Linux、EFI/u-boot 引导加载程序和用户空间实用程序,如:kexec-tools 和 makedumpfile。 - 我也共同维护 crash-utility 工具

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.