用户空间缺页异常pte_handle_fault()分析(一)

2014-11-24 09:24:39 · 作者: · 浏览: 4

前面简单的分析了内核处理用户空间缺页异常的流程,进入到了handle_mm_fault()函数,该函数为触发缺页异常的地址address分配各级的页目录,也就是说现在已经拥有了一个和address配对的pte了,但是这个pte如何去映射物理页框,内核又得根据pte的状态进行分类和判断,而这个过程又会牵扯出一些其他的概念……这也是初读linux内核源码的最大障碍吧,在一些复杂的处理中,一个点往往可以延伸出一个面,容易让人迷失方向……因此后面打算分几次将这个函数分析完,自己也没有完全理解透,所以不到位的地方欢迎大家指出,一起交流~


static inline int handle_pte_fault(struct mm_struct *mm,
struct vm_area_struct *vma, unsigned long address,
pte_t *pte, pmd_t *pmd, unsigned int flags)
{
pte_t entry;
spinlock_t *ptl;

entry = *pte;
if (!pte_present(entry)) {//如果页不在主存中
if (pte_none(entry)) {//页表项内容为0,表明进程未访问过该页

/*如果vm_ops字段和fault字段都不为空,则说明这是一个基于文件的映射*/
if (vma->vm_ops) {
if (likely(vma->vm_ops->fault))
return do_linear_fault(mm, vma, address,
pte, pmd, flags, entry);
}
/*否则分配匿名页*/
return do_anonymous_page(mm, vma, address,
pte, pmd, flags);
}

/*属于非线性文件映射且已被换出*/
if (pte_file(entry))
return do_nonlinear_fault(mm, vma, address,
pte, pmd, flags, entry);

/*页不在主存中,但是页表项保存了相关信息,则表明该页被内核换出,则要进行换入操作*/
return do_swap_page(mm, vma, address,
pte, pmd, flags, entry);
}

...
...

}


首先要确定的一点就是pte对应的页是否驻留在主存中,因为pte有可能之前映射了页,但是该页被换出了。上面的代码给出了pte对应的页没有驻留在主存中的情况。如果pte对应的页没有驻留在主存中,且没有映射任何页,即pte_present()返回0,pte_none()返回0,则要判断要分配一个匿名页还是一个映射页。在Linux虚拟内存中,如果页对应的vma映射的是文件,则称为映射页,如果不是映射的文件,则称为匿名页。两者最大的区别体现在页和vma的组织上,因为在页框回收处理时要通过页来逆向搜索映射了该页的vma。对于匿名页的逆映射,vma都是通过vma结构体中的vma_anon_node(链表节点)和anon_vma(链表头)组织起来,再把该链表头的信息保存在页描述符中;而映射页和vma的组织是通过vma中的优先树节点和页描述符中的mapping->i_mmap优先树树根进行组织的,具体可以参看ULK3。


来看基于文件的映射的处理:


static int do_linear_fault(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, pte_t *page_table, pmd_t *pmd,
unsigned int flags, pte_t orig_pte)
{
pgoff_t pgoff = (((address & PAGE_MASK)
- vma->vm_start) >> PAGE_SHIFT) + vma->vm_pgoff;

pte_unmap(page_table);//如果page_table之前用来建立了临时内核映射,则释放该映射
return __do_fault(mm, vma, address, pmd, pgoff, flags, orig_pte);
}


关键函数__do_fault():


static int __do_fault(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, pmd_t *pmd,
pgoff_t pgoff, unsigned int flags, pte_t orig_pte)
{
pte_t *page_table;
spinlock_t *ptl;
struct page *page;
pte_t entry;
int anon = 0;
int charged = 0;
struct page *dirty_page = NULL;
struct vm_fault vmf;
int ret;
int page_mkwrite = 0;

vmf.virtual_address = (void __user *)(address & PAGE_MASK);
vmf.pgoff = pgoff;
vmf.flags = flags;
vmf.page = NULL;

ret = vma->vm_ops->fault(vma, &vmf);//调用定义好的fault函数,确保将所需的文件数据读入到映射页

if (unlikely(ret & (VM_FAULT_ERROR | VM_FAULT_NOPAGE)))
return ret;

if (unlikely(PageHWPoison(vmf.page))) {
if (ret & VM_FAULT_LOCKED)
unlock_page(vmf.page);
return VM_FAULT_HWPOISON;
}

/*
* For consistency in subsequent calls, make the faulted page always
* locked.
*/
if (unlikely(!(ret & VM_FAULT_LOCKED)))
lock_page(vmf.page);
else
VM_BUG_ON(!PageLocked(vmf.page));

/*
* Should we do an early C-O-W break
*/
page = vmf.page;
if (flags & FAULT_FLAG_WRITE) {//写访问
if (!(vma->vm_flags & VM_SHARED)) {//私有映射,则要创建一个副本进行写时复制
anon = 1;// 标记为一个匿名映射