LoginSignup
12
11

More than 5 years have passed since last update.

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

Posted at

今回やること

どうもです。@akachochinです。
前回書いたとおり、今回はdo_anonymous_page()を見ます。

do_anonymous_page()

do_anonymous_page()の実装を見ます。

mm/memory.c
static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,
    unsigned long address, pte_t *page_table, pmd_t *pmd,
    unsigned int flags)
{

以下で呼び出している関数check_stack_guard_page()は「vmaで示される仮想アドレス空間を拡張する必要があるか、もしある場合は隣り合ったvmaが示す仮想アドレス空間とぶつからないか判断した上、拡張する必要があれば拡張をする」関数です。(実装はmm/memory.cにあります。)

mm/memory.c
/* 略 */
  /* Check if we need to add a guard page to the stack */
  if (check_stack_guard_page(vma, address) < 0)
    return VM_FAULT_SIGBUS;

先に進みます。
Anonymous Memoryに対するreadの場合はzero page(ページの中身が全0のページ)を割り当てています。

mm/memory.c
  /* Use the zero-page for reads */
  if (!(flags & FAULT_FLAG_WRITE)) {
    entry = pte_mkspecial(pfn_pte(my_zero_pfn(address),
            vma->vm_page_prot));
    page_table = pte_offset_map_lock(mm, pmd, address, &ptl);
    if (!pte_none(*page_table))
      goto unlock;
    goto setpte;
  }

zero pageの扱いというのも仮想記憶では興味深いところの一つです。
おそらくは、以下のように扱われていると予想します。

物理的にzero pageをたくさん作るわけでなく、ただひとつだけzero pageを用意し、readの場合はこれを全プロセスで共有します。zero pageはよく使われるので本当に使う数だけ作ると0クリアする手間など無駄です。
そして、write時に初めて別途物理ページを割り当てて必要な内容をコピーします。

zero pageのハンドリングを確認するのは次回以降とします。
(今回はzero pageが割り当てられる、というところに留めます。)

次に行くと、anon_vma_prepare()は、mm/rmap.cにある関数で、vma->anon_vmaを返します。
もし、anon_vmaが割り当てられていない場合は割り当てた上でそれを返します。割り当てられない場合はメモリ不足とみなし(というかそれ以外はきっとないだろう)エラーを返します。

mm/memory.c
  /* Allocate our own private page. */
  if (unlikely(anon_vma_prepare(vma)))
    goto oom;

「movableなzone」についてはLinux(x86-32bit)のページフォルトハンドラを読んでみる(その5)で多少触れています。Anonymous Memoryの場合はこのzoneからページ割り当てを試みるようです。

mm/memory.c
  page = alloc_zeroed_user_highpage_movable(vma, address);
  if (!page)
    goto oom;
include/linux/highmem.h
static inline struct page *
alloc_zeroed_user_highpage_movable(struct vm_area_struct *vma,
          unsigned long vaddr)
{
  return __alloc_zeroed_user_highpage(__GFP_MOVABLE, vma, vaddr);
}

最終的には以下のとおり、alloc_page_vma()が呼ばれます。

arch/x86/include/asm/page.h
#define __alloc_zeroed_user_highpage(movableflags, vma, vaddr) \
  alloc_page_vma(GFP_HIGHUSER | __GFP_ZERO | movableflags, vma, vaddr)

実際のページ割り当てを行うalloc_page_vma()は仮想記憶の仕組みを知る上で重要ですので、次回見たいと思います。

次に進むと、__SetPageUptodate()を呼んでいます。
やっていることは本質的にページの状態を変えるだけなので、詳細は略します。

mm/memory.c
  /*
   * The memory barrier inside __SetPageUptodate makes sure that
   * preceeding stores to the page contents become visible before
   * the set_pte_at() write.
   */
  __SetPageUptodate(page);
include/linux/page-flags.h
static inline void __SetPageUptodate(struct page *page)
{
  smp_wmb();
  __set_bit(PG_uptodate, &(page)->flags);
}

先に進むと、cgroups系の関数を呼んでいます。
それはこの時点でページの割り当てができたので、資源に関する情報を更新していると思われます。
※最終的にcgroupsを理解することが目的で、そのために今は仮想記憶のデータ構造や主な処理を頭に入れています。
そのときまで楽しみはとっておきます。

mm/memory.c
  if (mem_cgroup_charge_anon(page, mm, GFP_KERNEL))
    goto oom_free_page;

最後に例のごとくPTEを更新して完了です。

mm/memory.c
  entry = mk_pte(page, vma->vm_page_prot);
  if (vma->vm_flags & VM_WRITE)
    entry = pte_mkwrite(pte_mkdirty(entry));
  /* 略 */
  inc_mm_counter_fast(mm, MM_ANONPAGES);
  page_add_new_anon_rmap(page, vma, address);
setpte:
  set_pte_at(mm, address, page_table, entry);
/* 略 */
  return 0;
/* 以後はエラー処理なので略 */
}

