関連記事
- 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
概要
QEMUの vexpress-a9 (arm) で Linux 5.15を起動させながら、ファイル書き込みのカーネル処理を確認していく。
本章では、mq-deadline I/Oスケジューラの概要紹介と初期処理(init_sched/init_hctx)・解放処理(exit_sched)を確認した。
はじめに
ユーザプロセスはファイルシステムという機構によって記憶装置上のデータをファイルという形式で書き込み・読み込みすることができる。
本調査では、ユーザプロセスがファイルに書き込み要求を実行したときにLinuxカーネルではどのような処理が実行されるかを読み解いていく。
調査対象や環境などはPart 1: 環境セットアップを参照。
mq-deadlineの概要
mq-deadline I/Oスケジューラは、deadline I/Oスケジューラをマルチキュー向けに作り直されたI/Oスケジューラである。
deadline (mq-deadline) I/Oスケジューラでは、現在処理中のリクエストから近いI/Oリクエストから順に処理をしていくが、deadlineを過ぎているリクエストは優先して処理される。
mq-deadline I/Oスケジューラでは、セクタ位置をキーとする赤黒木とリクエスト挿入順を保持するFIFOのデータ構造を持ち、これらがReadとWriteの2種類が用意される。
さらに、mq-deadlineでは I/O優先度RT
, BE
, IDLE
がサポートされており、優先度の高いリクエストがすべて終了した後に、低い優先度の要求がdispatchされることが保証されている。
mq-deadline I/Oスケジューラでは、同期リクエストのためにリクエストキューの25%を確保している。(後述するasync_depth
で変更可能)
そして、ReadリクエストはWriteリクエストより優先されて処理される。
mq-deadlineのパラメータ
mq-deadline I/Oスケジューラでは、いくつかのパラメータをsysfs経由で設定・確認することができる。
# ls /sys/block/mmcblk0/queue/iosched
async_depth front_merges write_expire
fifo_batch read_expire writes_starved
read_expire
: Readリクエストのdeadline (ミリ秒)write_expire
: Writeリクエストのdeadline (ミリ秒)fifo_batch
: FIFOデータ構造のサイズ (リクエスト数)writes_starved
: Readリクエストのディスパッチ回数に対するWriteリクエストのディスパッチ回数の優先回数(ディスパッチ数)front_merges
: フロントマージの有効/無効化 (Bool値)async_depth
: 非同期要求が占有できるリクエストキュー (リクエスト数)
また、debugfsからはmq-deadline I/Oスケジューラのステータスを確認することができる。
# ls /sys/kernel/debug/block/mmcblk0/sched/
async_depth owned_by_driver read1_next_rq write0_next_rq
batching queued read2_fifo_list write1_fifo_list
dispatch0 read0_fifo_list read2_next_rq write1_next_rq
dispatch1 read0_next_rq starved write2_fifo_list
dispatch2 read1_fifo_list write0_fifo_list write2_next_rq
ここで、0
はRT
, 1
はBE
, 2
はIDLE
を表している。
deadline_data構造体
Linux の IO scheduler elevetor_queue
のelevetor_data
に各IO scheduler固有のデータ構造を設定することができる。
mq-deadlineでは、deadline_data
が設定される。
// 79: struct deadline_data { /* * run time data */ struct dd_per_prio per_prio[DD_PRIO_COUNT]; /* Data direction of latest dispatched request. */ enum dd_data_dir last_dir; unsigned int batching; /* number of sequential requests made */ unsigned int starved; /* times reads have starved writes */ struct io_stats __percpu *stats; /* * settings that change how the i/o scheduler behaves */ int fifo_expire[DD_DIR_COUNT]; int fifo_batch; int writes_starved; int front_merges; u32 async_depth; spinlock_t lock; spinlock_t zone_lock; };
mq-deadlineでは、リクエストをREAD
とWRITE
に分類し、それぞれを別のキュー(赤黒木)で管理する。
ここでは、dir
(direction)として扱われ、dd_data_dir
で定義されている。
// 38: enum dd_data_dir { DD_READ = READ, DD_WRITE = WRITE, }; enum { DD_DIR_COUNT = 2 };
mq-deadlineではリクエストを、dd_per_prio
構造体にある キュー(赤黒木)で管理する。
この構造体は、各I/O優先度に用意される。
// 71: struct dd_per_prio { struct list_head dispatch; struct rb_root sort_list[DD_DIR_COUNT]; struct list_head fifo_list[DD_DIR_COUNT]; /* Next request in FIFO order. Read, write or both are NULL. */ struct request *next_rq[DD_DIR_COUNT]; };
mq-deadlineの関数群
ここでは、初期処理(init_sched
/init_hctx
)・解放処理(exit_sched
)の関数のみ注目する。
dd_init_sched
dd_init_sched
関数は、elevetor_type
にあるops
のinit_sched
に設定され、blk_mq_init_sched
関数から呼び出される関数となっている。
init_sched
ではrequest_queue
(I/Oスケジューラ固有のelevator_data
を含む) の確保と request_queue->elevator
への設定をする。
dd_init_sched
関数の定義は次の通りとなっている。
// 552: static int dd_init_sched(struct request_queue *q, struct elevator_type *e) { struct deadline_data *dd; struct elevator_queue *eq; enum dd_prio prio; int ret = -ENOMEM; eq = elevator_alloc(q, e); if (!eq) return ret; dd = kzalloc_node(sizeof(*dd), GFP_KERNEL, q->node); if (!dd) goto put_eq; eq->elevator_data = dd; dd->stats = alloc_percpu_gfp(typeof(*dd->stats), GFP_KERNEL | __GFP_ZERO); if (!dd->stats) goto free_dd; for (prio = 0; prio <= DD_PRIO_MAX; prio++) { struct dd_per_prio *per_prio = &dd->per_prio[prio]; INIT_LIST_HEAD(&per_prio->dispatch); INIT_LIST_HEAD(&per_prio->fifo_list[DD_READ]); INIT_LIST_HEAD(&per_prio->fifo_list[DD_WRITE]); per_prio->sort_list[DD_READ] = RB_ROOT; per_prio->sort_list[DD_WRITE] = RB_ROOT; } dd->fifo_expire[DD_READ] = read_expire; dd->fifo_expire[DD_WRITE] = write_expire; dd->writes_starved = writes_starved; dd->front_merges = 1; dd->last_dir = DD_WRITE; dd->fifo_batch = fifo_batch; spin_lock_init(&dd->lock); spin_lock_init(&dd->zone_lock); q->elevator = eq; return 0; free_dd: kfree(dd); put_eq: kobject_put(&eq->kobj); return ret; }
dd_init_sched
関数では、I/O優先度毎にリスト(赤黒木)と各パラメータを初期化する。
dd_init_hctx
dd_init_hctx
関数は、elevetor_type
にあるops
のinit_hctx
に設定され、blk_mq_init_sched
関数から呼び出される関数となっている。
init_hctx
ではHardware dispatch queuesをI/Oスケジューラに向けて初期化する。
dd_init_hctx
関数の定義は次の通りとなっている。
// 526: static int dd_init_hctx(struct blk_mq_hw_ctx *hctx, unsigned int hctx_idx) { dd_depth_updated(hctx); return 0; }
dd_init_hctx
関数は、後述するdd_depth_updated
関数を呼び出す。
dd_depth_updated
dd_depth_updated
関数は、elevetor_type
にあるops
のdepth_updated
に設定され、blk_mq_update_nr_requests
関数から呼び出される関数となっている。
depth_updated
では、リクエストキューの深さが変化したときにI/Oスケジューラの内部情報を更新することができる。
dd_depth_updated
関数の定義は次の通りとなっている。
// 514: static void dd_depth_updated(struct blk_mq_hw_ctx *hctx) { struct request_queue *q = hctx->queue; struct deadline_data *dd = q->elevator->elevator_data; struct blk_mq_tags *tags = hctx->sched_tags; dd->async_depth = max(1UL, 3 * q->nr_requests / 4); sbitmap_queue_min_shallow_depth(tags->bitmap_tags, dd->async_depth); }
mq-deadline I/Oスケジューラでは、非同期リクエストが占有できる割合をdd->async_depth
に設定することができる。
ただし、最低でも25% () は同期リクエストのために確保されているので、dd->async_depth
に設定できる値は高々75% ()となる。
dd_exit_sched
dd_exit_sched
関数は、elevetor_type
にあるops
のexit_sched
に設定され、blk_mq_exit_sched
関数から呼び出される関数となっている。
exit_sched
ではI/Oスケジューラ周りのエラーハンドリングやI/Oスケジューラの変更により、データ構造を解放する。
dd_exit_sched
関数の定義は次の通りとなっている。
// 532: static void dd_exit_sched(struct elevator_queue *e) { struct deadline_data *dd = e->elevator_data; enum dd_prio prio; for (prio = 0; prio <= DD_PRIO_MAX; prio++) { struct dd_per_prio *per_prio = &dd->per_prio[prio]; WARN_ON_ONCE(!list_empty(&per_prio->fifo_list[DD_READ])); WARN_ON_ONCE(!list_empty(&per_prio->fifo_list[DD_WRITE])); } free_percpu(dd->stats); kfree(dd); }
おわりに
本記事では、ブロックレイヤのblk_mq_submit_bio
関数を確認した。
変更履歴
- 2023/02/27: 記事公開
参考
- Deadline IO scheduler tunables — The Linux Kernel documentation
- Deadline IO scheduler 公式ドキュメント
- mq-deadline调度器原理及源码分析_51CTO博客_mq deadline
- 中国の記事だが、v4.20のmq-deadlineについてソースコード分析する
- LinuxのI/OスケジューラのDeadlineを調べてチューニングしてみた - YOMON8.NET
- Deadline I/Oスケジューラのパラメータチューニング