dm integrity - arosh/arosh.github.com GitHub Wiki

チェックサムの計算は integrity_sector_checksum で行っているっぽい

https://elixir.bootlin.com/linux/v5.15.14/source/drivers/md/dm-integrity.c#L1640

呼び出し元は integrity_metadata と __journal_read_write と do_journal_write と integrity_recalc なのでたぶん bitmap モードで呼ばれるのは integrity_metadata

https://elixir.bootlin.com/linux/v5.15.14/source/drivers/md/dm-integrity.c#L1772

ディスクへの書き込みは dm_integrity_rw_tag で行っているっぽい

https://elixir.bootlin.com/linux/v5.15.14/source/drivers/md/dm-integrity.c#L1405

metadata device を分割したコミット

https://github.com/torvalds/linux/commit/356d9d52e1221ba0c9f10b8b38652f78a5298329

log2_buffer_sectors は dm_bufio_client_create の第二引数 block_size などに関係してくる。1 << SECTOR_SIZE << log2_buffer_sectors がよく計算される。

block_size は block(チェックサムひとつに対応するディスク領域のサイズ)のサイズで 512 ~ 4096。sectors_per_bit は何セクタがビットマップの1ビットに対応するか。512*sectors_per_bit >= block_size

https://elixir.bootlin.com/linux/v5.15.14/source/drivers/md/dm-integrity.c#L4328

block_size を 4096 としたとき、40TB のボリュームに対するチェックサムの数は 10^10 程度になる。もしチェックサムが4バイト (2^32) しかなかったら1回のバックアップで数個くらいは意図せずチェックサムが重複してしまう。よってチェックサムは8バイトくらいは欲しい。このときチェックサムの総容量は 80GB。crc64 は無いので md5 か sha1 か sha256 か

https://elixir.bootlin.com/linux/v5.15.14/source/crypto/hash_info.c#L11

xxhash64 も使えるのか?

https://elixir.bootlin.com/linux/v5.15.14/source/crypto/xxhash_generic.c

get_mac から結局 crypt_larval_lookup が呼ばれる

crypto_alg_list に登録されていればよい

https://elixir.bootlin.com/linux/v5.15.23/source/crypto/api.c#L61

__crypto_register_alg 経由で登録されてそう。

interleave と area について。meta_dev モードでない場合はタグ領域とデータ領域が交互に並ぶようなデータフォーマットになる。タグ領域とデータ領域の一組を area と呼び、ひとつの area に含まれるデータ領域のセクタ数を --interleave-sectors で渡している。つまり data_sector / interleave_sectors で何番目の area なのか分かるし、data_sector % interleave_sectors で area 内の何番目のセクタなのか (offset) が分かる。ちなみにこれは meta_dev モードでない場合の話で、meta_dev モードでは area = 0, offset = data_sector である。

area と offset から data_sector に該当するタグの sector と offset を入れているのが dio->metadata_block と dio->metadata_offset である。tag_size が計算に入るのは分かるのだが buffer_sectors が入る理由がよく分からない。

https://elixir.bootlin.com/linux/v5.15.14/source/drivers/md/dm-integrity.c#L1931

--tag_size には2冪以外の整数も与えることができる。2冪の場合は log2_tag_size が設定され、そうでない場合は -1 になる。

https://elixir.bootlin.com/linux/v5.15.14/source/drivers/md/dm-integrity.c#L4155

internal_hash を渡さなかったときは仮想アドレスが書き込まれるらしい。

https://elixir.bootlin.com/linux/v5.15.15/source/drivers/md/dm-integrity.c#L1822

calculate_device_limits におけるサイズの計算

// * SB_SECTORS は dm-integrity.c 内に定義されているスーパーブロックのサイズで 8
// * ic->journal_section_sectors = (ic->journal_section_entries << ic->sb->log2_sectors_per_block) + JOURNAL_BLOCK_SECTORS;
//   https://elixir.bootlin.com/linux/v5.15.23/source/drivers/md/dm-integrity.c#L3370
// * ic->journal_sections は 
initial_sectors = SB_SECTORS + (__u64)ic->journal_section_sectors * ic->journal_sections;

ic->journal_section_sectors * ic->journal_sections はビットマップのサイズになるはず。ならないと困る。

