一、新内核变动
kernel变化的真快,之前我记得4.x的内核的内核空间的线性映射区位于内核空间的高地址处的128TB,且当前的博客和一些书籍也都还是这样介绍。可翻了翻kernel的Documentation/arm64/memory.rst文档,发现最新的kernel已将这128TB移到了内核空间的最低地址处了。具体是2019年8月的一个commit,如下:
commit 14c127c957c1c6070647c171e72f06e0db275ebf
Author: Steve Capper <steve.capper@arm.com>
Date: Wed Aug 7 16:55:14 2019 +0100arm64: mm: Flip kernel VA spaceIn order to allow for a KASAN shadow that changes size at boot time, onemust fix the KASAN_SHADOW_END for both 48 & 52-bit VAs and "grow" thestart address. Also, it is highly desirable to maintain the samefunction addresses in the kernel .text between VA sizes. Both of theserequirements 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 thekernel VA range and everything else in the higher ranges.
二、虚拟地址空间
内核 4.x 和 5.0 版本的虚拟地址空间分布:
新arm64 内存分布:我的内核版本是5.15
各体系架构处理器的虚拟地址空间的布局各不相同,下面是ARM64位处理器使用48位虚拟地址,4级页表,页面大小4KB时的layout:
Start End Size Use
-----------------------------------------------------------------------
0000000000000000 0000ffffffffffff 256TB user
ffff000000000000 ffff7fffffffffff 128TB kernel logical memory map
ffff800000000000 ffff9fffffffffff 32TB kasan shadow region
ffffa00000000000 ffffa00007ffffff 128MB bpf jit region
ffffa00008000000 ffffa0000fffffff 128MB modules
ffffa00010000000 fffffdffbffeffff ~93TB vmalloc
fffffdffbfff0000 fffffdfffe5f8fff ~998MB [guard region]
fffffdfffe5f9000 fffffdfffe9fffff 4124KB fixed mappings
fffffdfffea00000 fffffdfffebfffff 2MB [guard region]
fffffdfffec00000 fffffdffffbfffff 16MB PCI I/O space
fffffdffffc00000 fffffdffffdfffff 2MB [guard region]
fffffdffffe00000 ffffffffffdfffff 2TB vmemmap
ffffffffffe00000 ffffffffffffffff 2MB [guard region]
地址空间的定义:
内核中划分的这么多区域,且都有自己对应的地址与大小,这些地址和大小在kernel中哪里定义着呢?具体位于:arch/arm64/include/asm/memory.h。
#define PAGE_OFFSET (_PAGE_OFFSET(VA_BITS))
#define KIMAGE_VADDR (MODULES_END)
#define BPF_JIT_REGION_START (KASAN_SHADOW_END)
#define BPF_JIT_REGION_SIZE (SZ_128M)
#define BPF_JIT_REGION_END (BPF_JIT_REGION_START + BPF_JIT_REGION_SIZE)
#define MODULES_END (MODULES_VADDR + MODULES_VSIZE)
.....
- PAGE_OFFSET
内核线性映射区的起始地址,大小为128TB。 - KASAN_SHADOW_START
KASAN影子内存的起始虚拟地址,大小为32TB。为什么是32TB呢?因为KASAN通常使用1:8或1:16比例的内存来做影子内存,分别对应大小为256TB/8=32TB或256TB/16=16TB,这里表示的是1:8的情况所以是32TB。 - KIMAGE_VADDR
定义了内核镜像的链接地址,通过其定义"#define KIMAGE_VADDR (MODULES_END)"看出它整好位于modules区域的结尾处,即vmalloc区域的起始地址。vmlinux.ld.S文件设置链接地址时会用到它,start_kernel->paging_init->map_kernel会将内核镜像的各个段依次映射到该区域。 - VMALLOC_START
定义了vmalloc区域的起始地址,大小约等于93TB。记得之前ARM32可以通过bootargs去控制vmalloc区域的大小,不知道64还有没。但是有没有也没所谓了,毕竟64位的处理器上虚拟地址空间已不像32位处理器那么紧张。 - VMEMMAP_START
定义了vmemmap区域的起始地址,大小2TB。sparsemem内存模型中用来存放所有struct page的虚拟地址空间。
寄存器TTBR0和TTBR1:
本文讲到了内核地址空间和用户地址空间,这就不得不提一下ARM64相关的两个寄存器TTBR0和TTBR1。它们的功能类似于X86里的CR3寄存器用来存放进程的1级页表(PGD)的基地址。但不同的是ARM64使用了两个寄存器分别存放用户空间和内核空间的1级页表基地址。
我们知道所有进程的内核地址空间的页表是共用一套的,所以TTBR1中的内容不会改变,永远等于init_mm->swapper_pg_dir。但各个进程的用户空间的页表各自独立,那么TTBR0中的内容则等于各自进程的task_struct->mm_struct->pgd
最后提一下,处理器如何知道什么时候访问TTBR0,什么时候访问TTBR1呢?ARMv8手册中有提到,当CPU访问地址时,若地址的第63bit为1则自动使用TTBR1,为0则使用TTBR0。
备注: 各个版本的虚拟地址分布, 有一点差异,但是大致区域是一致。比如不同的地方:
上面的知乎网友的图, vmalloc_start 的地址是 0xffffa00000000000; 而我打印的 vmalloc_start 的地址是 0xffff800010000000;