関連記事
- Part 1: 環境セットアップ
- Part 2: System call Interface
- Part 3: VFS
- Part 4: ext2 (1) write_iter
- Part 5: ext2 (2) write_begin
- Part 6: ext2 (3) get_block
- Part 7: ext2 (4) write_end
- Part 8: writeback (1) work Queue
- Part 9: writeback (2) wb_writeback
- Part 10: writeback (3) writepages
- Part 11: writeback (4) write_inode
- Part 12: block (1) submit_bio
- Part 13: block (2) blk_mq
- Part 14: I/O scheduler (1) mq-deadline
- Part 15: I/O scheduler (2) insert_request
- Part 16: I/O scheduler (3) dispatch_request
- Part 17: block (3) blk_mq_run_work_fn
- Part 18: block (4) block: blk_mq_do_dispatch_sched
- Part 19: MMC (1) initialization
- Part 20: PL181 (1) mmci_probe
- Part 21: MMC (2) mmc_start_host
- Part 22: MMC (3) mmc_rescan
- Part 23: MMC (4) mmc_attach_sd
- Part 24: MMC (5) mmc_blk_probe
概要
QEMUの vexpress-a9 (arm) で Linux 5.15を起動させながら、ファイル書き込みのカーネル処理を確認していく。
本章では、mq-deadline I/Oスケジューラのリクエストをディスパッチ(dd_dispatch_request)を確認した。
はじめに
ユーザプロセスはファイルシステムという機構によって記憶装置上のデータをファイルという形式で書き込み・読み込みすることができる。
本調査では、ユーザプロセスがファイルに書き込み要求を実行したときにLinuxカーネルではどのような処理が実行されるかを読み解いていく。
調査対象や環境などはPart 1: 環境セットアップを参照。

mq-deadlineの関数群
dd_has_work
dd_has_work関数は、elevetor_typeにあるopsのhas_workに設定され、__blk_mq_do_dispatch_sched関数から呼び出される関数となっている。
has_workでは、fifo_listやdispatchにリクエストが追加されているかどうかを確認する。

mq-deadline I/O スケジューラで登録されている dd_has_work関数の定義は次の通りとなっている。
// 787: static bool dd_has_work(struct blk_mq_hw_ctx *hctx) { struct deadline_data *dd = hctx->queue->elevator->elevator_data; enum dd_prio prio; for (prio = 0; prio <= DD_PRIO_MAX; prio++) if (dd_has_work_for_prio(&dd->per_prio[prio])) return true; return false; }
dd_has_work関数では、すべての優先度に対して dd_has_work_for_prio関数を呼び出す。
// 780: static bool dd_has_work_for_prio(struct dd_per_prio *per_prio) { return !list_empty_careful(&per_prio->dispatch) || !list_empty_careful(&per_prio->fifo_list[DD_READ]) || !list_empty_careful(&per_prio->fifo_list[DD_WRITE]); }
dd_dispatch_request
dd_has_work関数は、elevetor_typeにあるopsのhas_workに設定され、__blk_mq_do_dispatch_sched関数から呼び出される関数となっている。
dispatch_requestでは、fifo_listやdispatchからdispatchできるリクエストを返す。

