2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?