// タグの数 * タグサイズ
__u64 meta_size = (ic->provided_data_sectors >> ic->sb->log2_sectors_per_block) * ic->tag_size;

// meta_size が buffer_sectors * 512 の倍数でなければその分だけ拡張する
// ceil(meta_size / (buffer_sectors * 2^SECTOR_SHIFT))
meta_size = (meta_size + ((1U << (ic->log2_buffer_sectors + SECTOR_SHIFT)) - 1))
		>> (ic->log2_buffer_sectors + SECTOR_SHIFT);

// SECTOR_SHIFT が入っていないので、meta_size は最終的にセクタ数である
meta_size <<= ic->log2_buffer_sectors;

dm_integrity_map

REQ_PREFLUSH が付いていたとき

https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L1867

dm_integrity_io から bio を取り出して &ic->flush_bio_list という連結リストに追加してからワークキューを呼ぶ

https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L1571

queue_work で呼ばれる処理の実態である &ic->commit_work は integrity_commit である

https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L4224

container_of(ptr, type, member) は構造体 type のインスタンスであり x.member = ptr の関係にあるような x を取ってくるというマクロ。

https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L2339

bio のフラグ (REQ_OP_FLUSH, REQ_FUA, REQ_PREFLUSH)

https://qiita.com/rarul/items/4465db56a5cd546a3462#req_op_flush-req_fua-req_preflush

設定

https://gitlab.com/cryptsetup/cryptsetup/-/wikis/DMIntegrity

key value
start_sector 0 で OK
size 最終的なデバイスのサイズ(512B セクターで)
taget name integrity
data_dev /dev/sdX か major:minor で
offset デバイスのどこからを対象とするか(通常は 0)
tag_size セクタ当たりのタグサイズ(バイト)。8 (64bits) は欲しい。確かデフォルトで internal_hash のサイズが使われたような気がする
mode bitmap mode を表す B
interleave_sectors meta_dev モードの場合には関係なかった気がする
buffer_sectors デフォルト値は 128
block_size デフォルト値は 512 だがそれだと tag_size=8 のとき 1/64 のサイズになってしまって結構大きい
journal_watermark bitmap mode では関係ないはず
commit_time bitmap mode では関係ないはず
internal_hash xxhash64 指定できるのかな?
meta_device /dev/sdX か major:minor で
recalculate
sectors_per_bit bitmap mode でビットに対応するデータデバイスのセクタ数。タグ本体に比べれば ε だが大きいほどビットマップの書き換えが増える
bitmap_flush_interval ここで指定したミリ秒ごとに CLEAR の bitmap をフラッシュする。commit_time と同様に FLUSH のときは強制的にフラッシュしてくれるよね?? SET は FLUSH であろうがなかろうが即フラッシュ(正確には同期書き込み)。このパラメータは CLEAR の場合
fix_padding 互換性のためのパラメータらしい。meta_dev を使っている場合は関係なし
allow_discards allow とはいったいどういう意味で言っているのか
fix_hmac
legacy_recalculate
sudo integritysetup format ${META_DEV} --data-device ${DATA_DEV} --no-wipe --tag-size 8 --integrity xxhash64 --integrity-bitmap-mode --buffer-sectors 1
sudo integritysetup open ${META_DEV} tbs --data-device ${DATA_DEV} --integrity-recalculate --integrity xxhash64 --integrity-bitmap-mode --buffer-sectors 1 --bitmap-sectors-per-bit 128
  • integritysetup の --sector-size は dmsetup の block_size と同じ?
  • --buffer-sectors は大きくすると性能が落ちる。謎
tbs: 0 83886080 integrity 253:1 0 8 B 7 meta_device:253:0 journal_sectors:130944 interleave_sectors:1 buffer_sectors:1 sectors_per_bit:32768 bitmap_flush_interval:10000 internal_hash:xxhash64

meta_dev を指定しても data_dev が勝手に初期化される。なんてバグだ… --no-wipe と --integrity-recalculate を使う必要がある。

https://gitlab.com/cryptsetup/cryptsetup/-/issues/679

--bitma-sectors-per-bit を指定するとなぜか Invalid argument になる。

https://gitlab.com/cryptsetup/cryptsetup/-/blob/00859854191b27dd62e277e8adba4e575d6e7dca/lib/libcryptsetup.h#L564-567

