ブロックI/O
1. データ構造
1.1 bio
ブロック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 |
データ開始位置のページ内のオフセット |
1.2 Request
bioはElevatorに投入される時に、Requestに変換される。一つのRequestは物理的に連続セクタのI/O要求となる。Requestとbioの関連を図2に示す。新規にRequestが作られた場合1Requestに1つのbioしかないが、Requestがマージされていくと、bioはチェーンされていく。
図2 Requestとbioの関係
表2 struct bio_vecのフィールド
フィールド |
説明 |
---|---|
sector |
I/Oを行う開始セクタ番号 |
nr_sector |
I/Oを行うセクタ数 |
current_nr_sectors |
現在処理中のbio_vec内でのセクタ数。 |
hard_sector |
次にI/Oが完了するセクタの番号。 I/Oが完了するとblk_recalc_rq_sectors()で更新されていく。 |
hard_nr_sectors |
I/Oが完了していないセクタの数。 I/Oが完了するとblk_recalc_rq_sectors()で更新されていく。 |
hard_cur_sectors |
現在処理中のbio_vec内での、I/Oが完了していない残りセクタ数。 |
bio |
bioのリスト。 |
biotail |
bioのリストの最後のbio |
buffer |
Read/Write用バッファのアドレス。 |
2. RequestQueueのPlug/Unplug
ブロックI/O処理部はI/O RequestをElevatorに投入する際、RequestQueueをPlug(封鎖)/Unplugする。PlugすることでI/Oを細切れで実行しないで、少しため込みElevator内でRequestが効率化されやすくしている。Unplugすると、RequestQueueにたまっていたRequestをデバイスに発行する。Plugする契機:
- RequestをElevatorに入れようとした時、Elevatorが空だった(__make_request())
Unplugする契機:
- RequestをElevatorに入れた時に、RequestQueue内のRequest数が一定数を越えていた(elv_insert())
- Plugされてから一定時間経過した(blk_unplug_timeout())
3. 関連関数
3.1 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;
3.2 Plug関連
blk_plug_device()
デバイスのRequestQueueをPlug(封鎖)して新たなI/Oの実行を抑止する。
- RequestQueueにQUEUE_FLAG_PLUGGEDをセット
- Plugタイムアウト用のタイマ(q->unplug_delay(3ms))を開始
generic_unplug_device()
デバイスのRequestQueueを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()。
blk_queue_plugged()
RequestQueueがPlugされているかチェック
[関連ページ]
I/Oスケジューラ