block_read_full_page()
1. 概要
block_read_full_page()はPageCacheへのデータの読み込みを行う汎用ルーチン。
この関数は指定したページに対応するディスクブロックからファイルデータを読み込みページにデータを格納する(図1)。
図1 データの読み込み
この関数は、ファイルリードの過程でディスクからPageCacheへファイルデータを読み込むのに使用する。address_space_operationsのreadpage,readpagesに登録されているPageCacheへのデータ読み込みハンドラ(もしくはその延長の処理)から呼び出される(図2)。
図2 block_read_full_page()の位置づけ
2. mpageとの関係
同じようなPageCacheへのデータ読み込み関数としてmpageモジュールのmpage_readpage()などがある。
mpage_readpage()ではページに対応するディスクブロックが全て連続している場合に特化された処理となっている。ディスクブロックが非連続となっていてmapgeで処理できない場合は、より汎用的なblock_read_full_page()を呼び出すようになっている。
3. 実装
3.1 block_read_full_page()
block_read_full_page(struct page *page, get_block_t *get_block)pageで指定されるPageCacheにディスクからデータを読み込むRead I/Oを開始する。
get_blockには「ファイル上のブロック番号」→「ディスクブロック番号」に変換するルーチンが渡される。これはファイルシステム毎に異なる。例えばExt2ならext2_get_block()。
[実装メモ]本関数ではページに対応するディスクブロックに対して、それぞれI/Oを行う。物理ページが4KBでディスクブロックが1KBなら4つのブロックへのReadが発生する。各ブロックへのI/Oを管理するため、__bread()のBlock Read処理と同じようにBufferを使用する。このため、関数の頭の方でBufferを作成し、Buffer単位にI/Oを発行している。データ構造は図1のようになる。(Bufferに関する詳細はBuffer参照。)
なお、ここで作成されたBufferは、PageCacheへのRead I/O管理のために作成されただけなので、ディスクブロックのキャッシュ(/proc/meminfoのBuffer項目)としてはカウントされない。
I/Oを開始したBufferは(1)BH_Async_Readがセット (2)ロックを取得 される。これらはI/O完了時のハンドラ(end_buffer_async_read())でクリアされる。
page自体のロックはこの関数では取らない。呼び出しもとであらかじめロックしておく必要がある。
: /* Bufferがなければ作成する */ if (!page_has_buffers(page)) create_empty_buffers(page, blocksize, 0); /* Pageに対応するBuffer(ディスクブロックのデータ) * を全てチェック */ do { /* Uptodate状態ならI/O不要なので飛ばす */ if (buffer_uptodate(bh)) continue; if (!buffer_mapped(bh)) { /* Bufferがディスクブロックに対応付けられていない場合は * get_block()でディスク上の対応するブロック番号を * もとめてBufferをmapする。 */ if (iblock < lblock) { err = get_block(inode, iblock, bh, 0); if (err) SetPageError(page); } if (!buffer_mapped(bh)) { /* get_block()でmapできなかった。 * ファイル終端(EOF)を越えている場合はここ。 */ /* pageのbh対応部分を0で初期化する */ : continue; } if (buffer_uptodate(bh)) continue; } /* I/Oが必要なBufferを配列に保存しておく */ arr[nr++] = bh; } while (i, iblock, (bh = bh->b_this_page) != head); /* Pageに対応するBufferが全てディスクにマップ * されていたら、Pageもマップ状態にする */ if (fully_mapped) SetPageMappedToDisk(page); /* I/OすべきBufferがなかったのなら * ページをUptodate状態にして終了 */ if (!nr) { if (!PageError(page)) SetPageUptodate(page); unlock_page(page); return 0; } /* I/Oの準備 * ・Bufferのロック * ・I/O完了時のハンドラ(end_buffer_async_read)設定 * ・Bufferに非同期Read中であることを示すBH_Async_Readフラグをセット */ for (i = 0; i < nr; i) { bh = arr[i]; lock_buffer(bh); mark_buffer_async_read(bh); } /* I/O開始 */ for (i = 0; i < nr; i) { bh = arr[i]; /* 他のプロセスのRead処理などにより、 * すでにUptodate状態になっていた場合は * ここから直接I/O完了時のハンドラを呼び出す */ if (buffer_uptodate(bh)) end_buffer_async_read(bh, 1); else submit_bh(READ, bh); } return 0;
3.2 end_buffer_async_read()
end_buffer_async_read(bh, uptodate)BufferのRead I/O 完了時に呼び出されるハンドラ。block_read_full_page()が発行した各々のBufferに対するReadが完了する度に呼び出される。
bhはI/Oが完了したBufferのbuffer_head。I/Oが正常終了していればuptodateは1。エラーが発生していれば0になる。
[実装メモ]
このルーチンはReadが完了したBuffer(ディスクブロック)をUptodate状態にする。そして、同一ページ内のBufferが全てUptodat状態になったのならば、ページの全データが最新状態になったことになるのでページをUptodate状態にする(図4)。
Readが完了したBufferはBH_Async_Readフラグを落とし、ロックを解除する。ページ内の全BufferのReadが完了した場合は、ページ全体のReadが完了したことになるのでページのロックを解除する。これで、block_read_full_page()のReadが完了したことになる。
図4 I/O完了時の処理
page = bh->b_page; /* I/Oが完了したBufferをUptodate状態にする */ if (uptodate) { set_buffer_uptodate(bh); } else { /* I/Oでエラーが発生していた場合 */ clear_buffer_uptodate(bh); if (printk_ratelimit()) buffer_io_error(bh); SetPageError(page); } first = page_buffers(page); local_irq_save(flags); bit_spin_lock(BH_Uptodate_Lock, &first->b_state); /* I/Oが完了してBufferについて * ・BH_Async_Readフラグを落とす * ・ロックを解除 */ clear_buffer_async_read(bh); unlock_buffer(bh); /* page内のBufferが全てUptodate状態なら * pageもUptodate状態にする */ tmp = bh; do { if (!buffer_uptodate(tmp)) page_uptodate = 0; /* まだ、Read I/O中のBufferが見つかった */ if (buffer_async_read(tmp)) { BUG_ON(!buffer_locked(tmp)); goto still_busy; } tmp = tmp->b_this_page; } while (tmp != bh); /* ページ内の全BufferのRead I/Oが完了した */ if (page_uptodate && !PageError(page)) SetPageUptodate(page); unlock_page(page); return; still_busy: /* まだ、I/O処理中のBufferがある */ :
関連ページ
Buffermpage