page_add_new_anon_rmap()について少しだけ

mm/memory.c
/**
 * page_add_new_anon_rmap - add pte mapping to a new anonymous page
 * @page: the page to add the mapping to
 * @vma:  the vm area in which the mapping is added
 * @address:  the user virtual address mapped
 *
 * Same as page_add_anon_rmap but must only be called on *new* pages.
 * This means the inc-and-test can be bypassed.
 * Page does not have to be locked.
 */
void page_add_new_anon_rmap(struct page *page,
  struct vm_area_struct *vma, unsigned long address)
{
  VM_BUG_ON(address < vma->vm_start || address >= vma->vm_end);
  SetPageSwapBacked(page);
  atomic_set(&page->_mapcount, 0); /* increment count (starts at -1) */
  if (PageTransHuge(page))
    __inc_zone_page_state(page, NR_ANON_TRANSPARENT_HUGEPAGES);
  __mod_zone_page_state(page_zone(page), NR_ANON_PAGES,
      hpage_nr_pages(page));
  __page_set_anon_rmap(page, vma, address, 1);
  if (!mlocked_vma_newpage(vma, page)) {
    SetPageActive(page);
    lru_cache_add(page);
  } else
    add_page_to_unevictable_list(page);
}

ここで一番肝心なのは__page_set_anon_rmap()です。
ちょっと前に書いた文書には以下のような図を書きました(再掲)。

(Parents)vma->anon_vma_chain -- <same_vma> -- <same_vma>
                                 [avc]         [avc]
                               vma |         vma |
                                   |             |
                                   |             |
                                anon_vma      anon_vma
                                   |             |
                                   |             |
                               vma |         vma |
                                 [avc]         [avc]
(Child   )vma->anon_vma_chain -- <same_vma> -- <same_vma>

そして、__page_set_anon_rmapは以下のような実装になっています。

mm/rmap.c
static void __page_set_anon_rmap(struct page *page,
  struct vm_area_struct *vma, unsigned long address, int exclusive)
{
  struct anon_vma *anon_vma = vma->anon_vma;
/* 略 */
  anon_vma = (void *) anon_vma + PAGE_MAPPING_ANON;
  page->mapping = (struct address_space *) anon_vma;
  page->index = linear_page_index(vma, address);
}

○page構造体には、それが属するanon_vma(Anonnymous Virtual Memory Area)とanon_vmaが示すアドレス空間内でのオフセットが格納されます。
○anon_vmaは赤黒木につながっており、その中のデータ構造を経由してそれが属するvmaへと行き着くことができる。(mm/rmap.cのpage_mapped_in_vma()などを参照)

※lru_cache_add()でページキャッシュにつなぐのも、同一ページへの2回目以降のアクセスでunevictable_list()に移すのも重要そうですが、ここはまだ追わないことにします。
1つ追うと、3つ疑問点が出てくる状態で、すごく楽しいです。

次回やりたいこと

実際のページ割り当てを行うalloc_page_vma()を見たいと思います。
ただ、別件があり、少し間が開くかもしれません。

12
11
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
12
11