$ sudo integritysetup format ${META_DEV} --data-device ${DATA_DEV} --tag-size 8 --integrity xxhash64 --integrity-bitmap-mode --buffer-sectors 1 --bitmap-sectors-per-bit 32768

WARNING!
========
This will overwrite data on /dev/mapper/tbs-meta irrevocably.

Are you sure? (Type uppercase yes): YES
device-mapper: reload ioctl on   failed: Invalid argument
Cannot format integrity for device /dev/mapper/tbs-meta.
kprobe:dm_table_add_target {
  printf("%s\n", str(arg4));
}

sudo env BPFTRACE_STRLEN=200 bpftrace trace.bt すると以下の出力が得られた

/dev/mapper/tbs-data 0 8 J 4 block_size:512 buffer_sectors:1 internal_hash:xxhash64 meta_device:/dev/mapper/tbs-meta
/dev/mapper/tbs-data 0 8 J 5 journal_watermark:128 block_size:512 buffer_sectors:1 internal_hash:xxhash64 meta_device:/dev/mapper/tbs-meta

なるほどね

https://github.com/torvalds/linux/blob/ea4424be16887a37735d6550cfd0611528dbe5d9/drivers/md/dm-integrity.c#L4065-L4066

format のときは --bitmap-sectors-per-bit を付けなければいいらしい

sho_iizuka@golang:~/integrity$ cat fio.sh
#!/bin/sh
set -uex

DIR=/mnt/tbs-data
IODEPTHs="1 2 4 8 16 32 64"
NUMJOBSs="1 2 4 8"

for NUMJOBS in ${NUMJOBSs}; do
  for IODEPTH in ${IODEPTHs}; do
    NAME="iodepth_${IODEPTH}-numjobs_${NUMJOBS}"
    fio --direct=1 --ioengine=libaio --iodepth=${IODEPTH} --readwrite=randwrite --bs=16k --numjobs=${NUMJOBS} --directory=${DIR} --size=4g --runtime=30 --time_based --ramp_time=2s --group_reporting -name=fio --output-format=json > ${NAME}.json
  done
done
sho_iizuka@golang:~/integrity$ cat show.sh
#!/bin/sh
set -ue

DIR=/mnt/tbs-data
IODEPTHs="1 2 4 8 16 32 64"
NUMJOBSs="1 2 4 8"

for NUMJOBS in ${NUMJOBSs}; do
  for IODEPTH in ${IODEPTHs}; do
    NAME="iodepth_${IODEPTH}-numjobs_${NUMJOBS}"
    IOPS=$(cat ${NAME}.json | jq '.jobs[0].write.iops')
    echo -n "${IOPS}"
    echo -n -e "\t"
  done
  echo
done
sho_iizuka@golang:~/integrity$

疑問

bitmap のフォーマットは?

スーパーブロックの後ろに詰め込んでいるだけっぽい

bitmap への SET の FLUSH が終わるまでデータ領域の変更に関する bio を発行できないと思うが?

フラグに REQ_SYNC が付いているが同期 I/O という意味ではないという情報もあるのでよく分からない。ということは REQ_FUA のほうか?

dm_io の引数では io_req.notify.fn = NULL になっているのでこれでは検知できない。commit_wq のキューをつかってうまく制御している?

結論:io_req.notify.fn == NULL だったら同期 I/O になる

https://elixir.bootlin.com/linux/v5.15.14/source/drivers/md/dm-io.c#L547

integrity_commit の中で呼んでいる dm_integrity_flush_buffers で ic->bufio を書き出しているが ic->bufio にはどういうものが書かれているか?

--buffer-sectors は結局何が変わるパラメータなのか?

チェックサムの読み書きをするのに使われる ic->bufio インスタンスを生成する際に dm_bufio_client_create() の block_size というパラメータで与えられる。そのサイズを基準として読み書きをするのでチェックサムのディスクレイアウトも block_size の倍数になるようにする必要があるし、読み書きの際に与えるポジションも block_size を基準に与えなければならない。ただしディスクレイアウトに関しては meta_dev != NULL の場合には結局は連続して配置されるのであまり気にしなくてよい。

