The quiter you become,the more you are able to hear!

ARM64 Linux 内核虚拟地址空间

Author: geneblue

Blog: https://geneblue.github.io/

  • 原文地址
  • 翻译: GeneBlue

现在我们来讨论 64 位 ARM CPU 的 Linux 内核虚拟地址空间。在 ARM 官网上也可以找到一些关于 ARMv8 的信息。http://www.arm.com/products/processors/armv8-architecture.php

32 位 CPU 的最大问题就是虚拟地址空间只有 4GB 大小。即使一些平台支持 PAE (Physical Address Extension)扩展,该问题仍然存在,因为 PAE 主要关注物理地址的扩展问题,而不是虚拟地址。自从 64 位 CPU 问世后,情况就大为不同。AMD64 和 ARMv8 可以支持 2^64 次方的地址空间,这可是个相当大的数字。

实际上,2^64 次方太大了,Linux 内核只采用了 64 bits 的一部分(开启 CONFIG_ARM64_64K_PAGES 时使用 42 bits,页大小是 4K 时使用 39 bits),该文假设使用的页大小是 4K(VA_BITS = 39)

1
2
3
4
5
#ifdef CONFIG_ARM64_64K_PAGES
#define VA_BITS (42)
#else
#define VA_BITS (39)
#endif

ARM64 上也有足够的虚拟地址了,用户空间和内核空间可以有各自的 2^39 = 512GB 的虚拟地址。

所有用户虚拟地址前 25 bits 均为 0,所有的内核虚拟地址前 25 bits 均为 1。用户与内核之间的地址不可使用。

ARM64 虚拟地址空间布局

内核空间

可以通过源码来了解 ARM64 的内核虚拟地址空间。

在 arch/arm64/include/asm/memory.h,可以看出一些差异。首先,lowmem 区不存在了,这是因为虚拟地址很大了,可以处理 lowmem 内存,不用再担心虚拟地址问题(但内核地址仍然有一些限制)。其次,不同内核的虚拟地址顺序改变了。

1
2
3
4
5
6
7
8
9
#ifdef CONFIG_ARM64_64K_PAGES
#define VA_BITS (42)
#else
#define VA_BITS (39)
#endif
#define PAGE_OFFSET (UL(0xffffffffffffffff) << (VA_BITS - 1))
#define MODULES_END (PAGE_OFFSET)
#define MODULES_VADDR (MODULES_END - SZ_64M)
#define EARLYCON_IOBASE (MODULES_VADDR - SZ_4M)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        pr_notice("Virtual kernel memory layout:\n"                            
" vmalloc : 0x%16lx - 0x%16lx (%6ld MB)\n"
#ifdef CONFIG_SPARSEMEM_VMEMMAP
" vmemmap : 0x%16lx - 0x%16lx (%6ld MB)\n"
#endif
" modules : 0x%16lx - 0x%16lx (%6ld MB)\n"
" memory : 0x%16lx - 0x%16lx (%6ld MB)\n"
" .init : 0x%p" " - 0x%p" " (%6ld kB)\n"
" .text : 0x%p" " - 0x%p" " (%6ld kB)\n"
" .data : 0x%p" " - 0x%p" " (%6ld kB)\n",
MLM(VMALLOC_START, VMALLOC_END),
#ifdef CONFIG_SPARSEMEM_VMEMMAP
MLM((unsigned long)virt_to_page(PAGE_OFFSET),
(unsigned long)virt_to_page(high_memory)),
#endif
MLM(MODULES_VADDR, MODULES_END),
MLM(PAGE_OFFSET, (unsigned long)high_memory),

MLK_ROUNDUP(__init_begin, __init_end),
MLK_ROUNDUP(_text, _etext),
MLK_ROUNDUP(_sdata, _edata));

也可以看看 arch/arm64/mm/init.c 和 arch/arm64/include/asm/pgtable.h

pkmap 和 fixmap 不存在了,内核假设所有内存都有有效的内核虚拟地址,所以不再需要创建 pkmap 或者 fixmap。

ARM64 虚拟地址空间布局

用户空间

用户空间的虚拟地址布局和 ARM32 类似,因为用户地址空间增大到了 512GB ,所以可以在 64 位 CPU 上运行更大的应用。

ARMv8 是兼容 ARM 32 位应用的,所有的 32 位 ARM 应用都可以无需修改运行在 ARMv8 上。那在 64 位内核上,32 位应用的地址布局是怎样的呢?

实际上,64 位内核运行的进程都是 64 位的。为了运行 32 位 ARM 应用,Linux 内核仍然从 init 进程创建出一个 64 位的进程,当将其用户地址空间限制到 4GB。通过这种方式,64 位的 Linux 内核可以同时运行 32 位和 64 位的应用。

1
2
3
4
5
6
7
#ifdef CONFIG_COMPAT
#define TASK_SIZE_32 UL(0x100000000)
#define TASK_SIZE (test_thread_flag(TIF_32BIT) ? \
TASK_SIZE_32 : TASK_SIZE_64)
#else
#define TASK_SIZE TASK_SIZE_64
#endif /* CONFIG_COMPAT */

64 位 ARM 应用在 64 位 Linux 内核上的情况:

ARM64 64 位应用虚拟地址空间布局

32 位 ARM 应用在 64 位 Linux 内核上的情况:

ARM64 32 位应用虚拟地址空间布局

需要注意到,32 位应用仍然拥有 512GB 的内核虚拟地址空间,并且不与内核共享自己的 4GB 空间。但在 ARM32 上,32 位应用只有 3GB 的地址空间。

 | ARM32 Linux | ARM64 Linux

--- | --- | --- 32 位用户虚拟地址空间大小 | 3GB | 4GB 64 位用户虚拟地址空间大小 | N/A | 512GB 内核虚拟地址空间大小 | 1GB | 512GB