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

## 今回やること
どうもです。@akachochinです。
[前回](http://qiita.com/akachochin/items/7ab3ea00b346bd4a3de1)書いたとおり、今回はdo_anonymous_page()を見ます。

## do_anonymous_page()

do_anonymous_page()の実装を見ます。

```c: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にあります。)

```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のページ)を割り当てています。

```c: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が割り当てられていない場合は割り当てた上でそれを返します。割り当てられない場合はメモリ不足とみなし(というかそれ以外はきっとないだろう)エラーを返します。

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

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

```c:mm/memory.c
  page = alloc_zeroed_user_highpage_movable(vma, address);
  if (!page)
    goto oom;
```

```c: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()が呼ばれます。

```c: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()を呼んでいます。
やっていることは本質的にページの状態を変えるだけなので、詳細は略します。

```c: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);
```

```c:include/linux/page-flags.h
static inline void __SetPageUptodate(struct page *page)
{
  smp_wmb();
  __set_bit(PG_uptodate, &(page)->flags);
}
```

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

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

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

```c: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()について少しだけ

```c: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()です。
[ちょっと前に書いた文書](http://qiita.com/akachochin/items/cbda5d83ec220295add5)には以下のような図を書きました(再掲)。

```
(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は以下のような実装になっています。

```c: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()を見たいと思います。
ただ、別件があり、少し間が開くかもしれません。