Linux Kernel(2.6)の実装に関するメモ書き

ブロックI/O


Rev.36を表示中。最新版はこちら

編集中

データ構造

ブロックI/Oはstruct bio構造体で管理される。ブロックI/O処理部は上位(Buffer,PageCache等)からどのようなI/Oを行うかをbioで受け取る。ブロックI/O処理部はbioをI/O Requestに変換してElevator(I/Oスケジューラ)に入れる。


図1 ブロックI/Oの管理データ

表1 struct bioのフィールド(一部)
フィールド
説明
bi_sector
I/Oを行う領域の先頭セクタ番号
bi_next
bioのリスト。ブロックI/Oが既存のRequestにマージされていく時にチェーンされていく。
bi_dev
I/Oを行うブロックデバイスの構造体へのポインタ
bi_rw
I/OがRead/Writeかを示す。ビットマップになっており、同期I/Oか非同期I/Oかの情報も持つ。
bi_vcnt
bi_io_vecに格納されているエントリ数
bi_size
I/Oの合計サイズ(Byte)
この値を512で割ってセクタ数を求めている。
bi_max_vecs
bi_io_vecに確保されている領域のエントリ数。
(1,4,16,64,128,BIO_MAX_PAGESのいずれか)
bi_io_vec
I/O対象のページを指すリスト。struct bio_vecの配列になっている。ここで指定されたページとの間で、ブロックデバイスとデータがRead/writeされる。
bi_end_io
I/O完了時に呼び出されるハンドラ

表2 struct bio_vecのフィールド
フィールド
説明
bv_page
ページのアドレス
bv_len
データ長
bv_offset
データ開始位置のページ内のオフセット


RequestQueueのPlug/Unplug

I/O Requestを細切れで出さないで、まとめるためにしばらくPlug(封鎖)してRequestをため込む。Unplugすると、RequestQueueにたまっていたRequestをデバイスに発行する。

Plugする契機:
  • RequestをRequestQueueに積もうとした時、Queueが空だった

Unplugする契機:
  • RequestをElevatorに入れた時に一定数を越えた(elv_insert())
  • Plugされてから一定時間経過した(blk_unplug_timeout())

関連関数

I/O発行関連

submit_bio(rw, bio)
ブロックI/O(bio)をRequestに変換してElevator(I/Oスケジューラ)に入れる。

処理の実体はgeneric_make_request()。

generic_make_request()
デバイスのRequestQueueを取り出し、ハンドラmake_request_fn()を呼び出して、bioをRequestに変換後、RequestQueueに対応したElevatorにRequestを入れる。

generic_make_request()の処理概要
/*
 * パーティションサイズのチェック
 */

do {
    /* デバイスのRequestQueue(bdev->bd_disk->queue)取得 */
    q = bdev_get_queue(bio->bi_bdev);
    :
    ret = q->make_request_fn(q, bio);  // (*1)
} while (ret);

(*1) デバイスドライバがblk_queue_make_request()でハンドラを再設定していない限り、__make_request()が使われる。

__make_request()
ブロックI/Oがマージ可能ならマージして、マージできないならbioからRequestを作成してElevatorに投入する。

__make_request()の処理概要

if (barrier || elv_queue_empty(q))
    goto get_rq;

/* 既にElevatorにあるRequestにマージできないかチェック */
el_ret = elv_merge(q, &req, bio);
switch (el_ret) {
case ELEVATOR_BACK_MERGE:  /* 既存RequestにBackMerge可  */
    if (!q->back_merge_fn(q, req, bio))
        break;

    /* Requestのマージ
     *   Requestのbiotailにbioを追加
     *   セクタ数を加算
     */

    /* マージしたことでさらに次のRequestと
     * マージしようとする */
    if (!attempt_back_merge(q, req))
        /* マージが無理ならさっき行ったマージ分をElevatorに通知 */
        elv_merged_request(q, req);
    goto out;

case ELEVATOR_FRONT_MERGE: /* 既存RequestにFrontMerge可 */
  if (!q->front_merge_fn(q, req, bio))
        break;

    /* Requestのマージ
     *   Requestのbioリストの先頭に追加
     *   データの格納アドレスを更新
     *   セクタ数を加算
     */

    if (!attempt_front_merge(q, req))
        elv_merged_request(q, req);
    goto out;

default:
    /* マージできないので新規Requestを作成して投入 */
    ;
}

get_rq:
/*
 * bioからRequestを作成する
 */
req = get_request_wait(q, rw, bio);

init_request_from_bio(req, bio);

/* Elevatorが空ならRequestQueueをPlug(封鎖)して
 * Requestが細切れで発行されないようにする。
 */
if (elv_queue_empty(q))
    blk_plug_device(q);

/* ElevatorにRequestを投入 */
add_request(q, req);

out:
    /* 同期I/OならRequestQueueをUnplugして
     * すぐにI/Oを実行させる
     */
    if (sync)
        __generic_unplug_device(q);
    return 0;


Plug関連

blk_plug_device()
デバイスをPlug(封鎖)して新たなI/Oの実行を抑止する。
  • RequestQueueにQUEUE_FLAG_PLUGGEDをセット
  • Plugタイムアウト用のタイマ(q->unplug_delay(3ms))を開始


generic_unplug_device()
デバイスをUnplugしてI/Oの実行を再開する。
  • RequestQueueのQUEUE_FLAG_PLUGGEDをクリア
  • デバイスドライバのRequest実行ルーチン(q->request_fn())を呼び出してI/Oを実行
blk_unplug_timeout()
Plug後、一定時間(3ms)経ってもUnplugされなかった場合のタイムアウトルーチン。UnplugしてI/O Requestを発行する(*1)。

(*1) WorkQueueを使って実装されているので、ここではWorkをスケジュールするだけ()。実際の処理はblk_unplug_work()になる。 kblockd_schedule_work(&q->unplug_work)

blk_unplug_work()
Plugがタイムアウトした時のUnplugを行うWork処理。

q->unplug_fn(q)を呼び出してUnplugを行う。
unplug_fnはgeneric_unplug_device()。


[関連ページ]
I/Oスケジューラ


最終更新 2006/07/25 19:43:29 - kztomita
(2006/03/27 13:59:44 作成)
添付ファイル
bio.png - kztomita
request.png - kztomita


リンク
最近更新したページ
検索