I/O スケジューラ (その6)
今回は I/O スケジューラの API の elevator_merge_req_fn メソッドについて。CFQ の実装を調べて、その役割を学習する。
まずはカーネルドキュメント [1] から elevator_merge_req_fn メソッドの仕事について調べる。それによると
elevator_merge_req_fn called when two requests get merged. the one
which gets merged into the other one will be
never seen by I/O scheduler again. IOW, after
being merged, the request is gone.
とある。つまり、elevator_merge_req_fn メソッドはリクエストオブジェクト同士を併合した時に呼び出されるらしい。そして、一方のリクエストオブジェクトは、I/O スケジューラでは二度と扱わないと言っている。これはどういう事を言っているのか。
CFQ における elevator_merge_req_fn メソッドの実体は cfq_merged_requests() 関数 [block/cfq-iosched.c] で定義されている。以下がそのコードになる。
static void
cfq_merged_requests(struct request_queue *q, struct request *rq,
struct request *next)
{
/*
* reposition in fifo if next is older than rq
*/
if (!list_empty(&rq->queuelist) && !list_empty(&next->queuelist) &&
time_before(next->start_time, rq->start_time))
list_move(&rq->queuelist, &next->queuelist);
cfq_remove_request(next);
}
ここでは、変数 next で表されるリクエストオブジェクトが rq で表されるリクエストオブジェクトよりも若い (新しい) 場合に list_move() を実行して rq のリクエストオブジェクトを next のリスト上の後方に移動させている。
そして cfq_remove_request() を呼び出している。こいつは、前回に、リクエストオブジェクトをディスパッチキューに挿入する処理 cfq_dispatch_insert() でも登場した処理で、引数で与えられたリクエストオブジェクトを CFQ 内部のリクエストキュー (cfq_queue オブジェクトで表されるキュー) からパージする。
cfq_merged_requests() 関数の処理はたったこれだけ。やっている事は、単に第三引数で与えられたリクエストオブジェクトを cfq_remove_request() を呼び出して CFQ のキューからパージするだけ。リクエストの併合処理も、併合後の一方のリクエストの廃棄やら、環境情報や統計情報の更新といった諸処の処理を全く行わない。単に CFQ の内部リストから外すだけ。
リストの更新処理を行っているのは、マージされたリクエストオブジェクトの処理順序がパージされたリクエストよりも前方に存在する場合に、前者のリクエストオブジェクトのキュー内部での位置を、後者が存在していた位置に移動させる為。パージされたリクエストオブジェクトの存在が過去形なのは、直後にリクエストキューから外される為。
CFQ における elevator_merge_req_fn の処理内容は以上だが。これだけではつまらないので、この処理を行う I/O ブロックレイヤの elevator_merge_req_fn メソッドを呼び出す処理を調査する。
elevator_merge_req_fn メソッドを実行するのは elv_merge_requests() 関数 [block/elevator.c] である。以下がそのコード
void elv_merge_requests(struct request_queue *q, struct request *rq,
struct request *next)
{
elevator_t *e = q->elevator;
if (e->ops->elevator_merge_req_fn)
e->ops->elevator_merge_req_fn(q, rq, next);
elv_rqhash_reposition(q, rq);
elv_rqhash_del(q, next);
q->nr_sorted--;
q->last_merge = rq;
}
elv_merge_requests() では elevator_merge_req_fn メソッドを呼び出した後。キュー内のハッシュリストの再配置し、マージしたリクエストオブジェクトを I/O ブロックレイヤのリクエストキューのリストから外し、リクエストキューの環境情報を更新する。
この関数が呼び出された段階では、リクエストオブジェクトのマージが完了している。elv_merge_requests() を呼び出す関数は、attempt_merge() 関数 [block/ll_rw_blk.c] になる。以下がそのコード。
static int attempt_merge(struct request_queue *q, struct request *req,
struct request *next)
{
if (!rq_mergeable(req) || !rq_mergeable(next))
return 0;
/*
* not contiguous
*/
if (req->sector + req->nr_sectors != next->sector)
return 0;
if (rq_data_dir(req) != rq_data_dir(next)
|| req->rq_disk != next->rq_disk
|| next->special)
return 0;
/*
* If we are allowed to merge, then append bio list
* from next to rq and release next. merge_requests_fn
* will have updated segment counts, update sector
* counts here.
*/
if (!ll_merge_requests_fn(q, req, next))
return 0;
/*
* At this point we have either done a back merge
* or front merge. We need the smaller start_time of
* the merged requests to be the current request
* for accounting purposes.
*/
if (time_after(req->start_time, next->start_time))
req->start_time = next->start_time;
req->biotail->bi_next = next->bio;
req->biotail = next->biotail;
req->nr_sectors = req->hard_nr_sectors += next->hard_nr_sectors;
elv_merge_requests(q, req, next);
if (req->rq_disk) {
disk_round_stats(req->rq_disk);
req->rq_disk->in_flight--;
}
req->ioprio = ioprio_best(req->ioprio, next->ioprio);
__blk_put_request(q, next);
return 1;
}
ここでは、引数で指定した2つのリクエストオブジェクトが、同一ブロックデバイスに対する要求対象の物理アドレス空間が隣接している場合に、一方のリクエストをもう一方にヘイグさせる。具体的には、一方のリクエストオブジェクトが持つ bio オブジェクトのリストをもう一方にマージする。
マージしたリクエストオブジェクトは elevator_merge_req_fn メソッドによって I/O スケジューラの内部リクエストキューからパージされる。そして、I/O ブロックレイヤのリクエストキューからもパージされる。
elv_merge_requests() の後で実行されている disk_round_stats() 関数の呼び出しと、gendisk オブジェクトの in_flight メンバの更新処理は、リクエストの対象となるディスクデバイスの統計情報の更新を行っている。これについてはカーネルドキュメント [2] が存在するので、そちらに詳しく書かれている。
簡単に言うと。/proc/interrupts のように、ディスクアクセスやディスクアクセスに関する統計情報を取得できる。具体的には、read/write の回数や、セクタ数、リクエストに要した時間、I/O スケジューラによる待ち時間などを、/sys/block/<device name>/stat から参照できる。こいつを使う事で、I/O 関連のパフォーマンス測定なんかに役に立ちそうだ。
attempt_merge() では最後に __blk_put_request() 関数を呼び出し、マージしたリクエストオブジェクトの廃棄処理を行っている。こいつの処理内容は、別の回の内容と被るので次回以降に話をする。
今回学習した内容は次の4つ。
(1) CFQ I/O スケジューラにおける elevator_merged_req_fn メソッドの実装
(2) I/O スケジューラ一般における elevator_merged_req_fn メソッドの処理
(3) I/O ブロックレイヤにおけるリクエストオブジェクト同士の併合処理の実装
(4) ディスクに対する I/O の統計情報設定のカーネル処理
[1] Documentation/block/biodoc.txt
[2] Documentation/block/stat.txt
- Category(s)
- 学習経過
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/ohyama/i-o-30b930b130e530fc30e9-305d306e6/tbping