本文在讨论虚拟地址布局,所采用的是如下的配置:
kernel版本:5.15
CONFIG_ARM64_VA_BITS=39
CONFIG_ARM64_PA_BITS=48
PAGE_SHIFT = 12 (4KB 页面)
STRUCT_PAGE_MAX_SHIFT = 6 (默认值,struct page 大小 64 字节)
未启用 KASAN
Linux虚拟地址布局
在ARM64中,地址由32bit变为64bit,但是64bit并不是全用到了,最大支持48位物理地址,最大可支持256T的物理地址空间,对于目前的应用来讲完全足够了。
虚拟地址的宽度选择,有36bit、39bit、42bit、47bit、48bit、52bit等
config ARM64_VA_BITS
int
default 36 if ARM64_VA_BITS_36
default 39 if ARM64_VA_BITS_39
default 42 if ARM64_VA_BITS_42
default 47 if ARM64_VA_BITS_47
default 48 if ARM64_VA_BITS_48
default 52 if ARM64_VA_BITS_52
以30bit为例,用户空间和内核空间的大小均为512G.
用户空间范围:0x00000000_00000000 ~ 0x0000007F_FFFFFFFF
内核空间范围:0xFFFFFF80_00000000 ~ 0xFFFFFFFF_FFFFFFFF
内核虚拟地址空间分布
关于这部分我会根据代码中的定义来计算,并且最后会贴出一个具体的分布图。
代码定义位于:arch/arm64/include/asm/memory.h
/*
* Size of the PCI I/O space. This must remain a power of two so that
* IO_SPACE_LIMIT acts as a mask for the low bits of I/O addresses.
*/
#define PCI_IO_SIZE SZ_16M
/*
* VMEMMAP_SIZE - allows the whole linear region to be covered by
* a struct page array
*
* If we are configured with a 52-bit kernel VA then our VMEMMAP_SIZE
* needs to cover the memory region from the beginning of the 52-bit
* PAGE_OFFSET all the way to PAGE_END for 48-bit. This allows us to
* keep a constant PAGE_OFFSET and "fallback" to using the higher end
* of the VMEMMAP where 52-bit support is not available in hardware.
*/
#define VMEMMAP_SHIFT (PAGE_SHIFT - STRUCT_PAGE_MAX_SHIFT)
#define VMEMMAP_SIZE ((_PAGE_END(VA_BITS_MIN) - PAGE_OFFSET) >> VMEMMAP_SHIFT)
/*
* PAGE_OFFSET - the virtual address of the start of the linear map, at the
* start of the TTBR1 address space.
* PAGE_END - the end of the linear map, where all other kernel mappings begin.
* KIMAGE_VADDR - the virtual address of the start of the kernel image.
* VA_BITS - the maximum number of bits for virtual addresses.
*/
#define VA_BITS (CONFIG_ARM64_VA_BITS)
#define _PAGE_OFFSET(va) (-(UL(1) << (va)))
#define PAGE_OFFSET (_PAGE_OFFSET(VA_BITS))
#define KIMAGE_VADDR (MODULES_END)
#define MODULES_END (MODULES_VADDR + MODULES_VSIZE)
#define MODULES_VADDR (_PAGE_END(VA_BITS_MIN))
#define MODULES_VSIZE (SZ_128M)
#define VMEMMAP_START (-(UL(1) << (VA_BITS - VMEMMAP_SHIFT)))
#define VMEMMAP_END (VMEMMAP_START + VMEMMAP_SIZE)
#define PCI_IO_END (VMEMMAP_START - SZ_8M)
#define PCI_IO_START (PCI_IO_END - PCI_IO_SIZE)
#define FIXADDR_TOP (VMEMMAP_START - SZ_32M)
#if VA_BITS > 48
#define VA_BITS_MIN (48)
#else
#define VA_BITS_MIN (VA_BITS)
#endif
#define _PAGE_END(va) (-(UL(1) << ((va) - 1)))
#define KERNEL_START _text
#define KERNEL_END _end
线性映射区域
#define _PAGE_OFFSET(va) (-(UL(1) << (va)))
#define PAGE_OFFSET (_PAGE_OFFSET(VA_BITS)) // = -(1 << 39)
PAGE_OFFSET = -(1 << 39) = 0xFFFFFF8000000000
#define _PAGE_END(va) (-(UL(1) << ((va) - 1)))
#define PAGE_END (_PAGE_END(VA_BITS_MIN)) // = -(1 << 38)
PAGE_END = -(1 << 38) = 0xFFFFFFC000000000
故:
线性区域大小:PAGE_END - PAGE_OFFSET = 0x4000000000
= 256 GB
线性区域范围:0xFFFFFF8000000000
~ 0xFFFFFFC000000000
模块区域
#define MODULES_VADDR (_PAGE_END(VA_BITS_MIN)) // = PAGE_END
#define MODULES_VSIZE (SZ_128M)
#define MODULES_END (MODULES_VADDR + MODULES_VSIZE)
MODULES_VADDR = 0xFFFFFFC000000000
MODULES_END = 0xFFFFFFC000000000 + 0x8000000
= 0xFFFFFFC080000000
故:
模块区域大小:128M
模块区域范围:0xFFFFFFC000000000
~ 0xFFFFFFC080000000
内核镜像区域 (Kernel Image)
#define KIMAGE_VADDR (MODULES_END)
#define KERNEL_START _text
#define KERNEL_END _end
起始地址 = 0xFFFFFFC080000000
结束地址 由实际内核大小决定(编译时确定)
VMEMMAP 区域
#define VMEMMAP_SHIFT (PAGE_SHIFT - STRUCT_PAGE_MAX_SHIFT) // 12-6=6
#define VMEMMAP_SIZE ((PAGE_END - PAGE_OFFSET) >> VMEMMAP_SHIFT)
VMEMMAP_SIZE = (0xFFFFFFC000000000 - 0xFFFFFF8000000000) >> 6
= 0x10000000
= 256 MB
#define VMEMMAP_START (-(UL(1) << (VA_BITS - VMEMMAP_SHIFT))) // = -(1 << 33)
VMEMMAP_START = 0xFFFFFFFE00000000
VMEMMAP_END = 0xFFFFFFFE00000000 + 0x10000000
= 0xFFFFFFFE10000000
故:
VMEMMAP区域大小:VMEMMAP_SIZE = 256MB
VMEMMAP区域范围:0xFFFFFFFE00000000
~ 0xFFFFFFFE10000000
PCI I/O 区域
#define PCI_IO_SIZE SZ_16M
#define PCI_IO_END (VMEMMAP_START - SZ_8M)
#define PCI_IO_START (PCI_IO_END - PCI_IO_SIZE)
PCI_IO_END = 0xFFFFFFFE00000000 - 0x800000
= 0xFFFFFFFDFF800000
PCI_IO_START = 0xFFFFFFFDFF800000 - 0x1000000
= 0xFFFFFFFDFE800000
故:
PCI I/O区域大小:16MB
PCI I/O区域范围:0xFFFFFFFDFE800000
~ 0xFFFFFFFDFF800000
固定映射区域 (Fixed Mapping)
#define FIXADDR_TOP (VMEMMAP_START - SZ_32M)
#define FIXADDR_SIZE (__end_of_permanent_fixed_addresses << PAGE_SHIFT)
#define FIXADDR_START (FIXADDR_TOP - FIXADDR_SIZE)
FIXADDR_TOP = 0xFFFFFFFE00000000 - 0x2000000
= 0xFFFFFFFDFE000000
FIXADDR_START 是根据FIXMAP的数量来计算的,这个根据编译宏会不一样,所以这边不计算了
完整地址内核空间布局
线性空间下移
从上一节中我们可以看到线性映射区域的地址位于 0xFFFFFF80_00000000
~ 0xFFFFFFC0_00000000
,这个我们是通过源码计算得来的,但是我们在看一些书籍或者博客中介绍虚拟地址空间布局时,会得到大概下面的图:
这张图中的线性映射区域位于高地址处,而我们计算出的是位于内核虚拟地址的低地址处,难道我们计算的有问题???
其实不然!
2019年8月的一个commit,将线性映射区域 下移了。
commit 14c127c957c1c6070647c171e72f06e0db275ebf
Author: Steve Capper <steve.capper@arm.com>
Date: Wed Aug 7 16:55:14 2019 +0100
arm64: mm: Flip kernel VA space
In order to allow for a KASAN shadow that changes size at boot time, one
must fix the KASAN_SHADOW_END for both 48 & 52-bit VAs and "grow" the
start address. Also, it is highly desirable to maintain the same
function addresses in the kernel .text between VA sizes. Both of these
requirements necessitate us to flip the kernel address space halves s.t.
the direct linear map occupies the lower addresses.
This patch puts the direct linear map in the lower addresses of the
kernel VA range and everything else in the higher ranges.
可查看kernel的Documentation/arm64/memory.rst文档。