20 處理非連續內存區訪問

回憶一下“缺頁異常處理程序”,當出現缺頁異常,並且是進程處於內核態,即do_page_fault()中的那個if (unlikely(address >= TASK_SIZE))分支語句後,將通過vmalloc_fault(address)判斷該發生缺頁異常的地址address是否處於非連續內存區:(arch/i386/mm/Fault.c)
static inline 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;

    /*
     * 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_paddr = read_cr3();
    pmd_k = vmalloc_sync_one(__va(pgd_paddr), address);
    if (!pmd_k)
        return -1;
    pte_k = pte_offset_kernel(pmd_k, address);
    if (!pte_present(*pte_k))
        return -1;
    return 0;
}

我們已經在“非連續內存區”博文中看到,內核在更新非連續內存區對應的頁表項時是非常獺惰的。事實上,vmalloc()和vfree()函數只把自己限制在更新主內核頁表(即頁全局目錄init_mm.pgd和它的子頁表)。

然而,一旦內核初始化階段結束,任何進程或內核線程便都不直接使用主內核頁表。因此,我們來考慮內核態進程對非連續內存區的第一次訪問。當把線性地址轉換爲物理地址時,CPU的內存管理單元肯定會遇到空的頁表項併產生一個缺頁。

但是,缺頁異常處理程序認識這種特殊情況,因爲異常發生在內核態且產生缺頁的線性地址大於TASK_SIZE。因此,vmalloc_fault()檢查相應的主內核頁表項:
pgd_paddr = read_cr3();
#define read_cr3() ({ /
    unsigned int __dummy; /
    __asm__ ( /
        "movl %%cr3,%0/n/t" /
        :"=r" (__dummy)); /
    __dummy; /
})


static inline pmd_t *vmalloc_sync_one(pgd_t *pgd, unsigned long address)
{
    unsigned index = pgd_index(address);
    pgd_t *pgd_k;
    pud_t *pud, *pud_k;
    pmd_t *pmd, *pmd_k;

    pgd += index;
    pgd_k = init_mm.pgd + index;

    if (!pgd_present(*pgd_k))
        return NULL;

    /*
     * set_pgd(pgd, *pgd_k); here would be useless on PAE
     * and redundant with the set_pmd() on non-PAE. As would
     * set_pud.
     */

    pud = pud_offset(pgd, address);
    pud_k = pud_offset(pgd_k, address);
    if (!pud_present(*pud_k))
        return NULL;

    pmd = pmd_offset(pud, address);
    pmd_k = pmd_offset(pud_k, address);
    if (!pmd_present(*pmd_k))
        return NULL;
    if (!pmd_present(*pmd))
        set_pmd(pmd, *pmd_k);
    else
        BUG_ON(pmd_page(*pmd) != pmd_page(*pmd_k));
    return pmd_k;
}

#define pte_offset_kernel(dir, address) /
    ((pte_t *) pmd_page_kernel(*(dir)) +  pte_index(address))
#define pte_index(address) /
        (((address) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
#define pmd_page_kernel(pmd) /
        ((unsigned long) __va(pmd_val(pmd) & PAGE_MASK))
#define pmd_val(x)    ((x).pmd)


把存放在cr3寄存器中的當前進程頁全局目錄的物理地址賦給局部變量pgd_paddr(內核不使用current->mm_pgd導出當前進程的頁全局目錄地址,因爲這種缺頁可能在任何時刻都發生,甚至在進程切換期間發生。),把與pgd_paddr相應的線性地址賦給局部變量pgd,並且把主內核頁全局目錄的線性地址賦給pgd_k局部變量。

如果產生缺頁的線性地址所對應的主內核頁全局目錄項爲空,即if(!pud_present(*pud_k)),則vmalloc_sync_one()返回NULL,vmalloc_fault()函數返回-1。否則,函數檢查與錯誤線性地址相對應的主內核頁上級目錄項和主內核頁中間目錄項。如果它們中有一個爲空,vmalloc_fault()就返回-1。

否則,就把主目錄項複製到進程頁中間目錄的相應項中(set_pmd(pmd, *pmd_k),如果PAE被激活,頁上級目錄項就不可能爲空;如果沒有激活PAE,設五頁中間目錄項的同時也就隱含設裏了頁上級目錄項。)。

隨後對頁表項重複上述整個操作(pte_k = pte_offset_kernel(pmd_k, address))。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章