Swap - ページアウト
1. ページアウト処理の概要
1.1 ページアウトの開始
空きメモリが少くなってkswapdがwakeupされたり、alloc_pages()でFreeListからページが取れなかった場合、Inactiveリストからページを回収して空きメモリを増やそうとする(「空きページの確保」参照)。Inactiveリストをスキャンした時にPageCacheのページは(Dirtyでなければ)解放されるだけだが、匿名ページは解放前にディスク(スワップファイル)に書き出す必要があり、ページアウト処理が発生する。
Inactiveリストのページをスキャンしてページを解放する処理はshrink_list()で行なわれる。shrink_list()ではページアウトに関して主に以下の処理を行なう。これらの処理は1.2節以降を参照。
- ページアウト先の割り当て (1.2節)
- ユーザ空間からのアンマップ (1.3節)
- ページアウト(I/O) (1.4節)
shrink_list()のページアウトに関する処理を抜きだしたものを以下に示す。
while (!list_empty(page_list)) { : /* 匿名ページでSwapCacheに登録されていないければ、 * add_to_swap()を呼び出してページアウト先を決定する。 * 1.2節参照 */ if (PageAnon(page) && !PageSwapCache(page)) { if (!sc->may_swap) goto keep_locked; /* スワップ領域から未使用ページを取得して、SwapCacheに登録 * そして、ページをDirty状態にする */ if (!add_to_swap(page, GFP_ATOMIC)) goto activate_locked; } /* ページがユーザ空間からマップされている * (プロセスのPTEが設定されている)場合は * ユーザ空間からアンマップする。 * 1.3節参照 */ if (page_mapped(page) && mapping) { switch (try_to_unmap(page, 0)) { : : case SWAP_SUCCESS: ; } } /* Dirtyページをディスクに書きだし * 1.4節参照 */ if (PageDirty(page)) { switch(pageout(page, mapping)) { : : case PAGE_SUCCESS: } } }
1.2 ページアウト先の割り当て
shrink_list()でページをスキャンしてSwapCacheに登録されていない匿名ページを見つけたら、このページはページアウト対象なので、どこにページアウトするかを決めるためにadd_to_swap()を呼び出す。
add_to_swap()はSwap空間のどのページにページアウトするかを決定し、ページをSwapCacheに入れてDirty状態にする(図1)。
図1 add_to_swap()の流れ
ページアウト先の決定はget_swap_page()で行なう。get_swap_page()は、システムに複数のSwap領域がある場合は以下のルールに基づき、まずどのSwap領域を使用するか決める。
- 空き領域のあるSwap領域の内、最も優先度の高いものを選択する
- 同優先度のSwap領域がある場合は、その中でラウンドロビンで選択する
その次に、選択したSwap領域からscan_swap_map()で空きページを割り当てる。scan_swap_map()は空きページを探す際、空きページがある程度連続している領域(クラスタ)から順番に割り当てていくようになっている(図2)。このようにすることで、複数ページのページアウトが連続ディスクブロックに書き出されやすくなり性能が向上する。クラスタ内のページを全て割り当てたら次の新しいクラスタを探してそこから割り当てを行う。クラスタのサイズはSWAPFILE_CLUSTER(256)ページ。
図2 空き領域のクラスタ化
1.3 ページのアンマップ
ページアウトするとページのデータをディスク(スワップファイル)に書き出してページを解放するので、それ以降ページへのアクセスはできなくなる。このため、プロセスのユーザ空間からこのページをマップしていた場合はアンマップする必要がある。
アンマップはtry_to_unmap()で行う。try_to_unmap()は指定ページへのマップをページテーブルから削除する(図3)。
アンマップ処理の際、ある物理ページにマップしている仮想アドレス空間は1つとは限らない(複数のプロセスで共有されている可能性がある)。このため、try_to_unmap_anon()(*1)はRmapをたどり、このページにマップしている全仮想空間からアンマップする(*2)。
アンマップ後、ページアウトしたページが必要になり、プロセスがアンマップしたアドレス空間へアクセスするとページフォルトが発生しページイン処理が開始される(*3)。
図3 アンマップ処理
(*1) 匿名ページのアンマップ関数。try_to_unmap()は指定されたページが匿名ページだった場合はtry_to_unmap_anon()を呼び出す。
(*2) 図3は1つのユーザ空間からしかマップされていないケースの例。
(*3) アンマップする空間に対応するPTEは図3にもあるようにPresent bitをクリアしてマップを無効にする。そして、PTE内の余った(Present bit = 0の時は使用されない)領域にページアウト先の情報を書き込んでおく。今後、このアドレス空間にアクセスするとPTEが無効なのでページフォルトが発生する。ページフォルトのハンドラはPTEに書き込まれているページアウト先の情報を取り出してページイン処理を開始する。PTEの設定はtry_to_unmap_one()で行われている。
1.4 ページのディスクへの書き出し
ページアウト処理はpageout()で行う。pageout()はページの書き込み用ルーチンmapping->a_ops->writepageを呼び出す。スワップ領域へのページアウトの場合は、ページはすでにSwapCacheに入れられているのでmappingはswapper_spaceが使用される(*1)。swapper_spaceのwritepageはswap_writepage()となっており、この関数がWrite I/Oを発行する。
書き出し処理は非同期となっており、I/Oが完了するとend_swap_bio_write()が呼び出される。ページアウトしたページはInactiveであれば、Inactiveリストの末尾に移動され、shrink処理で優先的に解放されるようになる(*2)。
図4 ページアウト処理
(*1) pageout()はスワップ領域へのページアウト以外にも、DirtyなPageCacheのデータをディスクへの書き戻すのにも使用される。この場合、mappingはキャッシュしているファイルのaddress_spaceとなり、対応するファイルシステムのwritepageハンドラが呼び出される。
(*2) shrink処理のページスキャンはLRUリストの末尾の方から行われる