ブロックI/O
Rev.34を表示中。最新版はこちら。
編集中データ構造
ブロック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をRequestQueueに積んだときに一定数を越えた
Plugされてから一定時間経過した
関連関数
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;
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を実行
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()。
unplug_fnはgeneric_unplug_device()。
[関連ページ]
I/Oスケジューラ