两个不同的进程 虚拟地址相同_Linux内存管理:虚拟地址空间

442cad84d77b2cc2913b2abfa1c08767.png

相关背景:

文章开始前,先聊聊相关的背景知识,我们知道64位处理器的虚拟地址已经支持到了64bit,但是64位处理器的物理地址总线实际位宽并没有达到64bit,常用的地址线宽有39bit和48bit,最新的ARMv8.2架构也已经可以支持到52bit了。那为什么没有支持到64bit呢?以常用的48bit地址线宽举例,其最大寻址能力是2^48 bytes(即256TB内存),对于当今的个人电脑或服务器来说都是足够用的。再加上增加地址总线的宽度会给芯片设计上带来不小的难度,所以并没有一步到位搞成64bit。

本文主要介绍ARM64位处理器地址空间的布局。前文已提到地址总线宽度有39bit、48bit以及52bit,且64位处理器又支持3级或4级页表,页大小也可以配置成4KB或64KB,组合起来的话情况太多,本文为了简化,就基于最常用组合展开叙述:48bit地址总线,4级页表,页面大小4KB。 且不包括ARM64虚拟化模式和安全模式下的情况。

新内核变动:

再来说一下写这篇文章时的感慨吧,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 +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.

所以本文基于目前mainline的内核版本v5.9-rc2 展开叙述。

虚拟地址空间:

各体系架构处理器的虚拟地址空间的布局各不相同,下面是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]

注:以上layout来自内核文档Documentation/arm64/memory.rst。x86的位于Documentation/x86/x86_64/mm.txt

为了直观点,画了幅图:

b173c51417aa884570464bd47ad6b220.png

地址空间的定义:

内核中划分的这么多区域,且都有自己对应的地址与大小,这些地址和大小在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。

c6f1c237d5aba36da2c2a45139f8d159.png

原创文章,转载和引用请注明出处。

作者:Yann Xu

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值