I/O スケジューラ (その8-2)
前回は CFQ における elevator_dispatch_fn メソッドの実態である cfq_dispatch_requests() の処理にの一部についてみてきた。具体的には、引数 force に 1 が設定された際に、CFQ の内部リクエストキューが保持するリクエストオブジェクトを強制的にディスパッチキューに転送する処理についての動作を確認した。
2. 概要
今回は、cfq_dispatch_requests() の実行時、引数 force に 0 が設定された場合の処理についての動作を確認する。また、この部分の処理は、CFQ のアルゴリズムの本質を伺う重要な処理となっている。
3. 学習内容のまとめ
引数 force に 0 が設定されて呼び出された時、 cfq-dispatch_requests() は、I/O スケジューラ独自の判断によって、デバイスドライバに対するリクエストの発行を行う。具体的には、プロセスの I/O 優先度の基づいたキューの評価値を元に、すべてのプロセスに対して公平にリクエストを発行できる仕組みを提供している。
4. 内容詳細
では cfq_dispatch_requests() の処理について見て行く [code 1] 。
[code 1]
1 static int cfq_dispatch_requests(struct request_queue *q, int force)
2 {
3 struct cfq_data *cfqd = q->elevator->elevator_data;
4 struct cfq_queue *cfqq;
5 int dispatched;
6
7 if (!cfqd->busy_queues)
8 return 0;
9
10 if (unlikely(force))
11 return cfq_forced_dispatch(cfqd);
12
13 dispatched = 0;
14 while ((cfqq = cfq_select_queue(cfqd)) != NULL) {
15 int max_dispatch;
16
17 max_dispatch = cfqd->cfq_quantum;
18 if (cfq_class_idle(cfqq))
19 max_dispatch = 1;
20
21 if (cfqq->dispatched >= max_dispatch) {
22 if (cfqd->busy_queues > 1)
23 break;
24 if (cfqq->dispatched >= 4 * max_dispatch)
25 break;
26 }
27
28 if (cfqd->sync_flight && !cfq_cfqq_sync(cfqq))
29 break;
30
31 cfq_clear_cfqq_must_dispatch(cfqq);
32 cfq_clear_cfqq_wait_request(cfqq);
33 del_timer(&cfqd->idle_slice_timer);
34
35 dispatched += __cfq_dispatch_requests(cfqd, cfqq, max_dispatch);
36 }
37
38 return dispatched;
39 }
前回は 11 行目までの処理の話をした。引数 force の値が 0 の場合、13 行目以降の処理を実行する。
まずは 14 行目で cfq_select_queue() を呼び出し、処理対象の CFQ 内部リクエストキューを選択する [code 2]。
[code 2]
1 static struct cfq_queue *cfq_select_queue(struct cfq_data *cfqd)
2 {
3 struct cfq_queue *cfqq;
4
5 cfqq = cfqd->active_queue;
6 if (!cfqq)
7 goto new_queue;
8
9 /*
10 * The active queue has run out of time, expire it and select new.
11 */
12 if (cfq_slice_used(cfqq))
13 goto expire;
14
15 /*
16 * The active queue has requests and isn't expired, allow it to
17 * dispatch.
18 */
19 if (!RB_EMPTY_ROOT(&cfqq->sort_list))
20 goto keep_queue;
21
22 /*
23 * No requests pending. If the active queue still has requests in
24 * flight or is idling for a new request, allow either of these
25 * conditions to happen (or time out) before selecting a new queue.
26 */
27 if (timer_pending(&cfqd->idle_slice_timer) ||
28 (cfqq->dispatched && cfq_cfqq_idle_window(cfqq))) {
29 cfqq = NULL;
30 goto keep_queue;
31 }
32
33 expire:
34 cfq_slice_expired(cfqd, 0);
35 new_queue:
36 cfqq = cfq_set_active_queue(cfqd);
37 keep_queue:
38 return cfqq;
39 }
[code 1] の 14 行目では while 文の判定処理から cfq_select_queue() を呼び出して、アクティブキューを次々と選択させている。
ここで行われるアクティブキューの変更は cfq_select_queue() のみで行われる処理であるので。ここでの処理の本質は、リクエストオブジェクトを持つすべての CFQ 内部リクエストキューに対して、公平にディスパッチ処理を行わせる事にある。
ここで選出される CFQ 内部リクエストキューは通常、現在登録されているアクティブキューになる。この段階でアクティブキューがスライスタイム (対象のリクエストキューが現在キューに溜まっているリクエストオブジェクトを、優先的にディスパッチキューに転送することが出来る権利を有する時間) を使い切っていた場合、cfq_slice_expire() を呼び出してリクエストキューの再評価を行い、cfq_set_active_queue() を帯出して、新たなアクティブキューを選択する。
呼び出し元の cfq_dispatch_requests() [code 1] では、cfq_select-queue() [code 2] から得たリクエストキューを取得し 35 行目の __cfq_dispatch_requests() を呼び出す。ただし __cfq_dispatch_requests() によるリクエストオブジェクトのデバイスドライバへの転送処理 (ディスパッチ処理) の回数が規程されている上限回数を上回り、且つ他にリクエストオブジェクトを持つリクエストキューを I/O スケジューラが持っている場合に処理を終了する ([code 1] 21-23 行目の処理) 。又、[code 1] の 24 行目の判定は、前回のディスパッチ処理で、アクティブキューの中のリクエストオブジェクトが全て転送しきれずに残り、且つアクティブキューのタイムスライスが残っている事が前提として存在する。その上で、他のリクエストキューにはリクエストオブジェクトが無いとき。CFQ は、ディスパッチ処理の上限回数の 4 倍まで、対象のアクティブキューのディスパッチ処理を許可する。
また 28-29 行目の判定処理は、現在デバイスドライバで同期 I/O 処理 (遅延が許されない I/O 処理) が行われている場合に、ここでのディスパッチ処理において、アクティブキューが溜めているリクエストオブジェクトが同期 I/O 要求でない場合に、ディスパッチ処理を直ちに中止する事を表している。
cfq_data 型の sync_flight メンバがシンの時、現在デバイス及びデバイスドライバにおいて、同期 I/O が処理中である事を意味している。
では、同期 I/O の処理が行われている時に、上述の条件下において I/O スケジューラ側がディスパッチ処理を停止する理由は何か。答えはとても単純で、優先度の高い同期 I/O が迅速に処理される為に、ディスパッチキューの中に余計な (優先度の低い) 要求をデバイスに送らないようにするためである。
ここで、ディスパッチ処理を実際に行う __cfq_dispatch_requests() のコードを見る。
[code 3]
1 static int
2 __cfq_dispatch_requests(struct cfq_data *cfqd, struct cfq_queue *cfqq,
3 int max_dispatch)
4 {
5 int dispatched = 0;
6
7 BUG_ON(RB_EMPTY_ROOT(&cfqq->sort_list));
8
9 do {
10 struct request *rq;
11
12 /*
13 * follow expired path, else get first next available
14 */
15 rq = cfq_check_fifo(cfqq);
16 if (rq == NULL)
17 rq = cfqq->next_rq;
18
19 /*
20 * finally, insert request into driver dispatch list
21 */
22 cfq_dispatch_insert(cfqd->queue, rq);
23
24 dispatched++;
25
26 if (!cfqd->active_cic) {
27 atomic_inc(&RQ_CIC(rq)->ioc->refcount);
28 cfqd->active_cic = RQ_CIC(rq);
29 }
30
31 if (RB_EMPTY_ROOT(&cfqq->sort_list))
32 break;
33
34 } while (dispatched < max_dispatch);
35
36 /*
37 * expire an async queue immediately if it has used up its slice. idle
38 * queue always expire after 1 dispatch round.
39 */
40 if (cfqd->busy_queues > 1 && ((!cfq_cfqq_sync(cfqq) &&
41 dispatched >= cfq_prio_to_maxrq(cfqd, cfqq)) ||
42 cfq_class_idle(cfqq))) {
43 cfqq->slice_end = jiffies + 1;
44 cfq_slice_expired(cfqd, 0);
45 }
46
47 return dispatched;
48 }
ここでは、単純に引数で渡された上限回数だけ CFQ 内部リクエストキューに溜まったリクエストオブジェクトを取り出し cfq_dispatch_insert() を呼び出して、リクエストオブジェクトをディスパッチキューに転送する処理を行っている。この関数の詳細については、前回に説明を行ったので、そちらを参照されたし。
- Category(s)
- 学習経過
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/ohyama/i-o-30b930b130e530fc30e9-305d306e8-2/tbping