进程分配用户空间或是文件或者设备文件空间映射函数分析(三)

我们在上篇文章中说了缺页中断处理函数handle_pte_fault()函数,里面涉及到了几个比较重要的函数我这里先讲述的是do_no_page(),在调用这个函数的时候,我们发现时判断了二级页表项entry,发现pte_none(entry=1时,我们就可以认为该页没有被进程访问过且没有映射磁盘文件。我们先来看下它的具体代码吧。

static int
do_no_page(struct mm_struct *mm, struct vm_area_struct *vma,
 unsigned long address, int write_access, pte_t *page_table, pmd_t *pmd)
{
 struct page * new_page;
 struct address_space *mapping = NULL;
 pte_t entry;
 int sequence = 0;
 int ret = VM_FAULT_MINOR;
 int anon = 0;

 if (!vma->vm_ops || !vma->vm_ops->nopage)//这里发现vma没有相应的操作函数或者操作函数里面没有nopage函数的存在时,没有磁盘文件的映射,说明下面的函数是匿名映射。
  return do_anonymous_page(mm, vma, page_table,
     pmd, write_access, address);//这个映射有两种情况,如果匿名页不可以写或是利用时,我们就把它简单的映射到物理页empty_zero_page上。最后返回的是次缺页标志。如果发现可以写时,首先调用函数anon_vma_prepare()函数。第一步通过vma->anon_vma找到匿名页线性区域结构链表,如果链表不为空时返回就是0。如果是空的话,我们就调用函数find_mergeable_anon_vma()去寻找不为空anon_vma。如果我们找到系统已有的匿名页描述结构体的话我们使allocated=NULL,locked=anon_vma.如果不行还是没找到的话,我们就跑到 anon_vma_cachep高速缓存中去申请一页内存页作为anon_vma结构体。如果找到匿名页的描述结构体后,我们把vma->anon_vma=anon_vma,同时把vma这个结构体再挂在匿名页线性空间结构链表上。结束了anon_vma_prepare()函数后,我们就会调用函数alloc_page_vma()向物理内存申请一页内存页,申请方式无非还是指定节点或是不指定节点申请物理内存页。接着又是调用pte_offset_map()这个函数确定address对应的二级也表中的哪个具体的页表项。最后用page_table指向它。mm->rss++;其中的rss表示对于这个进程,系统分配了多少物理内存页。entry = maybe_mkwrite(pte_mkdirty(mk_pte(page,vma->vm_page_prot)),vma);这里就是设置好二级页表描述符,这样我们在最后只需调用set_pte()就可以建立好映射了。
 pte_unmap(page_table);
 spin_unlock(&mm->page_table_lock);

 if (vma->vm_file) {//如果vm_file不为空则说明这个页已经映射了磁盘文件
  mapping = vma->vm_file->f_mapping;//把文件的地址空间结构指针赋给mapping
  sequence = atomic_read(&mapping->truncate_count);
 }
 smp_rmb();//内存屏蔽函数
retry:
 new_page = vma->vm_ops->nopage(vma, address & PAGE_MASK, &ret);//申请一页物理内存,new_page指向这页的结构体。
 if (new_page == NOPAGE_SIGBUS)
  return VM_FAULT_SIGBUS;
 if (new_page == NOPAGE_OOM)
  return VM_FAULT_OOM;
 if (write_access && !(vma->vm_flags & VM_SHARED)) {//如果要求可写,但是该页不是共享内存页时
  struct page *page;

  if (unlikely(anon_vma_prepare(vma)))//检测创建匿名线性区域对象struct anon_vma
   goto oom;
  page = alloc_page_vma(GFP_HIGHUSER, vma, address);//先申请一页空间
  if (!page)
   goto oom;
  copy_user_highpage(page, new_page, address);//把new_page的里面的内容复制到page里面
  page_cache_release(new_page);//释放new_page高速缓存
  new_page = page;
  anon = 1;//匿名标志置1
 }

 spin_lock(&mm->page_table_lock);
 if (mapping &&
       (unlikely(sequence != atomic_read(&mapping->truncate_count)))) {//如果该页真的是被磁盘文件映射了,这就要求mapping和sequence无误
  sequence = atomic_read(&mapping->truncate_count);
  spin_unlock(&mm->page_table_lock);
  page_cache_release(new_page);
  goto retry;//如果出现错误,我们把原来用vma->vm_ops->nopage()函数申请物理内存页给释放掉。我们要从新释放。
 }
 page_table = pte_offset_map(pmd, address);//如果该页不是被某磁盘文件映射了,我们其实直接可以来到这一步的。这里也就是获取address对应的二级描述符指针。
 if (pte_none(*page_table)) {//如果描述符不存在,说明该页没有被访问过。
  if (!PageReserved(new_page))
   ++mm->rss;
  flush_icache_page(vma, new_page);
  entry = mk_pte(new_page, vma->vm_page_prot);//设置二级描述符
  if (write_access)//如果要求可写
   entry = maybe_mkwrite(pte_mkdirty(entry), vma);//设置的描述符的方式不一样的。
  set_pte(page_table, entry);//在相应的页表项中填入二级描述符,这样就建立好映射了。
  if (anon) {//如果是匿名标志。
   lru_cache_add_active(new_page);
   page_add_anon_rmap(new_page, vma, address);
  } else
   page_add_file_rmap(new_page);//这里设置new_page->_mapcount变量,表示该物理内存页被几个二级页表项所调用的
  pte_unmap(page_table);
 } else {//说明该页已经被映射到物理页了。
  pte_unmap(page_table);
  page_cache_release(new_page);//既然已经有了物理内存页,我们就没有必要保留前面申请的物理内存页。这里释放它。
  spin_unlock(&mm->page_table_lock);
  goto out;
 }
 update_mmu_cache(vma, address, entry);//这个函数的主要作用是内存访问一致性处理,你会发现,如果我们是申请物理内存页,建立映射,这样一路下来的话,我们就需要在最后把address对应的指令和数据cache,按cache块大小使之无效,清空写缓存。
 spin_unlock(&mm->page_table_lock);
out:
 return ret;
oom:
 page_cache_release(new_page);
 ret = VM_FAULT_OOM;
 goto out;
}//这个函数最后成功的话都是返回VM_FAULT_MINOR标志的。

下面我要介绍的是,如果发现该页是属于非线性磁盘文件的映射时,我们调用函数do_file_page()

static int do_file_page(struct mm_struct * mm, struct vm_area_struct * vma,
 unsigned long address, int write_access, pte_t *pte, pmd_t *pmd)
{
 unsigned long pgoff;
 int err;

 BUG_ON(!vma->vm_ops || !vma->vm_ops->nopage);//如果发现时一个匿名映射时,说明没有磁盘文件映射,系统崩溃。
 if (!vma->vm_ops || !vma->vm_ops->populate ||
   (write_access && !(vma->vm_flags & VM_SHARED))) {
  pte_clear(pte);
  return do_no_page(mm, vma, address, write_access, pte, pmd);//如果发现没有vma->vm_ops->populate函数存在的话,我们只可以通过调用do_no_page来为我们申请物理页和建立好映射。
 }

 pgoff = pte_to_pgoff(*pte);//这里就是*pte>>2赋给pgoff,我个人认为是为了把原来的描述符的标志清除了。

 pte_unmap(pte);
 spin_unlock(&mm->page_table_lock);

 err = vma->vm_ops->populate(vma, address & PAGE_MASK, PAGE_SIZE, vma->vm_page_prot, pgoff, 0);//通过populate函数设置线性区域的线性地址空间对应所对应的页表项。
 if (err == -ENOMEM)
  return VM_FAULT_OOM;
 if (err)
  return VM_FAULT_SIGBUS;
 return VM_FAULT_MAJOR;//这个函数要是成功返回的话是主缺页标志。
}
下面介绍的函数是这样的,如果标志不为0,但是就是没有设置了L_PTE_PRESENT标志位的,说明该页已经被换出去的,我们调用do_swap_page()函数把它调回来。

static int do_swap_page(struct mm_struct * mm,
 struct vm_area_struct * vma, unsigned long address,
 pte_t *page_table, pmd_t *pmd, pte_t orig_pte, int write_access)
{
 struct page *page;
 swp_entry_t entry = pte_to_swp_entry(orig_pte);//这里函数调用就是为了下面的lookup_swap_cache做准备的。
 pte_t pte;
 int ret = VM_FAULT_MINOR;

 pte_unmap(page_table);
 spin_unlock(&mm->page_table_lock);
 page = lookup_swap_cache(entry);//swapper_space是置换页地址空间高速缓存,swapper_space.page_tree就是高速缓存中的基数树。通过radix_tree_lookup()函数,我们在这棵树中找到entry.val作为索引号的已经被换出的置换页。
 if (!page) {
   swapin_readahead(entry, address, vma);
   page = read_swap_cache_async(entry, vma, address);
  if (!page) {

   spin_lock(&mm->page_table_lock);
   page_table = pte_offset_map(pmd, address);
   if (likely(pte_same(*page_table, orig_pte)))
    ret = VM_FAULT_OOM;
   else
    ret = VM_FAULT_MINOR;
   pte_unmap(page_table);
   spin_unlock(&mm->page_table_lock);
   goto out;
  }

  ret = VM_FAULT_MAJOR;
  inc_page_state(pgmajfault);
  grab_swap_token();
 }

 mark_page_accessed(page);
 lock_page(page);
 spin_lock(&mm->page_table_lock);
 page_table = pte_offset_map(pmd, address);
 if (unlikely(!pte_same(*page_table, orig_pte))) {
  pte_unmap(page_table);
  spin_unlock(&mm->page_table_lock);
  unlock_page(page);
  page_cache_release(page);
  ret = VM_FAULT_MINOR;
  goto out;
 }
 swap_free(entry);
 if (vm_swap_full())
  remove_exclusive_swap_page(page);

 mm->rss++;
 pte = mk_pte(page, vma->vm_page_prot);
 if (write_access && can_share_swap_page(page)) {
  pte = maybe_mkwrite(pte_mkdirty(pte), vma);
  write_access = 0;
 }
 unlock_page(page);

 flush_icache_page(vma, page);
 set_pte(page_table, pte);
 page_add_anon_rmap(page, vma, address);

 if (write_access) {
  if (do_wp_page(mm, vma, address,
    page_table, pmd, pte) == VM_FAULT_OOM)
   ret = VM_FAULT_OOM;
  goto out;
 }
 update_mmu_cache(vma, address, pte);
 pte_unmap(page_table);
 spin_unlock(&mm->page_table_lock);
out:
 return ret;
}//由于这个函数涉及到很多内存页交换的问题,还涉及到不少的匿名区域,这样就使得这个函数我没法在这里很明白地去讲解,有待以后可以回来把它完成。
当我们遇到有些线性区域是不可写,但是此时又出现了写的要求时,我们会用到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, pte_t pte)
{
 struct page *old_page, *new_page;
 unsigned long pfn = pte_pfn(pte);//这里是获得页描述符pte对应的物理页的页号
 pte_t entry;

 if (unlikely(!pfn_valid(pfn))) {//如果这个也好超出了一定范围:((pfn) >= PHYS_PFN_OFFSET && (pfn) < (PHYS_PFN_OFFSET + max_mapnr))
  pte_unmap(page_table);
  printk(KERN_ERR "do_wp_page: bogus page at address %08lx/n",
    address);
  spin_unlock(&mm->page_table_lock);
  return VM_FAULT_OOM;//先是打印错误信息,接着我们返回VM_FAULT_OOM表示内存不足的标志。
 }
 old_page = pfn_to_page(pfn);//我们通过页号再找到对应的struct page结构体。

 if (!TestSetPageLocked(old_page)) {//判断这个内存页是否被锁。这里表明如果没锁
  int reuse = can_share_swap_page(old_page);//看看这页是否给别人用过,我们是通过old_page->_count变量传入page_count()后,我们会得知这个页被使用了几次了。
  unlock_page(old_page);
  if (reuse) {//这里表明页被人引用过了
   flush_cache_page(vma, address);//我们先使指令和数据cache无效
   entry = maybe_mkwrite(pte_mkyoung(pte_mkdirty(pte)),
           vma);//设置二级描述符,这里设置的描述符是可写的。
   ptep_set_access_flags(vma, address, page_table, entry, 1);//里面调用了set_pte()函数完成映射,还要刷新address所指线性区域的TLB
   update_mmu_cache(vma, address, entry);//如果page设置了PG_dcache_dirty标志位,按cache块清除address虚拟地址对应的数据和指令cache无效,清空缓存。其实这里就是内存访问一致性处理。
   pte_unmap(page_table);
   spin_unlock(&mm->page_table_lock);
   return VM_FAULT_MINOR;
  }
 }
 pte_unmap(page_table);
 if (!PageReserved(old_page))
  page_cache_get(old_page);//如果page没有设置页保留的话,我们使old_page->_count+1
 spin_unlock(&mm->page_table_lock);

 if (unlikely(anon_vma_prepare(vma)))//检测或者创建匿名区域的struct anon_vma对象结构体。
  goto no_new_page;
 new_page = alloc_page_vma(GFP_HIGHUSER, vma, address);//申请一页物理内存页
 if (!new_page)
  goto no_new_page;
 copy_cow_page(old_page,new_page,address);//把旧页的内容拷贝到新页上,如果address是映射到empty_zero_page页的话,就直接把新页全部清零。
 spin_lock(&mm->page_table_lock);
 page_table = pte_offset_map(pmd, address);//这里是求道address对应的二级描述符的虚拟地址
 if (likely(pte_same(*page_table, pte))) {//如果发现描述符不一样时
  if (PageAnon(old_page))
   mm->anon_rss--;//判断如果发现时匿名页时。自减
  if (PageReserved(old_page))
   ++mm->rss;//如果是保留页时。自增
  else
   page_remove_rmap(old_page);
  break_cow(vma, new_page, address, page_table);
  lru_cache_add_active(new_page);
  page_add_anon_rmap(new_page, vma, address);
  new_page = old_page;
 }//对于发现不一致,上面if里面的一系列处理在干什么,其实不难看懂,但是为什么这样做,希望理解得人可以留言。
 pte_unmap(page_table);
 page_cache_release(new_page);
 page_cache_release(old_page);
 spin_unlock(&mm->page_table_lock);
 return VM_FAULT_MINOR;

no_new_page:
 page_cache_release(old_page);
 return VM_FAULT_OOM;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值