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

ブロック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を実行
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()。

blk_queue_plugged()
RequestQueueがPlugされているかチェック

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


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


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