linux 內存管理(9) -頁表實現

  • 瞭解linux 頁表實現

1.MMU/TLB/Cache概述

  • MMU:完成的工作就是虛擬地址到物理地址的轉換,可以讓系統中的多個程序跑在自己獨立的虛擬地址空間中,相互不會影響。程序可以對底層的物理內存一無所知,物理地址可以是不連續的,但是不妨礙映射連續的虛擬地址空間。

  • TLB:MMU工作的過程就是查詢頁表的過程,頁表放置在內存中時查詢開銷太大,因此專門有一小片訪問更快的區域用於存放地址轉換條目,用於提高查找效率。當頁表內容有變化的時候,需要清除TLB,以防止地址映射出錯。

  • Cache:處理器和存儲器之間的緩存機制,用於提高訪問速率,在ARMv8上會存在多級Cache,其中L1 Cache分爲指令Cache和數據Cache,在CPU Core的內部,支持虛擬地址尋址;L2 Cache容量更大,同時存儲指令和數據,爲多個CPU Core共用,這多個CPU Core也就組成了一個Cluster。

在這裏插入圖片描述
具體訪問流程:
在這裏插入圖片描述
2.虛擬地址到物理地址的轉換

  虛擬地址到物理地址的映射通過查表的機制來實現,ARMv8中,Kernel Space的頁表基地址存放在TTBR1_EL1寄存器中,User Space頁表基地址存放在TTBR0_EL0寄存器中,其中內核地址空間的高位爲全1,(0xFFFF0000_00000000 ~ 0xFFFFFFFF_FFFFFFFF),用戶地址空間的高位爲全0,(0x00000000_00000000 ~ 0x0000FFFF_FFFFFFFF)。

在這裏插入圖片描述

ARMv8中:

  • 虛擬地址支持
    64位虛擬地址中,並不是所有位都用上,除了高16位用於區分內核空間和用戶空間外,有效位的配置可以是:36, 39, 42, 47。這可決定Linux內核中地址空間的大小。比如使用的內核中有效位配置爲CONFIG_ARM64_VA_BITS=39,用戶空間地址範圍:0x00000000_00000000 ~ 0x0000007f_ffffffff,大小爲512G,內核空間地址範圍:0xffffff80_00000000 ~ 0xffffffff_ffffffff,大小爲512G。
  • 頁面大小支持
    支持3種頁面大小:4KB, 16KB, 64KB。
  • 頁表支持
    支持至少兩級頁表,至多四級頁表,Level 0 ~ Level 3。

  結合有效虛擬地址位, 頁面大小,頁表的級數,可以組合成不同的頁表映射方式。例如使用內核配置爲:39位有效位,4KB大小頁面,3級頁表,手冊描述了整個translation的過程:
在這裏插入圖片描述

  • 虛擬地址[63:39]用於區分內核空間與用戶空間,從而選擇不同的TTBRn寄存器來獲取Level 1頁表基地址;
  • 虛擬地址[38:30]放置Level 1頁表中的索引,從而找到對應的描述符地址並獲取描述符內容,根據描述符中的內容獲取Level 2頁表基地址;
  • 虛擬地址[29:21]Level 2頁表中的索引,從而找到對應的描述符地址並獲取描述符內容,根據描述符中的內容獲取Level 3頁表基地址;
  • 虛擬地址[20:12]Level 3頁表中的索引,從而找到對應的描述符地址並獲取描述符內容,根據描述符中的內容獲取物理地址的高36位,以4K地址對齊;
  • 虛擬地址[11:0]放置的是物理地址的偏移,結合獲取的物理地址高位,最終得到物理地址。

3.Linux頁表映射

3.1.ARM處理器查詢頁表

  32bit的Linux採用三級映射:PGD–>PMD–>PTE,64bit的Linux採用四級映射:PGD–>PUD–>PMD–>PTE,多了個PUD。

縮寫:
PGD:Page Global Directory、PUD:Page Upper Directory、PMD:Page Middle Directory、PTE:Page Table Entry。

在ARM32 Linux採用兩層映射,省略了PMD,除非定義 CONFIG_ARM_LPAE纔會使用3級映射。

  Linux虛擬內存三級管理由以下三級組成:

  • PGD: Page Global Directory (頁目錄)
  • PMD: Page Middle Directory (頁目錄)
  • PTE: Page Table Entry (頁表項)

  每一級有以下三個關鍵描述宏:

  • SHIFT
  • SIZE
  • MASK

如頁的對應描述爲:

/* PAGE_SHIFT determines the page size  asm/page.h */  
#define PAGE_SHIFT      12  
#define PAGE_SIZE       (_AC(1,UL) << PAGE_SHIFT)  
#define PAGE_MASK       (~(PAGE_SIZE-1))  

