1. 程式人生 > >頁表管理

頁表管理

在不支援PAE的x86機器上,兩層頁表已足夠。中間頁目錄(PMD)被定義成大小為1,它在系統初始化時直接對映為全域性頁目錄(PGD),並在編譯時間以外的時段進行優化。

描述頁目錄

每個程序都有一個指向其自己PGD的指標(mm_struct->pgd),它其實就是一個物理頁面幀。該幀包括了一個pgd_t型別的陣列。在x86結構中,程序頁表的載入是通過把mm_struct->pgd複製到cr3暫存器完成,這種方式對TLB的重新整理有副作用。事實上,這種方式體現了在不同結構中__flush_tlb()的實現情況。
這裡寫圖片描述

描述頁表項

三層頁表中的每一個項PTEPMDPGD分別由pte_t

pmd_tpgd_t描述。他們實際上都是無符號的整型資料,之所以定義成結構,一方面是為了起到資料型別保護的作用,以使他們不會被濫用;另一方面是為了滿足某些特性,如在支援PAE的x86中,將有額外的4位用於大於4GB記憶體的定址。
未啟用PAE的x86結構下,定義如下

typedef struct { unsigned long pte_low; } pte_t;
typedef struct { unsigned long pmd; } pmd_t;
typedef struct { unsigned long pgd; } pgd_t;
typedef struct { unsigned
long pgprot; } pgprot_t;

在不支援PAE的x86中,pte_t是一個32位的整形指標。因為每個pte_t指向一個頁面幀的地址,而所有的被指向的地址都確定是頁對齊的。因此在32為值裡面有12位空閒出來用於描述這個頁表項的狀態位。
這裡寫圖片描述
Linux不會將PSE位(_PAGE_PROTNONE)用於使用者頁面,當某個頁面常駐記憶體,且核心不希望使用者訪問的時候,也就是當一個區域通過使用mprotect()函式設定PROT_NONE標誌保護起來的時候,實際上是將_PAGE_PRESENT置為0,將_PAGE_PROTNONE置為1。當使用巨集pte_present()檢查這兩位的情況時,只有核心自己知道這個頁是在記憶體中的,但對於使用者是不可訪問的。因為_PAGE_PRESENT

位被清零了,當用戶試圖訪問這個頁面時,會觸發一次缺頁異常,核心能夠強制安全檢查,以確定是要將頁面換出或終止程序。

使用頁表項

頁表遍歷操作:

pgd_t *pgd;
pmd_t *pmd;
pte_t *ptep, pte;
pgd=pgd_offset(mm, address);
if(pgd_none(*pgd)||pgd_bad(*pgd))
    goto out;
pmd=pmd_offset(pgd, address);
if(pmd_none(*pmd)||pmd_bad(*pmd))
    goto out;
ptep=pte_offset(pmd, address);
if(!ptep)
    goto out;
pte=*ptep;

核心頁表

對於x86結構,頁表初始化分為兩個階段。bootstrap階段設定頁表使之對映8MB的空間,使得分頁單元能被啟動;第二個階段初始化了剩餘的頁表。

  1. bootstrap
    arch/i386/kernel/head.S中的彙編函式startup_32()負責啟動分頁單元。vmlinuz中的所有核心程式碼都是以PAGE_OFFSET+1MiB為基址編譯的,核心實際上裝載在記憶體的0x00100000處(1MiB)。第一個MiB被用於BIOS與某些裝置通訊。在啟動分頁單元之前,bootstrap中的程式碼通過減去__PAGE_OFFSET將1MiB視為基址。因此在分頁單元啟動之前,需要建立一個頁表對映轉換8MiB的實體記憶體到虛擬地址的PAGE_OFFSET
    頁表將虛擬地址的0x00000000~0x00800000和0xc00100000~0xc00900000對映到了0x00100000~0x00900000的實體地址處