Linuxのブロック層について調べた。blk_queue_bio()
を読んだ。
カーネルバージョン
v4.4.0
blk_queue_bio()
blk_queue_bio()
は書籍で説明されているv2.6.x系で言うところの、__make_request()
に相当する関数で、
IOスケジューラの呼び出しと、リクエストキューへのリクエストの追加をしている。
既存リクエストとのマージ
まず、リクエストキューにQUEUE_FLAG_NOMERGES
が立っていないかみる。
これが立っていると、リクエストキューにbioはマージできない。
request_count
はいま同じキューに繋がるリクエストが、いくつcurrent->plugにはいっているかを表している。
if (!blk_queue_nomerges(q)) {
if (blk_attempt_plug_merge(q, bio, &request_count, NULL))
return BLK_QC_T_NONE;
} else
request_count = blk_plug_queued_count(q);
spin_lock_irq(q->queue_lock);
まずは、blk_attempt_plug_merge()
で、plug_list内のマージを試す。
この段階ならまだプロセスごとのキューしか触ってないので、リクエストキューのロックはとらなくて良い。
if (rq->q != q || !blk_rq_merge_ok(rq, bio))
continue;
el_ret = blk_try_merge(rq, bio);
if (el_ret == ELEVATOR_BACK_MERGE) {
ret = bio_attempt_back_merge(q, rq, bio);
if (ret)
break;
} else if (el_ret == ELEVATOR_FRONT_MERGE) {
ret = bio_attempt_front_merge(q, rq, bio);
if (ret)
break;
}
次にelv_merge()
でIOスケジューラによるマージ可能かチェックを行って、可能ならばelv_bio_merged()
でマージを行う。
さらに、elv_merged_request()
でリクエスト自体がマージできないかみる。(マージしたbioで2つのリクエストが繋がる場合がある)
ここまでで、マージできたということは、bioはリクエストに入ったので、この関数は抜ける。
el_ret = elv_merge(q, &req, bio);
if (el_ret == ELEVATOR_BACK_MERGE) {
if (bio_attempt_back_merge(q, req, bio)) {
elv_bio_merged(q, req, bio);
if (!attempt_back_merge(q, req))
elv_merged_request(q, req, el_ret);
goto out_unlock;
}
} else if (el_ret == ELEVATOR_FRONT_MERGE) {
if (bio_attempt_front_merge(q, req, bio)) {
elv_bio_merged(q, req, bio);
if (!attempt_front_merge(q, req))
elv_merged_request(q, req, el_ret);
goto out_unlock;
}
}
マージできなかった場合
マージができなかった場合、そのbioに対してリクエストを新しく確保する。
コメントには失敗しないとあるけど、キューにDYINGフラグがある時は失敗するみたい、ドライバがアンロードされたとき?
init_request_from_bio()
とblk_rq_bio_prep()
requestを初期化してbioをrq->bioのリストにつなげる。
リクエストの構造もbioと同様にフラグとsectorの情報を管理する。
/*
* Grab a free request. This is might sleep but can not fail.
* Returns with the queue unlocked.
*/
req = get_request(q, rw_flags, bio, GFP_NOIO);
if (IS_ERR(req)) {
bio->bi_error = PTR_ERR(req);
bio_endio(bio);
goto out_unlock;
}
/*
* After dropping the lock and possibly sleeping here, our request
* may now be mergeable after it had proven unmergeable (above).
* We don't worry about that case for efficiency. It won't happen
* often, and the elevators are able to handle it.
*/
init_request_from_bio(req, bio);
キューへの接続
BLK_MAX_REQUEST_COUNT
より多くplug_listにリクエストがつながっていた場合、リクエストキューへのつなぎかえを行う。
そうでない場合は、単にリクエストをplug_listにつなげる。
そもそもplugがない場合は__blk_run_queue()
でブロックデバイスにキューを吐き出させるようになっている。
current->plug = NULL
はどういう条件のときなのかはまだ見ていない。
plug = current->plug;
if (plug) {
/*
* If this is the first request added after a plug, fire
* of a plug trace.
*/
if (!request_count)
trace_block_plug(q);
else {
if (request_count >= BLK_MAX_REQUEST_COUNT) {
blk_flush_plug_list(plug, true);
trace_block_plug(q);
}
}
list_add_tail(&req->queuelist, &plug->list);
blk_account_io_start(req, true);
} else {
spin_lock_irq(q->queue_lock);
add_acct_request(q, req, where);
__blk_run_queue(q);
out_unlock:
spin_unlock_irq(q->queue_lock);
}
まとめ
IOスケジューラに行く前に、プロセスごとのplugなる構造でIOスケジューラ関係なくbioをマージする処理がある。
同じプロセスがシーケンシャルなIOを出している場合などで、有効なのかもしれない。