ページキャッシュ
ディスク操作に掛かるデータは、パフォーマンスの上からメモリに蓄えられます。それをディスクキャッシュと言います。ディスクキャッシュの対象は、ファイル操作する上で必要なオブジェクト(inode,dentry)と、ファイルのデータその物となります。ファイルのデータその物のキャッシュをページキャッシュと言います。
従ってページキャッシュは、ファイル単位すなわちinode単位で管理する必要があります。それを行っているのがinode->mapping->page_treeのradix-treeのトップです。inode->mappingはアドレス空間のオブジェクトで、mapping->page_treeのようにページキャッシュの管理リストおよびそれを操作する関数群を有しています。
ファイルのディスク上のブロックは、ランダムに配置されるのは理解しやすいと思いますが、それを読み込んだデータについてはユーザプロセスの感覚からちょっと理解しがたいところです。ユーザプロセスでは一連のバッファを確保した後、そこにデータを蓄えるといった感じだからです。しかしカーネルではページ単位でメモリをディスク上のブロックのように管理しています。一連の蓄えたデータは物理的に連続したメモリに蓄えられるわけでありません。それ故、ファイル上のオフセットをページ単位のインデックスをキーとして、node->mapping->page_treeでページ単位で管理する必要があるわけです。
ページキャッシュを新規の確保する時、alloc_page関数でとりあえずページを確保して、そのページをページキャッシュとすべき処理を行います。inode->mapping->page_treeへの登録は、そのページが再度参照された場合、すばやく見つけるためのリストとなります。そして、メモリーが少なくなってきたときページ回収の処理のため、参照するもう一つのリストが必要です。これをlruリストといいます。
lruリストはキャッシュがどのinodeに属するかに関係なく、ページキャッシュが作成される毎にそのページの属するゾーン下のメンバーのリストに登録されていきます。(なお、ramfsでページキャッシュを確保する時、ramfsではページキャッシュは回収不可なので登録されません。また、dentry,inodeのページも同様にこのリストに登録されていくと思います。)
ページはadd_to_page_cache_lru関数でページキャッシュとしての処理が行われます。引数のpageはalloc_pageして確保したページです。*mappingはinode->mappingです。offsetはデータの位置をページ単位(4K)のインデックスです。add_to_page_cache関数で、inode->mapping->page_treeかかる処理を行い、lru_cache_add関数でlruリストにかかる処理を行います。
page_cache_get関数はpage->_countをインクリメントします。このinodeで参照することになるからです。page->mapping = mappingとします。ページ回収で逆マッピング処理で、pageからinode->mappingを参照する必要があるからです。またpage->mappingがNULLかどうかで、そのページがページキャッシュとして使用されているかどうか判断できます。
page->index = offsetはページ単位のファイル上の位置をセットし、radix_tree_insert関数でmapping->page_tree下のradix-treeにoffsetをキーとして、pageを登録します。
登録できたらmapping->nrpages++で、ページキャッシュ数を加算します。__inc_zone_page_stateはCPU変数のゾーンごとにページがどのような用途に使われているかの情報をセットしているようです。
最後にradix_tree_preload_end関数を呼び出して終了です。これはカーネルのプリエンプションを許可するだけです。
lru_cache_add関数でlruにページを登録します。page_cache_get関数でlruリストで参照するということで、page->_countをインクリメントします。
ページはいきなりlruリストに登録しないで、pagevec_add関数で一旦CPU変数へ登録して、ある一定数たまったまとめてlryリストに登録します。この関数が0なら、その一定量が溜まったということで、__pagevec_lru_add関数でlruリストに登録です。
従ってページキャッシュは、ファイル単位すなわちinode単位で管理する必要があります。それを行っているのがinode->mapping->page_treeのradix-treeのトップです。inode->mappingはアドレス空間のオブジェクトで、mapping->page_treeのようにページキャッシュの管理リストおよびそれを操作する関数群を有しています。
ファイルのディスク上のブロックは、ランダムに配置されるのは理解しやすいと思いますが、それを読み込んだデータについてはユーザプロセスの感覚からちょっと理解しがたいところです。ユーザプロセスでは一連のバッファを確保した後、そこにデータを蓄えるといった感じだからです。しかしカーネルではページ単位でメモリをディスク上のブロックのように管理しています。一連の蓄えたデータは物理的に連続したメモリに蓄えられるわけでありません。それ故、ファイル上のオフセットをページ単位のインデックスをキーとして、node->mapping->page_treeでページ単位で管理する必要があるわけです。
ページキャッシュを新規の確保する時、alloc_page関数でとりあえずページを確保して、そのページをページキャッシュとすべき処理を行います。inode->mapping->page_treeへの登録は、そのページが再度参照された場合、すばやく見つけるためのリストとなります。そして、メモリーが少なくなってきたときページ回収の処理のため、参照するもう一つのリストが必要です。これをlruリストといいます。
lruリストはキャッシュがどのinodeに属するかに関係なく、ページキャッシュが作成される毎にそのページの属するゾーン下のメンバーのリストに登録されていきます。(なお、ramfsでページキャッシュを確保する時、ramfsではページキャッシュは回収不可なので登録されません。また、dentry,inodeのページも同様にこのリストに登録されていくと思います。)
ページはadd_to_page_cache_lru関数でページキャッシュとしての処理が行われます。引数のpageはalloc_pageして確保したページです。*mappingはinode->mappingです。offsetはデータの位置をページ単位(4K)のインデックスです。add_to_page_cache関数で、inode->mapping->page_treeかかる処理を行い、lru_cache_add関数でlruリストにかかる処理を行います。
int add_to_page_cache_lru(struct page *page, struct address_space *mapping, pgoff_t offset, gfp_t gfp_mask) { int ret = add_to_page_cache(page, mapping, offset, gfp_mask); if (ret == 0) lru_cache_add(page); return ret; }radix_tree_preload関数は、カーネルのプリエンプションを禁止するのと、CPU変数下のradix_tree_preloadsに、所定の数のノード数(多分4階層)を前もってスラブから確保しておきます。前の処理ですでに4階層有しているなら、この処理はカーネルのプリエンプションを禁止するだけです。
page_cache_get関数はpage->_countをインクリメントします。このinodeで参照することになるからです。page->mapping = mappingとします。ページ回収で逆マッピング処理で、pageからinode->mappingを参照する必要があるからです。またpage->mappingがNULLかどうかで、そのページがページキャッシュとして使用されているかどうか判断できます。
page->index = offsetはページ単位のファイル上の位置をセットし、radix_tree_insert関数でmapping->page_tree下のradix-treeにoffsetをキーとして、pageを登録します。
登録できたらmapping->nrpages++で、ページキャッシュ数を加算します。__inc_zone_page_stateはCPU変数のゾーンごとにページがどのような用途に使われているかの情報をセットしているようです。
最後にradix_tree_preload_end関数を呼び出して終了です。これはカーネルのプリエンプションを許可するだけです。
int add_to_page_cache_locked(struct page *page, struct address_space *mapping, pgoff_t offset, gfp_t gfp_mask) { int error; VM_BUG_ON(!PageLocked(page)); error = mem_cgroup_cache_charge(page, current->mm, gfp_mask & ~__GFP_HIGHMEM); if (error) goto out; error = radix_tree_preload(gfp_mask & ~__GFP_HIGHMEM); if (error == 0) { page_cache_get(page); page->mapping = mapping; page->index = offset; spin_lock_irq(&mapping->tree_lock); error = radix_tree_insert(&mapping->page_tree, offset, page); if (likely(!error)) { mapping->nrpages++; __inc_zone_page_state(page, NR_FILE_PAGES); } else { page->mapping = NULL; mem_cgroup_uncharge_cache_page(page); page_cache_release(page); } spin_unlock_irq(&mapping->tree_lock); radix_tree_preload_end(); } else mem_cgroup_uncharge_cache_page(page); out: return error; }lruリストはページのゾーン毎に有していて、活性リストと非活性リストを有しています。活性/非活性の意味合いは、ページ使用頻度を2段階にしているということです。初めて登録されるページは非活性に、非活性のページが使用されると活性と言う具合です。当然ページ回収を非活性から行います。
lru_cache_add関数でlruにページを登録します。page_cache_get関数でlruリストで参照するということで、page->_countをインクリメントします。
ページはいきなりlruリストに登録しないで、pagevec_add関数で一旦CPU変数へ登録して、ある一定数たまったまとめてlryリストに登録します。この関数が0なら、その一定量が溜まったということで、__pagevec_lru_add関数でlruリストに登録です。
void lru_cache_add(struct page *page) { struct pagevec *pvec = &get_cpu_var(lru_add_pvecs); page_cache_get(page); if (!pagevec_add(pvec, page)) __pagevec_lru_add(pvec); put_cpu_var(lru_add_pvecs); }__pagevec_lru_add関数でlruリストへの登録です。CPU変数下に蓄えてページ分でのループ処理となります。まず登録するページを、及びそのゾーンをを取得し、add_page_to_inactive_list関数でこのzoneの非活性リストに登録しています。if (pagezone != zone) の条件文は、ページ毎にゾーンが違うため、前に処理したゾーンと違う場合、前のゾーンのロックを解除し、今処理しようとするロックを取得するためで、当然、前のゾーンと同じなら、ロック解除/取得は無駄な処理となってしまいます。
void __pagevec_lru_add(struct pagevec *pvec) { int i; struct zone *zone = NULL; for (i = 0; i < pagevec_count(pvec); i++) { struct page *page = pvec->pages[i]; struct zone *pagezone = page_zone(page); if (pagezone != zone) { if (zone) spin_unlock_irq(&zone->lru_lock); zone = pagezone; spin_lock_irq(&zone->lru_lock); } VM_BUG_ON(PageLRU(page)); SetPageLRU(page); add_page_to_inactive_list(zone, page); } if (zone) spin_unlock_irq(&zone->lru_lock); release_pages(pvec->pages, pvec->nr, pvec->cold); pagevec_reinit(pvec); } add_page_to_inactive_list(struct zone *zone, struct page *page) { list_add(&page->lru, &zone->inactive_list); __inc_zone_state(zone, NR_INACTIVE); }