LoginSignup
6
5

More than 5 years have passed since last update.

Linux(x86-32bit)のページフォルトハンドラを読んでみる(その6)

Posted at

今回やること

どうもです。@akachochinです。
前回読んだdo_linear_fault()の続きです。
do_linear_fault()は前回の表を見るとわかりますが、バッキングストアあり/物理メモリ未割り当ての領域に対するページフォルトをハンドリングする初理です。

COWについては前回やりましたので、残り2つ(Read/Shared Write)について見たいと思います。

まずは、do_read_fault()

バッキングストアありの領域/物理メモリ未割付/アクセスreadのときに以下の関数が呼ばれます。

mm/memory.c
static int do_read_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)
{
  /* 略 */
  ret = __do_fault(vma, address, pgoff, flags, &fault_page);
  if (unlikely(ret & (VM_FAULT_ERROR | VM_FAULT_NOPAGE | VM_FAULT_RETRY)))
    return ret;

  /* 略 */
  do_set_pte(vma, address, fault_page, pte, false, false);
  /* 略 */

  return ret;
}

__do_fault()で本筋の処理を行い、アドレス解決した結果をdo_set_pteでPTEに書き込むというところが大筋だとわかります。

次は、do_shared_fault()

バッキングストアありの領域/物理メモリ未割付/アクセスwriteのときに以下の関数が呼ばれます。
バッキングストアを共有しているので、書いた内容は他の共有者からも見えることになります。なので「shared」なのです。この点がCOWと違うところです。

mm/memory.c
static int do_shared_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)
{
  /* 略 */
  ret = __do_fault(vma, address, pgoff, flags, &fault_page);
  if (unlikely(ret & (VM_FAULT_ERROR | VM_FAULT_NOPAGE | VM_FAULT_RETRY)))
    return ret;

  /*
   * Check if the backing address space wants to know that the page is
   * about to become writable
   */
  /* バッキングストアがpage_mkwriteというI/Fを持つ場合、それを
   * do_page_mkwrite()で呼び出す。
   * page_mkwriteは「ページが書き込み可能になることを通知すること
   * でバッキングストア側で必要な処理をさせたり拒否させたりする」
   * ためのI/F。詳細はmm/memory.cのdo_page_mkwrite()参照。
   */
  if (vma->vm_ops->page_mkwrite) {
    unlock_page(fault_page);
    tmp = do_page_mkwrite(vma, fault_page, address);
    if (unlikely(!tmp ||
        (tmp & (VM_FAULT_ERROR | VM_FAULT_NOPAGE)))) {
      page_cache_release(fault_page);
      return tmp;
    }
  }

  /* 略 */
  do_set_pte(vma, address, fault_page, pte, true, false);
  pte_unmap_unlock(pte, ptl);

  if (set_page_dirty(fault_page))
    dirtied = 1;
  mapping = fault_page->mapping;
  unlock_page(fault_page);
  /* 略 */
  /* file_update_time outside page_lock */
  /* バッキングストアにpage_mkwrite()がない場合、バッキングストア
   * に通知ができないので、せめて更新時刻だけでも変更する
   */
  if (vma->vm_file && !vma->vm_ops->page_mkwrite)
    file_update_time(vma->vm_file);

  return ret;
}

先のdo_read_fault()もdo_shared_fault()も肝心なところは__do_fault()で実施しているようだ。

__do_fault()

よって、__do_fault()を見る。

mm/fault.c
static int __do_fault(struct vm_area_struct *vma, unsigned long address,
    pgoff_t pgoff, unsigned int flags, struct page **page)
{
  struct vm_fault vmf;
  int ret;

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

  ret = vma->vm_ops->fault(vma, &vmf);
  if (unlikely(ret & (VM_FAULT_ERROR | VM_FAULT_NOPAGE | VM_FAULT_RETRY)))
    return ret; 

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

  if (unlikely(!(ret & VM_FAULT_LOCKED)))
    lock_page(vmf.page);
  else
    VM_BUG_ON_PAGE(!PageLocked(vmf.page), vmf.page);

  *page = vmf.page;
  return ret;
}

肝心のところは、ファイルのページャ(vma->vm_ops->fault)次第ということがわかります。
バッキングストアは各ファイル種別(Regular fileとかShared memoryなど)ごとに処理が違うためにこのようなことになっています。

次回

並行でAnonymous Memory(バッキングストアなし)の領域/物理メモリ未割付のケース、つまりdo_anonymous_page()を読みます。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5