处理非连续内存区访问

本文探讨了内核态下缺页异常处理的过程,特别是在非连续内存区的处理方式。重点介绍了vmalloc_fault()函数如何判断缺页异常地址的有效性,以及vmalloc_sync_one()函数同步主内核页表和进程页表的机制。
摘要由CSDN通过智能技术生成

回忆一下“缺页异常处理程序 ”,当出现缺页异常,并且是进程处于内核态,即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))。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值