blk_mq デバイスドライバを作る Part1

Part2 : http://d.hatena.ne.jp/w_o/20160516#1463367951

残念ながら馬4th editionは来年の12月に出ることが決まったので、blk_mq ドライバの作りかたは、ソースを読むしかないのだった。


以下、簡単にblk_mqドライバの作りかたを残しておく。


まず、blk_mq だが、非常に簡単に言うと、

  • 普通のblock deviceでは、一本のキューとドライバでspin_lockを共有していた
  • blk_mq では、デバイス毎に複数キューが作れて、キューごとにspinlockを持ってる

と、なる。


複数キューがあったときの挙動は調べてないので知らないけど、基本的にはロック戦略が変わってるだけで、普通のブロックデバイスとそんなにAPIが違うわけではない。


一番参考になるのは、ベンチマーク用のnull_blkドライバだろうか。
https://github.com/torvalds/linux/blob/master/drivers/block/null_blk.c
モジュールパラメータで、普通のブロックとblk_mqを切り替えられるようになっている。


まあこれはこれで計測用に場合分け処理が入ってるからわかりにくいんだけど…


blk_mqはAPI的には普通のブロックデバイスとそんなに変わらないので、とりあえず普通のブロックデバイスの書きかたを知っておく必要がある。

https://static.lwn.net/images/pdf/LDD3/ch16.pdf

各自読んで。

実際動いているコードだと、
https://github.com/torvalds/linux/blob/master/drivers/block/ps3disk.c
PS3 storage 用のドライバが変な処理無くてわかりやすいと思う。(でもDMA使ってないのでそのまま参考にはしづらい)


大体、以下のような感じだ。

struct driver {
    spinlock_t lock;
    struct request_queue *queue;
    struct gendisk *disk;
};

void request(struct request_queue *q) {
    struct request *req;
    while (req = blk_fetch_request(q)) {
        if (req->cmd_type == XX) {
            do_somthing(req);
        }
        __blk_end_request_all(req,0);
    }
}

void probe()
{
    struct driver *drv = init_driver();

    disk = gendisk(request_func, &drv->lock);
    queue = blk_init_queue
    disk->queue = queue;

    add_disk(disk);
}


まあもうちょっとちゃんと書いて割り込み使って非同期にするなら、

struct driver {
    spinlock_t lock;
    struct request_queue *queue;
    struct gendisk *disk;
};

irqreturn_t irq_handler(int irq)
{
    struct request *req = get_current_request();
    blk_end_request_all(req,0);
}

void request(struct request_queue *q) {
    struct request *req;
    while (req = blk_fetch_request(q)) {
        if (req->cmd_type == XX) {
            start_device(req);
        }
    }
}

void probe()
{
    struct driver *drv = init_driver();

    request_irq(IRQ, irq_handler);

    disk = gendisk(request_func, &drv->lock);
    queue = blk_init_queue
    disk->queue = queue;

    add_disk(disk);
}

こんな感じ。

blk_mq もこの形はほとんど変わらない。


ただ、普通のブロックデバイスは、

  • 普通のブロックデバイスは、 request() が呼ばれる時に gendisk() で登録したspinlockがロックされている
    • ドライバがデバイスを操作してる時に、リクエストキューにリクエストを積んでいくことができない
  • blk_end_request_all のように、struct request の操作をする時にもspinlockをかける必要がある
    • これはうまく作ればキューにリクエストを詰む操作とは独立して実行できるはず

という問題があって、それがblk_mqでは改善されてる


(明日か近い日へ続く)