核心頁表和程序頁表
初學核心時,經常被“核心頁表”和“程序頁表”搞暈,不知道這到底是個啥東東,跟我們平時理解的頁表有和關係。。
核心頁表:即書上說的主核心頁表,在核心中其實就是一段記憶體,存放在主核心頁全域性目錄init_mm.pgd(swapper_pg_dir)中,硬體並不直接使用。
程序頁表:每個程序自己的頁表,放在程序自身的頁目錄task_struct.pgd中。
在保護模式下,從硬體角度看,其執行的基本物件為“程序”(或執行緒),而定址則依賴於“程序頁表”,在程序排程而進行上下文切換時,會進行頁表的切換:即將新程序的pgd(頁目錄)載入到CR3暫存器中。從這個角度看,其實是完全沒有用到“核心頁表”的,那麼“核心頁表”有什麼用呢?跟“程序頁表”有什麼關係呢?
1、核心頁表中的內容為所有程序共享,每個程序都有自己的“程序頁表”,“程序頁表”中對映的線性地址包括兩部分:
使用者態
核心態
其中,核心態地址對應的相關頁表項,對於所有程序來說都是相同的(因為核心空間對所有程序來說都是共享的),而這部分頁表內容其實就來源於“核心頁表”,即每個程序的“程序頁表”中核心態地址相關的頁表項都是“核心頁表”的一個拷貝。
2、“核心頁表”由核心自己維護並更新,在vmalloc區發生page fault時,將“核心頁表”同步到“程序頁表”中。以32位系統為例,核心頁表主要包含兩部分:
線性對映區
vmalloc區
其中,線性對映區即通過TASK_SIZE偏移進行對映的區域,對32系統來說就是0-896M這部分割槽域,對映對應的虛擬地址區域為TASK_SIZE-TASK_SIZE+896M。這部分割槽域在核心初始化時就已經完成對映,並建立好相應的頁表,即這部分虛擬記憶體區域不會發生page fault。
vmalloc區,為896M-896M+128M,這部分割槽域用於對映高階記憶體,有三種對映方式:vmalloc、固定、臨時,這裡就不像述了。。
以vmalloc為例(最常使用),這部分割槽域對應的線性地址在核心使用vmalloc分配記憶體時,其實就已經分配了相應的實體記憶體,並做了相應的對映,建立了相應的頁表項,但相關頁表項僅寫入了“核心頁表”,並沒有實時更新到“程序頁表中”,核心在這裡使用了“延遲更新”的策略,將“程序頁表”真正更新推遲到第一次訪問相關線性地址,發生page fault時,此時在page fault的處理流程中進行“程序頁表”的更新:
/* * 缺頁地址位於核心空間。並不代表異常發生於核心空間,有可能是使用者 * 態訪問了核心空間的地址。 */ if (unlikely(fault_in_kernel_space(address))) { if (!(error_code & (PF_RSVD | PF_USER | PF_PROT))) { //檢查發生缺頁的地址是否在vmalloc區,是則進行相應的處理 if (vmalloc_fault(address) >= 0) return;
/* * 對於發生缺頁異常的指標位於vmalloc區情況的處理,主要是將 * 主核心頁表向當前程序的核心頁表同步。 */ static noinline __kprobes int vmalloc_fault(unsigned long address) { unsigned long pgd_paddr; pmd_t *pmd_k; pte_t *pte_k; /* Make sure we are in vmalloc area: */ /* 區域檢查 */ if (!(address >= VMALLOC_START && address < VMALLOC_END)) return -1; WARN_ON_ONCE(in_nmi()); /* * Synchronize this task's top level page-table * with the 'reference' page table. * * Do _not_ use "current" here. We might be inside * an interrupt in the middle of a task switch.. */ /*獲取pgd(最頂級頁目錄)地址,直接從CR3暫存器中讀取。 *不要通過current獲取,因為缺頁異常可能在上下文切換的過程中發生, *此時如果通過current獲取,則可能會出問題*/ pgd_paddr = read_cr3(); //從主核心頁表中,同步vmalloc區發生缺頁異常地址對應的頁表 pmd_k = vmalloc_sync_one(__va(pgd_paddr), address); if (!pmd_k) return -1; //如果同步後,相應的PTE還不存在,則說明該地址有問題了 pte_k = pte_offset_kernel(pmd_k, address); if (!pte_present(*pte_k)) return -1; return 0; }