本文在讨论虚拟地址布局,所采用的是如下的配置:
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的数量来计算的,这个根据编译宏会不一样,所以这边不计算了

完整地址内核空间布局

区域

起始地址

结束地址

大小

描述

线性映射区

0xFFFFFF8000000000

0xFFFFFFC000000000

256 GB

直接映射物理内存

模块区

0xFFFFFFC000000000

0xFFFFFFC080000000

128 MB

内核模块加载区域

内核镜像区

0xFFFFFFC080000000

KERNEL_END

动态

内核代码和数据

vmalloc 区

KERNEL_END

0xFFFFFFFDFE000000

动态

动态内存分配区域

固定映射区

FIXADDR_START

0xFFFFFFFDFE000000

动态

特殊用途固定地址

PCI I/O 区

0xFFFFFFFDFE800000

0xFFFFFFFDFF800000

16 MB

PCI 设备 I/O 映射

隔离区 1

0xFFFFFFFDFF800000

0xFFFFFFFE00000000

8 MB

保护间隙

VMEMMAP 区

0xFFFFFFFE00000000

0xFFFFFFFE10000000

256 MB

物理页元数据数组

隔离区 2

0xFFFFFFFE10000000

0xFFFFFFFFFFFFFFFF

3.75 GB

未使用空间

线性空间下移

从上一节中我们可以看到线性映射区域的地址位于 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文档。

虚拟地址空间总体分布