今回やること
どうもです。@akachochinです。
前回書いたとおり、今回はdo_anonymous_page()を見ます。
do_anonymous_page()
do_anonymous_page()の実装を見ます。
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にあります。)
/* 略 */
/* 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のページ)を割り当てています。
/* 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が割り当てられていない場合は割り当てた上でそれを返します。割り当てられない場合はメモリ不足とみなし(というかそれ以外はきっとないだろう)エラーを返します。
/* Allocate our own private page. */
if (unlikely(anon_vma_prepare(vma)))
goto oom;
「movableなzone」についてはLinux(x86-32bit)のページフォルトハンドラを読んでみる(その5)で多少触れています。Anonymous Memoryの場合はこのzoneからページ割り当てを試みるようです。
page = alloc_zeroed_user_highpage_movable(vma, address);
if (!page)
goto oom;
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()が呼ばれます。
#define __alloc_zeroed_user_highpage(movableflags, vma, vaddr) \
alloc_page_vma(GFP_HIGHUSER | __GFP_ZERO | movableflags, vma, vaddr)
実際のページ割り当てを行うalloc_page_vma()は仮想記憶の仕組みを知る上で重要ですので、次回見たいと思います。
次に進むと、__SetPageUptodate()を呼んでいます。
やっていることは本質的にページの状態を変えるだけなので、詳細は略します。
/*
* The memory barrier inside __SetPageUptodate makes sure that
* preceeding stores to the page contents become visible before
* the set_pte_at() write.
*/
__SetPageUptodate(page);
static inline void __SetPageUptodate(struct page *page)
{
smp_wmb();
__set_bit(PG_uptodate, &(page)->flags);
}
先に進むと、cgroups系の関数を呼んでいます。
それはこの時点でページの割り当てができたので、資源に関する情報を更新していると思われます。
※最終的にcgroupsを理解することが目的で、そのために今は仮想記憶のデータ構造や主な処理を頭に入れています。
そのときまで楽しみはとっておきます。
if (mem_cgroup_charge_anon(page, mm, GFP_KERNEL))
goto oom_free_page;
最後に例のごとくPTEを更新して完了です。
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()について少しだけ
/**
* 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は以下のような実装になっています。
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()を見たいと思います。
ただ、別件があり、少し間が開くかもしれません。