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
がよく計算される。
- https://elixir.bootlin.com/linux/v5.15.14/source/drivers/md/dm-integrity.c#L4129
- https://elixir.bootlin.com/linux/v5.15.14/source/drivers/md/dm-integrity.c#L4420
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 が呼ばれる
- https://elixir.bootlin.com/linux/v5.15.23/source/drivers/md/dm-integrity.c#L3655
- https://elixir.bootlin.com/linux/v5.15.23/source/crypto/api.c#L217
crypto_alg_list に登録されていればよい
https://elixir.bootlin.com/linux/v5.15.23/source/crypto/api.c#L61
__crypto_register_alg 経由で登録されてそう。
- https://elixir.bootlin.com/linux/v5.15.23/source/crypto/algapi.c#L423
- https://elixir.bootlin.com/linux/v5.15.23/source/crypto/algapi.c#L269
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 である。
- https://elixir.bootlin.com/linux/v5.15.15/source/drivers/md/dm-integrity.c#L418
- https://ipsj.ixsq.nii.ac.jp/ej/?action=repository_uri&item_id=196121&file_id=1&file_no=1
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
// * 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;
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
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 をフラッシュする。 |
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 になる。
$ 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
なるほどね
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$
スーパーブロックの後ろに詰め込んでいるだけっぽい
- https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L1079
- https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L2789
フラグに REQ_SYNC が付いているが同期 I/O という意味ではないという情報もあるのでよく分からない。ということは REQ_FUA のほうか?
- https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L2788
- https://www.akiradeveloper.com/post/device-mapper-io-submit/
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 にはどういうものが書かれているか?
チェックサムの読み書きをするのに使われる ic->bufio インスタンスを生成する際に dm_bufio_client_create() の block_size というパラメータで与えられる。そのサイズを基準として読み書きをするのでチェックサムのディスクレイアウトも block_size の倍数になるようにする必要があるし、読み書きの際に与えるポジションも block_size を基準に与えなければならない。ただしディスクレイアウトに関しては meta_dev != NULL の場合には結局は連続して配置されるのであまり気にしなくてよい。
以下のように初期化されている。31 - SECTOR_SHIFT
は 1 << log2_buffer_sectors << SECTOR_SHIFT
が 2^31 になるようにということだろう。2^32-1 以下の2冪ということだろうか。
ic->log2_buffer_sectors = min((int)__fls(buffer_sectors), 31 - SECTOR_SHIFT);
チェックサムの読み書きに使われる 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
ここで dm_integrity_range が登場している。bitmap_block_work() の使い方を見たところ、データとチェックサムが一致していないかもしれない状態に入ったら remove_range() している。
- https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L2775
- https://elixir.bootlin.com/linux/v5.15.63/source/drivers/md/dm-integrity.c#L2798
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