blk_mq デバイスドライバを作る Part2 (終)
Part1 : http://d.hatena.ne.jp/w_o/20160511#1462967279
blk_mq では、APIとしては大きく二点変更がある。
- 普通のblockでは、request() コールバックにキューごと渡されてきたが、blk_mq では、リクエストごとにコールバックが呼ばれる
- blk_xx みたいな API は、 blk_mq_xx みたいになる
これに加えて、前回書いたように、ロックが細粒度になってるので、競合に注意するという点が増える。
struct driver { spinlock_t lock; struct request_queue *queue; struct gendisk *disk; struct blk_mq_tag_set tag_set; // 設定はここに入れる struct blk_mq_hw_ctxt *ctxts[NUM_QUEUE]; }; struct drv_req_data { int foo; int bar; }; irqreturn_t irq_handler(int irq) { struct request *req = get_current_request(); blk_mq_end_request(req,0); } void queue_rq(struct blk_mq_hw_context *ctxt, const struct blk_mq_queue_data *data) // リクエストごとにコールバックに来る { spin_lock(&drv->lock); // これはロックされないで来る start_device(data->rq); spin_unlock(&drv->lock); } int init_hctx(struct blk_mq_hw_ctx *hctx, void *data, unsigned int index) { struct drviver *driver = (struct driver*) data; driver->ctxts[index] = hctx; hctx->driver_data = driver; } static struct blk_mq_ops drv_mq_ops = { .queue_rq = queue_rq, .init_hctx = init_hctx, }; void probe() { struct driver *drv = init_driver(); request_irq(IRQ, irq_handler); drv->tag_set.ops = &drv_mq_ops; drv->tag_set.nr_hw_queues = NUM_QUEUE; // 複数キューを作れる。これの数だけinit_hctxが呼ばれる (と思う未確認) drv->tag_set.queue_depth = QUEUE_DEPTH; // キュー深さ。これが埋まるまでqueue_rq が呼ばれる(と思う未確認) drv->tag_set.numa_node = NUMA_NO_NODE; // 未確認 drv->tag_set.cmd_size = sizeof(struct drv_req_data); // 後述 drv->tag_set.flags = BLK_MQ_F_SHOULD_MERGE; // 未確認 drv->tag_set.driver_data = drv; blk_mq_alloc_tag_set(&drv->tag_set); queue = blk_mq_init_queue(&drv->tag_set); disk = alloc_disk(NUM_MINOR); disk->queue = queue; add_disk(disk); }
こんな感じになる。
そんなに大きな違いは無い。(いや字面だけ見たら大分変わったように見えるな…まあドライバ本体は、start_device()にあるはずなので、そこは変更しなくてよいという意味で…)
細かい変更がいくつかある。まず、複数キューを作れるようになっている。NVMeとか最近のハイエンドストレージは、複数ハードウェアキューを持てる(まあでもHW内でソフト処理するんだからハードウェアキューというのも変だが)ので、それにあわせて、一個のブロックデバイスで、複数キューが持てるようになっている。
というか、blk-mq の mq は、multi-queue の略で、これが目的だから、細かい点というのも変なのだけど…まあ、multi-queue を実現するために、ロック戦略の変更が必要になって、結果としてロック戦略の変更のほうが大きな変更になってるように感じる。
次の変更点として、リクエスト毎に付けられるドライバ依存のデータが、ブロックレイヤで割り当てられるようになっている。(cmd_size の部分。上の例では使ってないのであまり良い例ではない https://github.com/torvalds/linux/blob/master/drivers/block/null_blk.c#L354 の、null_cmd のあたり)
これのメリットは真面目に計測してないのでよくわからないけど、
http://events.linuxfoundation.org/sites/events/files/slides/scsi.pdf
に、"Avoids per-request allocations in the driver"とあるので、まあなんか意味あるんではないのかな…まあNVMeだと最大64Kリクエストとか積めるわけで、メモリ割り当ても管理するのが大事なのかもしれない。
(書くことなくなったので終わり)