數據結構定義如下:

/* asm/page.h */  
typedef unsigned long pteval_t;  
  
typedef pteval_t pte_t;  
typedef unsigned long pmd_t;  
typedef unsigned long pgd_t[2];  
typedef unsigned long pgprot_t;  
  
#define pte_val(x)      (x)  
#define pmd_val(x)      (x)  
#define pgd_val(x)  ((x)[0])  
#define pgprot_val(x)   (x)  
  
#define __pte(x)        (x)  
#define __pmd(x)        (x)  
#define __pgprot(x)     (x)  

3.1.1. Page Directory (PGD and PMD)

  每個進程有它自己的PGD( Page Global Directory),它是一個物理頁,幷包含一個pgd_t數組。其定義見<asm/page.h>。 進程的pgd_t數據見 task_struct -> mm_struct -> pgd_t * pgd;

  ARM架構的PGD和PMD的定義如下<arch/arm/include/asm/pgtable.h>:

#define PTRS_PER_PTE  512    // PTE中可包含的指針<u32>數 (21-12=9bit)  
#define PTRS_PER_PMD  1  
#define PTRS_PER_PGD  2048   // PGD中可包含的指針<u32>數 (32-21=11bit)</p><p>#define PTE_HWTABLE_PTRS (PTRS_PER_PTE)  
#define PTE_HWTABLE_OFF  (PTE_HWTABLE_PTRS * sizeof(pte_t))  
#define PTE_HWTABLE_SIZE (PTRS_PER_PTE * sizeof(u32))
/*  
 * PMD_SHIFT determines the size of the area a second-level page table can map  
 * PGDIR_SHIFT determines what a third-level page table entry can map  
 */  
#define PMD_SHIFT  21  
#define PGDIR_SHIFT  21

虛擬地址SHIFT宏圖:
在這裏插入圖片描述
虛擬地址MASK和SIZE宏圖:
在這裏插入圖片描述
3.1.2 Page Table Entry

  PTEs, PMDs和PGDs分別由pte_t, pmd_t 和pgd_t來描述。爲了存儲保護位,pgprot_t被定義,它擁有相關的flags並經常被存儲在page table entry低位(lower bits),其具體的存儲方式依賴於CPU架構。

  每個pte_t指向一個物理頁的地址,並且所有的地址都是頁對齊的。因此在32位地址中有PAGE_SHIFT(12)位是空閒的,它可以爲PTE的狀態位。

PTE的保護和狀態位如下圖所示:
在這裏插入圖片描述

3.1.3. 如何通過3級頁表訪問物理內存

  爲了通過PGD、PMD和PTE訪問物理內存,其相關宏在asm/pgtable.h中定義。

• pgd_offset

根據當前虛擬地址和當前進程的mm_struct獲取pgd項的宏定義如下:

/* to find an entry in a page-table-directory */  
#define pgd_index(addr)     ((addr) >> PGDIR_SHIFT)  //獲得在pgd表中的索引  

#define pgd_offset(mm, addr)    ((mm)->pgd + pgd_index(addr)) //獲得pmd表的起始地址  

/* to find an entry in a kernel page-table-directory */  
#define pgd_offset_k(addr)  pgd_offset(&init_mm, addr)  

• pmd_offset

根據通過pgd_offset獲取的pgd 項和虛擬地址,獲取相關的pmd項(即pte表的起始地址)

/* Find an entry in the second-level page table.. */  
#define pmd_offset(dir, addr)   ((pmd_t *)(dir))   //即爲pgd項的值  

• pte_offset

根據通過pmd_offset獲取的pmd項和虛擬地址,獲取相關的pte項(即物理頁的起始地址)

#ifndef CONFIG_HIGHPTE  
#define __pte_map(pmd)      pmd_page_vaddr(*(pmd))  
#define __pte_unmap(pte)    do { } while (0)  
#else  
#define __pte_map(pmd)      (pte_t *)kmap_atomic(pmd_page(*(pmd)))  
#define __pte_unmap(pte)    kunmap_atomic(pte)  
#endif  

#define pte_index(addr)     (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))  

#define pte_offset_kernel(pmd,addr) (pmd_page_vaddr(*(pmd)) + pte_index(addr))  

#define pte_offset_map(pmd,addr)    (__pte_map(pmd) + pte_index(addr))  
#define pte_unmap(pte)          __pte_unmap(pte)  

#define pte_pfn(pte)        (pte_val(pte) >> PAGE_SHIFT)  
#define pfn_pte(pfn,prot)   __pte(__pfn_to_phys(pfn) | pgprot_val(prot))  

#define pte_page(pte)       pfn_to_page(pte_pfn(pte))  
#define mk_pte(page,prot)   pfn_pte(page_to_pfn(page), prot)  

