非连续内存区缺页异常处理

    在进入正题之前先看看vmalloc是怎么申请内存的(虽然在前面的文章中已经说过了)。管理vmalloc分配空间用到的数据结构是vm_struct。首先用slab分配一个vm_struct实例,然后从vm_struct链表中找到一个合适的位置准备插入这个实例。这个实例只是用来管理这块内存的,那下面就开始申请这些内存,也就是一页一页地从buddy system中分配单页来填充一个page数组(这就是vmalloc分配得到的内存)。那怎么访问这些分配的内存呢?下面就逐层地建立pgd、pmd、pte结构中的值。释放vmalloc申请的空间的过程就是逆过来的。

    下面进入正题!

    在逐层建立pgd、pmd、pte的时候并不触及当前进程的页表,因此当内核态的进程在访问非连续内存区时就会发生缺页(进程页表中的表项为空)。处理程序先检查这个缺页线性地址是否在主内核页表中(init_mm.pgd),如果发现就把对应的值拷贝到进程的页表项中,然后恢复进程的执行。

static int vmalloc_fault(unsigned long address)
{
unsigned
long pgd_paddr;
pmd_t
*pmd_k;
pte_t
*pte_k;
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;
}

    通过read_cr3函数可以获得当前进程页全局目录的物理地址。然后调用vmalloc_sync_one获得pmd_k,下面看一下这个函数的实现:

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;
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);
arch_flush_lazy_mmu_mode();
}
else
BUG_ON(pmd_page(
*pmd) != pmd_page(*pmd_k));
return pmd_k;
}

    从init_mm.pgd中取得对应的项,这样开始逐层检查address对应的项是否存在。直到检查到pmd的时候如果还存在的话就设置pmd然后返回。刚开始看的时候这里有点不明白:为什么只是设置pmd?pgd、pud就不管了吗?如果仔细想一想内核中多层页表的结构就清楚了,设置这些只是一些冗余的操作。但是再返回来想一下,如果设置了pgd的话还需要设置pmd吗?因为设置了pgd后找到的pmd也不是过去所能找到的那个pmd了。感慨一下自己的逻辑思维的漏洞百出。看完缺页处理之后对vmalloc分配内存的利用也有了进一步地理解。:)

    然后检查一下pte,返回对应的结果。

------------------------------------

个人理解,欢迎拍砖。

posted @ 2011-08-19 15:05  GG大婶  阅读(1851)  评论(1编辑  收藏  举报