以下のように初期化されている。31 - SECTOR_SHIFT1 << log2_buffer_sectors << SECTOR_SHIFT が 2^31 になるようにということだろう。2^32-1 以下の2冪ということだろうか。

ic->log2_buffer_sectors = min((int)__fls(buffer_sectors), 31 - SECTOR_SHIFT);

meta_dev には スーパーブロック、ビットマップ、チェックサムが並ぶが、ビットマップのサイズはどうやって決まるか?

チェックサムの読み書きに使われる dm_bufio_client の初期化時に ic->initial_sectors を基準位置に設定している。ic->start は dmsetup で与えるオフセットなので気にしなくてよい。

https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L4433

ic->initial_sectors は calculate_device_limits() で設定される。

https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L3381

ビットマップ用の領域のサイズに関するアサーションが入っていないではないか!とびっくりするが、必要なビットマップ領域がディスクのサイズに収まるように sectors_per_bit を大きくするような調整が入る。

https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L4336

ビットマップを CLEAR してよいのはデータとチェックサムの FLUSH 後だが、どうやって保証しているか?

ここで dm_integrity_range が登場している。bitmap_block_work() の使い方を見たところ、データとチェックサムが一致していないかもしれない状態に入ったら remove_range() している。

ベンチマーク

sudo lvcreate -L 40g -n data crypt
sudo lvcreate -L 4g -n meta crypt
META_DEV=/dev/crypt/meta
DATA_DEV=/dev/crypt/data
SECTORS_PER_BIT=128 2048 32768
BUFFER_SECTORS=1 16 128
sudo integritysetup format ${META_DEV} --data-device ${DATA_DEV} --no-wipe --integrity xxhash64 --sector-size 4096 --integrity-bitmap-mode --buffer-sectors ${BUFFER_SECTORS}
sudo integritysetup open ${META_DEV} integrity --data-device ${DATA_DEV} --integrity-recalculate --integrity xxhash64 --integrity-bitmap-mode --buffer-sectors ${BUFFER_SECTORS} --bitmap-sectors-per-bit ${SECTORS_PER_BIT}

チューニングが必要なパラメータ
- --bitmap-sectors-per-bit
- --buffer-sectors

Usage: integritysetup [-?VvqDRB] [-?|--help] [--usage] [-V|--version] [-v|--verbose] [--debug] [-q|--batch-mode]
        [--progress-frequency=secs]
        [--no-wipe]
        [--data-device=path]
        [-j|--journal-size=bytes] // これは bitmap 領域のサイズに関係しそう
        [--interleave-sectors=SECTORS] // meta_dev なら関係なし
        [--journal-watermark=percent] // 関係なし
        [--journal-commit-time=ms] // 関係なし
        [--bitmap-sectors-per-bit=INT] // sectors_per_bit
        [--bitmap-flush-time=ms] // bitmap_flush_interval
        [-t|--tag-size=bytes] // tag_size
        [-s|--sector-size=bytes] // たぶん dmsetup でいうところの block_size。512 ~ 4096 が設定可能
        [--buffer-sectors=SECTORS] // buffer_sectors
        [-I|--integrity=STRING] // ハッシュ関数
        [--integrity-key-size=BITS]
        [--integrity-key-file=STRING]
        [--journal-integrity=STRING]
        [--journal-integrity-key-size=BITS]
        [--journal-integrity-key-file=STRING]
        [--journal-crypt=STRING]
        [--journal-crypt-key-size=BITS]
        [--journal-crypt-key-file=STRING]
        [-D|--integrity-no-journal]
        [-R|--integrity-recovery-mode]
        [-B|--integrity-bitmap-mode] // これを使う
        [--integrity-recalculate]
        [OPTION...] <action> <action-specific>

sudo mkfs.ext4 /dev/mapper/integrity
sudo mount /dev/mapper/integrity /mnt/data
sudo chown sho_iizuka /mnt/data/
fio --direct=1 --ioengine=libaio --iodepth=64 --readwrite=randwrite --bs=16k --numjobs=8 --directory=/mnt/data --size=2g --runtime=60 --time_based --ramp_time=5s --group_reporting -name=fio
sudo umount /mnt/data
sudo integritysetup close integrity
⚠️ **GitHub.com Fallback** ⚠️