mq-deadline I/O スケジューラで登録されている dd_has_work関数の定義は次の通りとなっている。
// 477: static struct request *dd_dispatch_request(struct blk_mq_hw_ctx *hctx) { struct deadline_data *dd = hctx->queue->elevator->elevator_data; struct request *rq; enum dd_prio prio; spin_lock(&dd->lock); for (prio = 0; prio <= DD_PRIO_MAX; prio++) { rq = __dd_dispatch_request(dd, &dd->per_prio[prio]); if (rq) break; } spin_unlock(&dd->lock); return rq; }
dd_dispatch_request関数では、すべての優先度に対して __dd_dispatch_request関数を呼び出す。
// 362: static struct request *__dd_dispatch_request(struct deadline_data *dd, struct dd_per_prio *per_prio) { struct request *rq, *next_rq; enum dd_data_dir data_dir; enum dd_prio prio; u8 ioprio_class; lockdep_assert_held(&dd->lock); if (!list_empty(&per_prio->dispatch)) { rq = list_first_entry(&per_prio->dispatch, struct request, queuelist); list_del_init(&rq->queuelist); goto done; } /* * batches are currently reads XOR writes */ rq = deadline_next_request(dd, per_prio, dd->last_dir); if (rq && dd->batching < dd->fifo_batch) /* we have a next request are still entitled to batch */ goto dispatch_request; /* * at this point we are not running a batch. select the appropriate * data direction (read / write) */ if (!list_empty(&per_prio->fifo_list[DD_READ])) { BUG_ON(RB_EMPTY_ROOT(&per_prio->sort_list[DD_READ])); if (deadline_fifo_request(dd, per_prio, DD_WRITE) && (dd->starved++ >= dd->writes_starved)) goto dispatch_writes; data_dir = DD_READ; goto dispatch_find_request; } /* * there are either no reads or writes have been starved */ if (!list_empty(&per_prio->fifo_list[DD_WRITE])) { dispatch_writes: BUG_ON(RB_EMPTY_ROOT(&per_prio->sort_list[DD_WRITE])); dd->starved = 0; data_dir = DD_WRITE; goto dispatch_find_request; } return NULL; dispatch_find_request: /* * we are not running a batch, find best request for selected data_dir */ next_rq = deadline_next_request(dd, per_prio, data_dir); if (deadline_check_fifo(per_prio, data_dir) || !next_rq) { /* * A deadline has expired, the last request was in the other * direction, or we have run out of higher-sectored requests. * Start again from the request with the earliest expiry time. */ rq = deadline_fifo_request(dd, per_prio, data_dir); } else { /* * The last req was the same dir and we have a next request in * sort order. No expired requests so continue on from here. */ rq = next_rq; } /* * For a zoned block device, if we only have writes queued and none of * them can be dispatched, rq will be NULL. */ if (!rq) return NULL; dd->last_dir = data_dir; dd->batching = 0; dispatch_request: /* * rq is the selected appropriate request. */ dd->batching++; deadline_move_request(dd, per_prio, rq); done: ioprio_class = dd_rq_ioclass(rq); prio = ioprio_class_to_prio[ioprio_class]; dd_count(dd, dispatched, prio); /* * If the request needs its target zone locked, do it. */ blk_req_zone_write_lock(rq); rq->rq_flags |= RQF_STARTED; return rq; }
__dd_dispatch_request関数では、"リクエスト候補を検索するフェーズ"(dispatch_writesやdispatch_find_request)と"リクエストを抽出するフェーズ"(dispatch_request)に分かれる。
リクエスト候補となる条件は次の通りとなっている。
dispatchリストにリクエストが追加されている- 実行中のバッチがあり、R/Wの方向が同じである
- READ用のfifo_listにリクエストが追加されている
- WRITE用のfifo_listにリクエストが追加されている
リクエスト候補が見つかった場合には、 deadline_next_request関数でバッチ実行中のものを確認する。
もし、バッチ実行中でなければ、deadline_check_fifo関数でリクエストがdispatchすべきかどうかを取得する。
deadline_check_fifo関数の定義は次のようになっている。
// 277: static inline int deadline_check_fifo(struct dd_per_prio *per_prio, enum dd_data_dir data_dir) { struct request *rq = rq_entry_fifo(per_prio->fifo_list[data_dir].next); /* * rq is expired! */ if (time_after_eq(jiffies, (unsigned long)rq->fifo_time)) return 1; return 0; }
deadline_check_fifo関数では、リクエストが fifo に追加/更新された時刻からdeadlineした時刻 (fifo_time)をtime_after_eqマクロによって比較する。
その結果、そのリクエストが deadlineしている場合には、deadline_check_fifo関数は 1を返す。
リクエストがdispatchすべきであることがわかれば deadline_fifo_request関数でそのリクエストを取得する。
取得したリクエストは deadline_move_request関数によって、I/O scheduler(Elevator)に登録されている該当リクエストを削除する。
deadline_move_request関数の定義は次のようになっている。
// 259: static void deadline_move_request(struct deadline_data *dd, struct dd_per_prio *per_prio, struct request *rq) { const enum dd_data_dir data_dir = rq_data_dir(rq); per_prio->next_rq[data_dir] = deadline_latter_request(rq); /* * take it off the sort and fifo list */ deadline_remove_request(rq->q, per_prio, rq); }
deadline_move_request関数では、次のリクエストを next_rqに保持し、deadline_remove_request関数を呼び出す。
deadline_remove_request関数の定義は次のようになっている。
// 192: static void deadline_remove_request(struct request_queue *q, struct dd_per_prio *per_prio, struct request *rq) { list_del_init(&rq->queuelist); /* * We might not be on the rbtree, if we are doing an insert merge */ if (!RB_EMPTY_NODE(&rq->rb_node)) deadline_del_rq_rb(per_prio, rq); elv_rqhash_del(q, rq); if (q->last_merge == rq) q->last_merge = NULL; }
deadline_remove_request関数は、該当するリクエストをハッシュと赤黒木から削除する。
おわりに
本記事では、mq-deadline I/Oスケジューラのdd_has_work関数とdd_dispatch_request関数を確認した。
変更履歴
- 2023/05/06: 記事公開
参考
- Deadline IO scheduler tunables — The Linux Kernel documentation
- Deadline IO scheduler 公式ドキュメント
- https://blog.51cto.com/u_15061941/3859244
- 中国の記事だが、v4.20のmq-deadlineについてソースコード分析する