bioをブロックのrequest_queueにつなげる その３

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にはいっているかを表している。

~~~c
	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内のマージを試す。
この段階ならまだプロセスごとのキューしか触ってないので、リクエストキューのロックはとらなくて良い。

~~~c
		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はリクエストに入ったので、この関数は抜ける。

~~~c
	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の情報を管理する。


~~~c
	/*
	 * 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`はどういう条件のときなのかはまだ見ていない。

~~~c
	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を出している場合などで、有効なのかもしれない。