Read Ahead
概要
ファイルのRead処理ではファイルを後ろの方へシーケンシャルに読んでいくことが多いため、LinuxカーネルはファイルのあるブロックをReadすると、指定されたブロックから先のブロックもまとめてReadする(先読み)ようになっている。これには、以下のようなメリットがある。
- ファイルはディスク上に連続ブロックに配置されている可能性が高いため、ディスクヘッドの動きを小さくなりI/Oが効率化できる。
- ディスクへのI/O回数をまとめることでI/Oが効率化できる。
- 先にデータを読み込んで置くことで、アプリケーションが頻繁にI/O待ちしなくて済むようになる。
基本動作
Read Ahead動作では、Current windowとAhead windowの2種類のWindowを管理する(図1)。Current windowはアプリケーションがReadしている領域で、Ahead windowはRead I/Oを行っている領域。Current WindowとAhead Windowは隣接しており、アプリケーションのReadがAhead
windowに到達すると、Ahead windowがCurrent windowになり(*1)、新しいAhead windowが作成されてRead I/Oが開始される。
実装
page_cache_readahead(*mapping, *ra, *filp, offset, req_size)
先読みを行なう大元のルーチン。
mappingで指定されるファイルのアドレス空間のoffsetページからreq_sizeページの先読みを要求する。本関数は先読みした分だけReadAheadのポインタ(ra->prev_page)を進め、Current Windowを読みきった場合に、新しいAhead Windowを作成してRead I/Oを発行する(詳細は下記3.参照)。I/O完了は待たずにリターンする。
本関数は主に以下の3種類に条件分けされ処理を行っている。
2. 前回のRead Aheadからアクセス位置が連続していない
Ahead Windowに到達していた場合、Ahead WindowをCurrent Window に変更する。そして、make_ahead_window()で新しいAhead Windowを作成して、この領域に対してRead I/Oを発行する。この時、新しいCurrent Windowはまだ、Read I/Oが完了していない可能性がある。I/Oが完了していなかったらページはロックされている。
page_cache_readahead()の処理の概要
/* 前回の先読みからシーケンシャルなアクセスになっているかチェック */ sequential = (offset == ra->prev_page + 1); ra->prev_page = offset; /* Read Aheadを行う最大のページ数(128kb分) */ max = get_max_readahead(ra); /* 先読みするサイズを決定 */ newsize = min(req_size, max); : /* どこまで先読みしたかを保存 */ ra->prev_page += newsize - 1; if (sequential && ra->size == 0) { /* ファイル先頭への初回アクセス時の処理 */ /* 初回のWindow サイズ */ ra->size = get_init_ra_size(newsize, max); ra->start = offset; if (!blockable_page_cache_readahead(mapping, filp, offset, ra->size, ra, 1)) goto out; : goto out; } if (!sequential) { /* ランダムアクセス時の処理 */ ra_off(ra); /* Read AheadをDisable */ /* 要求された分はRead */ blockable_page_cache_readahead(mapping, filp, offset, newsize, ra, 1); goto out; } /* 以下はReadAheadの基本ケースの処理 */ /* ahead windowがまだ作成されていなければ * 作成してI/O開始。 */ if (ra->ahead_start == 0) { if (!make_ahead_window(mapping, filp, ra, 0)) goto out; } /* current windowを読みきって、ahead windowに到達したら * 現在のahead windowをcurrent windowにして * make_ahead_window()で新しいahead windowを作成。 * ahead windowのRead I/Oも開始される。 */ if (ra->prev_page >= ra->ahead_start) { ra->start = ra->ahead_start; ra->size = ra->ahead_size; make_ahead_window(mapping, filp, ra, 0); } out: return ra->prev_page + 1;make_ahead_window(*mapping, *filp, *ra, force)
Windowサイズは少しずつ大きくしていくが、handle_ra_miss()でキャッシュミスが通知されていた場合は、小さくしてPageCacheの使用量を抑えようとする。
blockable_page_cache_readahead(*mapping, *filp, offset, nr_to_read, block)
read_pages(*mapping, *filp, *pages, nr_pages)
ファイルのページリード用ハンドラ(mapping->a_ops->readpages)を呼び出して(*1)、Readを開始する。readpagesハンドラでは指定されたpagesのページにデータを読み込むRead I/Oを発行する。
(*1) do_generic_mapping_read()でmapping->a_ops->readpage()を呼び出す際は、ページをロックしてから呼び出していたが、mapping->a_ops->readpages()を呼び出す際は、ページをロックしていない。readpagesを呼び出した先でロックされている。(例えば、Ext2 FSではreadpagesはext2_readpages()を呼び出し、mpage_readpages()を呼び出す。mpage_readpages()はI/Oを開始する前にadd_to_page_cache()でページをPageCacheに登録してページがロックされる。)