#define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext)  
#define pte_clear(mm,addr,ptep) set_pte_ext(ptep, __pte(0), 0)

在這裏插入圖片描述

3.2.ARMV8 頁表映射

  • 39位有效位,4KB大小頁面,3級頁表
    在這裏插入圖片描述

3.3.代碼路徑:

  • arch/arm64/include/asm/pgtable-types.h:定義pgd_t, pud_t, pmd_t, pte_t等類型;
  • arch/arm64/include/asm/pgtable-prot.h:針對頁表中entry中的權限內容設置;
  • arch/arm64/include/asm/pgtable-hwdef.h:主要包括虛擬地址中PGD/PMD/PUD等的劃分,這個與虛擬地址的有效位及分頁大小有關,此外還包括硬件頁表的定義, TCR寄存器中的設置等;
  • arch/arm64/include/asm/pgtable.h:頁表設置相關;

Note:
當CONFIG_PGTABLE_LEVELS=4時:pgd–>pud–>pmd–>pte;
當CONFIG_PGTABLE_LEVELS=3時,沒有PUD頁表:pgd(pud)–>pmd–>pte;
當CONFIG_PGTABLE_LEVELS=2時,沒有PUD和PMD頁表:pgd(pud, pmd)–>pte

以39位爲例宏定義:
在這裏插入圖片描述

3.4.創建啓動頁表

  在彙編代碼階段的head.S文件中,負責創建映射關係的函數是create_page_tables。create_page_tables函數負責identity mapping和kernel image mapping。

  • identity map:是指把idmap_text區域的物理地址映射到相等的虛擬地址上,這種映射完成後,其虛擬地址等於物理地址。idmap_text區域都是一些打開MMU相關的代碼。
  • kernel image map:將kernel運行需要的地址(kernel txt、rodata、data、bss等等)進行映射。

3.4.1 idmap_pg_dir和swapper_pg_dir臨時頁表

  內核啓動過程中,在真正的物理內存尚未添加進系統,以及頁表還未初始化之前,爲了保證系統能正常運行,需要建立兩個臨時全局頁表:idmap_pg_dir和swapper_pg_dir。

__create_page_tables函數:

__create_page_tables:
	mov	x28, lr

	/*
	 * Invalidate the idmap and swapper page tables to avoid potential
	 * dirty cache lines being evicted.
	 */
	adrp	x0, idmap_pg_dir
	ldr	x1, =(IDMAP_DIR_SIZE + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE)
	bl	__inval_dcache_area

	/*
	 * Clear the idmap and swapper page tables.
	 */
	adrp	x0, idmap_pg_dir
	ldr	x1, =(IDMAP_DIR_SIZE + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE)
1:	stp	xzr, xzr, [x0], #16
	stp	xzr, xzr, [x0], #16
	stp	xzr, xzr, [x0], #16
	stp	xzr, xzr, [x0], #16
	subs	x1, x1, #64
	b.ne	1b

  其中兩個全局頁表的定義在arch/arm64/kernel/vmlinux.lds.S中,放置在BSS段之後:

. = ALIGN(PAGE_SIZE);
idmap_pg_dir = .;
. += IDMAP_DIR_SIZE;
swapper_pg_dir = .;
. += SWAPPER_DIR_SIZE;
/*  定義了連續的幾個頁,分別存放PGD,PMD,PTE等,連續在一起,這個也是head.S中填充的 */
#define SWAPPER_DIR_SIZE    (SWAPPER_PGTABLE_LEVELS * PAGE_SIZE)
#define IDMAP_DIR_SIZE      (IDMAP_PGTABLE_LEVELS * PAGE_SIZE)
  • idmap_pg_dir
    identify map,也就是物理地址和虛擬地址是相等的。爲什麼需要這麼一個映射呢?我們都知道在MMU打開之前,CPU訪問的都是物理地址,那麼當MMU打開後訪問的就是虛擬地址了,這段頁表的映射就是從CPU到打開MMU之前的這段代碼物理地址的映射,防止開啓MMU後,無法獲取頁表。可以從System.map文件中查看這些代碼:
    在這裏插入圖片描述

  • swapper_pg_dir
    Linux內核編譯後,kernel image是需要進行映射的,包括text,data等各種段。

彙編結束後的內存映射關係如下圖所示:
在這裏插入圖片描述
  當執行完上面的map之後,MMU就已經打開了並且開始進入C代碼運行階段,那麼下一步就要對dtb進行映射了。參考linux 內存管理(12) - 物理內存初始化

refer to

  • http://www.wowotech.net/memory_management/436.html
  • https://www.cnblogs.com/pengdonglin137/p/9157639.html
  • http://www.wowotech.net/armv8a_arch/create_page_tables.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章