逆マッピング
Rev.4を表示中。最新版はこちら。
ユーザプロセスのページを回収する時、ページだけでなく、そのページを参照しているページテーブルエントリーも回収処理(ページが割り当てられていない等のフラグセット)をしなければなりません。しかしそれが共有ページで、他のプロセスに参照されているページだと、それがどのプロセスが参照しているか検索する必要があります。そのプロセスのページテーブルエントリーも回収処理しなければならないからです。そこで、ページからら参照しているページテーブルを取得する機能を逆マッピングと言うのだそうです。(逆マッピングの機能が無ければすべてのプロセスのページテーブルをフル検索しなければなりません。)ページ割り当ては遅延割り当てとして、ページ例外処理の中のhandle_pte_fault関数で行われます。そして無名ページ割り当てはdo_anonymous_page関数がコールされます。
static inline int handle_pte_fault(struct mm_struct *mm, struct vm_area_struct *vma, unsigned long address, pte_t *pte, pmd_t *pmd, int write_access) { : if (!pte_present(entry)) { if (pte_none(entry)) { if (vma->vm_ops) { if (likely(vma->vm_ops->fault)) return do_linear_fault(mm, vma, address, pte, pmd, write_access, entry); } return do_anonymous_page(mm, vma, address, pte, pmd, write_access); } : if (pte_file(entry)) return do_nonlinear_fault(mm, vma, address, pte, pmd, write_access, entry); return do_swap_page(mm, vma, address, pte, pmd, write_access, entry); } : : return 0; }do_anonymous_page関数で実ページが割り当てられ、ページディスクリプター/ページテーブルの種々の設定を施します。そして、逆マッピングに関してはpage_add_new_anon_rmap関数をコールすることで行います。page_add_new_anon_rmap関数では、page->_mapcount=0(0はそのページの参照が1つと言うことです。参照しない場合は-1。新規割り当てのため他に参照していないため0で初期化)を設定したのち、__page_set_anon_rmap関数で逆マッピングの実設定を行います。
void page_add_new_anon_rmap(struct page *page, struct vm_area_struct *vma, unsigned long address) { atomic_set(&page->_mapcount, 0); /* elevate count by 1 (starts at -1) */ __page_set_anon_rmap(page, vma, address); }
#define PAGE_MAPPING_ANON 1 struct anon_vma { spinlock_t lock; /* Serialize access to vma list */ struct list_head head; /* List of private "related" vmas */ }; static void __page_set_anon_rmap(struct page *page, struct vm_area_struct *vma, unsigned long address) { struct anon_vma *anon_vma = vma->anon_vma; anon_vma = (void *) anon_vma + PAGE_MAPPING_ANON; page->mapping = (struct address_space *) anon_vma; : }逆マッピング処理はpage->mappingに、そのページがマップされているvm_area_struct のvma->anon_vmaを設定するだけです。なお、設定するvma->anon_vmaアドレスにPAGE_MAPPING_ANON(1)加えた値を設定しています。これは物理ページが無名ページかそうでないか判断するためで、これでpage->mappingの下位ビットが1ならそのページは無名ページと判断できる分けです。
これで、このページのpage->mappingから、vma->anon_vmaが取得でき、list_head構造体をメンバーに持つanon_vmaからvm_area_structが、そしてそのメンバーのmm_struct * vm_mmから、ページテーブルが取得できるわけですが・・・。
それじゃあ、page->mappingに直接ページテーブルのアドレスを設定すればいいではないか。
共有ページのページディスクリプターに直接逆マップ処理を施すとなると、共有する毎にページディスクリプター自身にその処理(その共有するプロセスのページテーブルのリスト)を施す必要があります。そうなると・・・
例えばfork関数で子プロセスを作成した場合を見てみると、子プロセスはページテーブル/メモリディスクリプタは独自に確保しますか、プロセス毎の独自の設定した後、実たる中身は親と同じで、そして物理ページそのものを共有します。従ってその子プロセス作成の処理は負荷の無い処理となっています。しかし物理ページ毎に逆マッピングの処理を施していたら、そのすべてのページに対してページ毎の子プロセスのページテーブルのリストを追加する処理が必要となってくるわけです。
forrk()->copy_process()->copy_mm()->dup_mm()->dup_mmap()とコールされます。dup_mmap関数では親プロセスのvm_area_struct構造体(メモリーリージョン)の中身をコピーしていく処理です。そしてその処理の中でanon_vma_link関数がコールされ、子プロセスのvm_area_structを子プロセスのanon_vmaにリスト登録していきます。vm_area_structの中身を親プロセスと同じものでした。すなわち子としてリスト登録したanon_vmaは、親と同じもとだということです。従ってpage->mappingのanon_vmaを辿ることで、ページを参照している各プロセス毎のvm_area_struct構造体がわかり、ページテーブルが参照できるということです。
void anon_vma_link(struct vm_area_struct *vma) { struct anon_vma *anon_vma = vma->anon_vma; if (anon_vma) { spin_lock(&anon_vma->lock); list_add_tail(&vma->anon_vma_node, &anon_vma->head); spin_unlock(&anon_vma->lock); } }無名ページの回収はtry_to_unmap_anon関数で行います。pageからstruct anon_vma *anon_vmaを取得し、list_for_each_entryマクロでstruct anon_vma *anon_vmaのリストから、各vm_area_struct 構造体を取得し、それを引数としてtry_to_unmap_one関数で、ページテーブルおよびページの回収を行うことができるわけです。
static int try_to_unmap_anon(struct page *page, int migration) { struct anon_vma *anon_vma; struct vm_area_struct *vma; int ret = SWAP_AGAIN; anon_vma = page_lock_anon_vma(page); if (!anon_vma) return ret; list_for_each_entry(vma, &anon_vma->head, anon_vma_node) { ret = try_to_unmap_one(page, vma, migration); if (ret == SWAP_FAIL || !page_mapped(page)) break; } page_unlock_anon_vma(anon_vma); return ret; }