1. 程式人生 > 實用技巧 >Linux記憶體管理:虛擬地址空間(AArch64)

Linux記憶體管理:虛擬地址空間(AArch64)

相關背景:

文章開始前,先聊聊相關的背景知識,我們知道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 <[email protected]>
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

為了直觀點,畫了幅圖:

地址空間的定義:

核心中劃分的這麼多區域,且都有自己對應的地址與大小,這些地址和大小在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。

from:https://zhuanlan.zhihu.com/p/207001939?utm_source=wechat_timeline