每個進程通過一個指針(即進程的mm_struct→pgd)指向其專屬的頁全局目錄(PGD),該目錄本身存儲在一個物理頁框中。這個頁框包含一個類型為pgd_t的數組,該類型是與架構相關的數據結構,定義在<asm/page.h>頭文件中。頁表的加載方式因架構而異:在x86架構中,進程頁表通過將mm_struct→pgd復制到cr3寄存器來加載,這一操作會附帶刷新TLB(事實上,這正是架構相關代碼中flush_tlb()函數的實現原理)。
PGD表中的每個有效表項指向一個物理頁框,其中包含類型為pmd_t的頁中間目錄(PMD)條目數組。每個PMD條目進一步指向另一個頁框,其中包含類型為pte_t的頁表項(PTE),而這些PTE最終指向存儲實際用戶數據的物理頁框。若頁面已被換出到后備存儲,交換條目將存儲在PTE中,并由頁錯誤處理例程do_swap_page()使用,以定位包含頁面數據的交換條目。頁表布局如圖所示。
任意給定的線性地址可被分解為三部分:三個頁表層級內的偏移量以及實際頁面內的偏移量。為輔助地址分解,系統為每個頁表層級提供了一組三元宏定義——SHIFT(XX_SHIFT表示XX段的bit位數)、SIZE(XX_SIZE表示XX段的大小,一般等于2^XX_SHIF)和MASK(XX_MASK表示從線性地址中獲取XX段的掩碼,XX段以外的bit位都是0)。其中,SHIFT宏定義了該層級頁表映射的地址位長度
MASK宏可通過與線性地址進行按位與(AND)操作,屏蔽掉所有高位比特,常用于判斷地址是否在頁表某一層級對齊。SIZE宏則揭示了每個層級的表項所覆蓋的字節范圍。SIZE與MASK宏的關系如圖3.3所示。在這組三元宏中,僅需定義SHIFT值,其余兩者可通過計算得出。例如,x86架構的頁面層級宏定義如下:
#define PAGE_SHIFT??? 12
#define PAGE_SIZE??? (1UL << PAGE_SHIFT)
#define PAGE_MASK??? (~(PAGE_SIZE-1))
其中:
- PAGE_SHIFT表示線性地址中頁內偏移部分的比特長度(x86為12位)
- PAGE_SIZE通過2^PAGE_SHIFT計算得出(即頁大小)
- PAGE_MASK通過對PAGE_SIZE-1取反得到,用于對齊頁邊界時清零偏移位。若需強制地址按頁對齊,可使用PAGE_ALIGN()宏,其原理是先將地址加上PAGE_SIZE-1,再與PAGE_MASK按位與操作以消除頁偏移比特。
類似地:
- PMD_SHIFT表示頁中間目錄(PMD)層級映射的地址比特數,其對應的PMD_SIZE與PMD_MASK通過相同方式計算。
- PGDIR_SHIFT表示頁全局目錄(PGD)頂層映射的地址比特數,PGDIR_SIZE和PGDIR_MASK遵循相同規則。
最后,關鍵的三個宏是PTRS_PER_*系列,它們定義了各層級頁表的表項數量:
- PTRS_PER_PGD表示PGD中的指針數量(x86無PAE時為1,024)
- PTRS_PER_PMD對應PMD層(x86無PAE時為1)
- PTRS_PER_PTE對應底層PTE(x86無PAE時為1,024)
這些宏共同構建了Linux頁表體系的核心尋址機制。