/*如果不是写操作的话(即读操作),那么非常简单,把zero_pfn的二级页表条目赋给entry,因为这里已经是缺页异常的请求调页的处理,又是读操作,所以肯定是本进程第一次访问这个页,所以这个页里面是什么内容无所谓,分配个默认全零页就好,进一步推迟物理页的分配,这就会让entry带着zero_pfn跳到标号setpte*/
if (!(flags & FAULT_FLAG_WRITE)) {
entry = pte_mkspecial(pfn_pte(my_zero_pfn(address),
vma->vm_page_prot));
ptl = pte_lockptr(mm, pmd);
spin_lock(ptl);
/*如果这个缺页的虚拟地址对应的二级页表条目所映射的内容居然在内存中,直接跳到标号unlock准备解锁返回*/
if (!pte_none(*page_table))
goto unlock;
/*跳到标号setpte就是写二级页表条目的内容即映射内容,对于这类就是把entry即zero_pfn写进去了*/
goto setpte;
}
/*如果是写操作,就要分配一个新的物理页了*/
/* Allocate our own private page. */
/*这里为空函数*/
pte_unmap(page_table);
/*分配一个anon_vma实例,反向映射相关,可暂不关注*/
if (unlikely(anon_vma_prepare(vma)))
goto oom;
/*它将调用alloc_page,这个页被0填充*/
page = alloc_zeroed_user_highpage_movable(vma, address);
if (!page)
goto oom;
__SetPageUptodate(page);
/*空函数*/
if (mem_cgroup_newpage_charge(page, mm, GFP_KERNEL))
goto oom_free_page;
/*把该页的物理地址加属性的值赋给entry,这是二级页表映射内容的基础值*/
entry = mk_pte(page, vma->vm_page_prot);
/*如果是写访问,那么设置这个二级页表条目属性还要加入:脏且可写*/
if (vma->vm_flags & VM_WRITE)
entry = pte_mkwrite(pte_mkdirty(entry));
/*把page_table指向虚拟地址addr的二级页表条目地址*/
page_table = pte_offset_map_lock(mm, pmd, address, &ptl);
/*如果这个缺页的虚拟地址对应的二级页表条目所映射的内容居然在内存中,报错返回*/
if (!pte_none(*page_table))
goto release;
/*mm的rss成员加一,用于记录分配给本进程的物理页总数*/
inc_mm_counter(mm, anon_rss);
/*page_add_new_anon_rmap用于建立线性区和匿名页的反向映射,可暂不关注*/
page_add_new_anon_rmap(page, vma, address);
setpte:
/*给page_table这个二级页表条目写映射内容,内容是entry*/
set_pte_at(mm, address, page_table, entry);
/* No need to invalidate - it was non-present before */
/*更新MMU*/
update_mmu_cache(vma, address, entry);
unlock:
pte_unmap_unlock(page_table, ptl);
return 0;
release:
mem_cgroup_uncharge_page(page);
page_cache_release(page);
goto unlock;
oom_free_page:
page_cache_release(page);
oom:
return VM_FAULT_OOM;
}
结合上面的描述和源码注释应该比较容易能理解请求调页的原理和流程;
现在分析写时复制COW,对于写时复制,首先把握一点就是只有写操作时才有可能触发写时复制,所以首先总要判断异常flag是否含有标志FAULT_FLAG_WRITE,然后判断二级页表条目值是否含有L_PTE_WRITE标志,这是意味着这个物理页是否可写,如果不可写则说明应该进入写时复制流程,调用处理函数do_wp_page;
可见,COW的应用场合就是访问映射的页不可写,它包括两种情况,第一种是fork导致,第二种是如malloc后第一次对他进行读操作,获取到的是zero_pfn零页,当再次写时需要写时复制,共同特点都是虚拟地址的二级页表映射内容在内存中,但是对应的页不可写,在函数do_wp_page中对于这两种情况的处理基本相似的;
另外一个应该知道的是,如果该页只有一个进程在用,那么就直接修改这个页可写就行了,不要搞COW,总之,不到不得以的情况下是不会进行COW的,这也是内核对于COW使用的原则,就是尽量不使用;
函数do_wp_page源码如下:
static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, pte_t *page_table, pmd_t *pmd,
spinlock_t *ptl, pte_t orig_pte)
{
struct page *old_page, *new_page;
pte_t entry;
int reuse = 0, ret = 0;
int page_mkwrite = 0;
struct page *dirty_page = NULL; |