テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(dm-writeboost report) マイグレーションのカーネルデーモン化

dm-writeboostの特徴の一つに, 自律的なマイグレーションがあります. 自律的というのは, 「backing storeの負荷に応じてマイグレーションするかどうかを決定する」というものです. 本来は, throttleするのが良いんだけど, 1秒ごとのon/offによってこれを近似している.

この機能, 今はPythonDaemonというライブラリを使ってユーザランドに実装されている. ユーザランドのデーモンがdm-writeboostのsysfsや/proc/diskstatsを1秒ごとにポーリングして, その状態変化に応じて, 前記sysfsに対してwriteすることでカーネルの挙動を変化させる. 具体的には, allow_migrateという変数を1/0スイッチさせる.

この実装をとった背景には, この機能を開発した当時, この機能がどの程度実装複雑になるか分からなかったので, カーネルから分離したかったという事情がある. そして, もし実装が複雑でなければカーネルにマージしようと思っていたし, そう出来る設計になっている. 安全に開発を進めつつも含みを残した.

他の問題は,

  • PythonDaemonというライブラリの挙動がどうも不可解. こいつをstopした時に, デーモンが消滅してくれない. 開発者に問い合わせたが返答がない. activeではないのかも知れない. 心情的にも, このライブラリへの依存を切りたい. 不愉快である. このおれを誰だと思っているんだ.
  • このデーモンは「なくてもdm-writeboostは動く」という意味でessentialではないという書き方を実はしているのだが, nearly essentialと言うべきものであり, Pythonのライブラリを入れないと機能をONに出来ないというのは使い勝手の点では致命的かも知れない.
  • 毎回ポーリングして計算をするというのが行儀悪い. カーネルからであればより簡潔に処理出来る可能性がある.

ざっと実装の調査:

struct disk_stats {
        unsigned long sectors[2];       /* READs and WRITEs */
        unsigned long ios[2];
        unsigned long merges[2];
        unsigned long ticks[2];
        unsigned long io_ticks;
        unsigned long time_in_queue;
};

たぶん, これでアクセスすることが求められている?

#define part_stat_read(part, field)     ((part)->dkstats.field)

例えば, 以下のようなコードで使われている. print文の中で文字列結合するの, checkpatchでハネられませんかね.

ssize_t part_stat_show(struct device *dev,
                       struct device_attribute *attr, char *buf)
{
        struct hd_struct *p = dev_to_part(dev);
        int cpu;

        cpu = part_stat_lock();
        part_round_stats(cpu, p);
        part_stat_unlock();
        return sprintf(buf,
                "%8lu %8lu %8llu %8u "
                "%8lu %8lu %8llu %8u "
                "%8u %8u %8u"
                "\n",
                part_stat_read(p, ios[READ]),
                part_stat_read(p, merges[READ]),
                (unsigned long long)part_stat_read(p, sectors[READ]),
                jiffies_to_msecs(part_stat_read(p, ticks[READ])),
                part_stat_read(p, ios[WRITE]),
                part_stat_read(p, merges[WRITE]),
                (unsigned long long)part_stat_read(p, sectors[WRITE]),
                jiffies_to_msecs(part_stat_read(p, ticks[WRITE])),
                part_in_flight(p),
                jiffies_to_msecs(part_stat_read(p, io_ticks)),
                jiffies_to_msecs(part_stat_read(p, time_in_queue)));
}

これは以下を表示するために使われている.

root@Kamille:/home/akira# cat /sys/block/sda/stat
   14950     9859   453142    16116     1417     2794    46808     9008        0    10920    23356

そして, /proc/diskstatsと同等の情報である.

root@Kamille:/home/akira# cat /proc/diskstats
   8       0 sda 14950 9859 453142 16116 1424 2796 46880 9008 0 10920 23356 # これ
   8       1 sda1 14459 9828 448978 15908 1386 2796 46880 8972 0 10712 23128
   8       2 sda2 2 0 4 0 0 0 0 0 0 0 0
   8       5 sda5 322 31 2824 132 0 0 0 0 0 116 116
  11       0 sr0 0 0 0 0 0 0 0 0 0 0 0
  11       1 sr1 0 0 0 0 0 0 0 0 0 0 0
   7       0 loop0 0 0 0 0 0 0 0 0 0 0 0
   7       1 loop1 0 0 0 0 0 0 0 0 0 0 0

diskstatsの方はこちらで実装されている.

static int diskstats_show(struct seq_file *seqf, void *v)
{
        struct gendisk *gp = v;
        struct disk_part_iter piter;
        struct hd_struct *hd;
        char buf[BDEVNAME_SIZE];
        int cpu;

        /*
        if (&disk_to_dev(gp)->kobj.entry == block_class.devices.next)
                seq_puts(seqf,  "major minor name"
                                "     rio rmerge rsect ruse wio wmerge "
                                "wsect wuse running use aveq"
                                "\n\n");
        */

        disk_part_iter_init(&piter, gp, DISK_PITER_INCL_EMPTY_PART0);
        while ((hd = disk_part_iter_next(&piter))) {
                cpu = part_stat_lock();
                part_round_stats(cpu, hd);
                part_stat_unlock();
                seq_printf(seqf, "%4d %7d %s %lu %lu %lu "
                           "%u %lu %lu %lu %u %u %u %u\n",
                           MAJOR(part_devt(hd)), MINOR(part_devt(hd)),
                           disk_name(gp, hd->partno, buf),
                           part_stat_read(hd, ios[READ]),
                           part_stat_read(hd, merges[READ]),
                           part_stat_read(hd, sectors[READ]),
                           jiffies_to_msecs(part_stat_read(hd, ticks[READ])),
                           part_stat_read(hd, ios[WRITE]),
                           part_stat_read(hd, merges[WRITE]),
                           part_stat_read(hd, sectors[WRITE]),
                           jiffies_to_msecs(part_stat_read(hd, ticks[WRITE])),
                           part_in_flight(hd),
                           jiffies_to_msecs(part_stat_read(hd, io_ticks)),

backing storeについてdiskstatsを引ければ勝ちだと思うので, dm_dev->block_device->hd_structを引けば良いのかな. 何かマクロがあればそれを使うものだけど, 以下のように直接引いてるところがあるので, たぶんこの方法で良いのだろうと思う.

static inline void blk_partition_remap(struct bio *bio)
{
        struct block_device *bdev = bio->bi_bdev;

        if (bio_sectors(bio) && bdev != bdev->bd_contains) {
                struct hd_struct *p = bdev->bd_part; // ここ

あとはカーネル内のロック設計でしょうか. キャッシュとbacking storeの接続関係がユーザランドの要請で変化するのと, カーネルデーモンがこれを利用することのアクセスが同時に起こると破滅する可能性がある(具体的にはぬるぽ).