非连续内存区缺页异常处理
在进入正题之前先看看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,返回对应的结果。
------------------------------------
个人理解,欢迎拍砖。