LeavaTailの日記

LeavaTailの日記

Linuxエンジニアを目指した技術者の備忘録

Raspberry Pi 4 Model B (64-bit版 Raspberry Pi OS) で dm-integrity のオーバーヘッドを計測する

変更履歴

  • 2021/5/9: 記事公開

はじめに

Raspberry Pi 4 Model B (Raspberry Pi 4) はarm64のシングルボードコンピュータであり、趣味から仕事まで幅広い用途として利用されている。
Raspberry Pi の 推奨OSの一つに DebianベースOSのRaspberry Pi OSがある。 2021年4月30日現在では、安定性や互換性の観点からRaspberry Pi OSのデフォルトが32-bit版となっている。 しかし、64-bit版のRaspberry Pi OSも用意されているので、設定を変更することで利用できる。

一方で、dm-integrityはLinuxカーネルにおける標準提供の機能であり、ブロックの整合性を保障することができる。 dm-integrity では、 Device mapperと呼ばれるブロックデバイスの仮想レイヤを作成する機能を用いて、ジャーナルログを生成する。
ブロックの整合性を保証できる一方で、性能の観点でもストレージの容量の観点でもオーバーヘッドが発生する。

目的

  • dm-integrityを導入したことで読み書き性能がどれだけ変化するかを調査する
  • 改ざん検知機能 (dm-verity と dm-integrity+dm-crypt) の間で読み込み性能の比較をする

dm-integrity とは

dm-integrityは、Linuxカーネル v4.12以降で利用することができるLinux標準提供の機能である。 dm-integrityのデータ構造は下記の通りとなっている。

f:id:LeavaTail:20210430182914p:plain
dm-integrityのディスクレイアウト

Device-mapperの機構を利用して、データと整合性タグをまとめてジャーナルにコミットしてから、データと整合性タグを適切な位置に書き込むことになっている。
ext4といったジャーナリングファイルシステムはファイルの整合性を保障する一方で、dm-integrityではブロック単位の整合性保証する。
そのため、ビットエラーや書き込み中の電源断による整合性保証のために利用される。

また、dm-integrityはdm-cryptと共に利用されることが多い。
dm-cryptは、Linuxカーネルにおける標準提供の機能であり、ブロック単位にを暗号/複合することができる。 二つの機構を組み合わせHMAC-sha256を使うことで、不正なデータの更新(改ざん)を検出することができる。

なぜ64-bit版 のOSで試すのか

2021年4月30日現在、Raspberry Pi OSはDebian 10 (buster) がベースとなっており、cryptsetup 2.1.0がパッケージされている。 一方で、armhf と cryptsetup 2.1.0は安定していない (ように見える)。

gitlab.com

gitlab.com

そこで下記の観点から、64-bit版のRaspberry Pi OSを使用することにした。

  • カーネルコンフィグを修正するために再ビルドが必要であること
  • arm64の読み書き性能を正しく計測するために、OSも64-bit版が適していること
  • 32-bit版OSの既知のバグを踏まないため

実行環境

計測対象のRaspberry Pi 4 Model Bの詳細は下記のとおりである。

名前 詳細
Hardware Raspberry Pi 4 Model B Rev 1.2 (4GB)
OS Raspberry Pi OS arm64 2021-04-09
Kernel Linux version 5.10.3-v8
Kconfig 付録参照
SDカード HDMCSDH16GCL10UIJP-WOA
USBメモリ SILICON POWER SP032GBUF3B02V1K
IPアドレス (eth0) 172.16.1.3
ユーザ名 pi
ホスト名 raspberry
dosfstools 4.1
cryptsetup v2.1.0
integrity v1.6.0
verity v1.7.0
crypt v1.22.0
fio v3.12

カーネルのビルドや環境のセットアップについては、前回の記事を参照。

leavatail.hatenablog.com

本記事では、SDカードにブートに必要なイメージを格納し、USBメモリに作成したext4ファイルシステムで計測する。

f:id:LeavaTail:20201231133644p:plain
本記事におけるデバイスとその用途の関係図

またUSBメモリ(/dev/sda)はMBRパーティションを作成し、8GB(/dev/sda1)と20GB(/dev/sda2)の二つから構成される。

測定方法

今回は以下の4つのパターンにおける読み書き性能を測定した。 (ただしdm-verityはRead-Onlyであるため、③は読み込み性能のみとする)

f:id:LeavaTail:20210507104309p:plain
測定環境

  1. Device mapperを使用しない場合 (実デバイスドライバファイルシステムを構築する)
  2. dm-integrityのみ使用する場合
  3. dm-verityを使用する場合
  4. dm-integrityとdm-cryprtの両方を使用する場合

各測定パターンの構築方法については付録参照。

また、読み書きの測定計測にはfioを利用し、write, randwrite, read, randreadの4つのエンジンを用いる。

各測定は10回計測し、外れ値を除外したスループットの平均値を性能として評価する。
ここで外れ値は、スループットが平均値から100%以上のずれがあるものや、レイテンシの値が異常値(0や極端に値が振れ幅が大きい)であるものとした。

write

writeとrandwriteについては、4KBのバッファI/Oで計測を実施した。*1

read

writeとrandwriteについては、4KBのバッファI/Oで計測を実施した。

性能計測

f:id:LeavaTail:20210509142718p:plain
write測定結果

上記のグラフは左から、「Device mapperを使用しない場合 」「dm-integrityのみ使用する場合」「dm-verityを使用する場合」「dm-integrityとdm-cryprtの両方を使用する場合」におけるwrite, readwriteのスループットを示している。
dm-verityはRead-Onlyパーティションを対象としているため、write,randwriteのスループットを0と表記している。

normal integrity オーバーヘッド verity crypt+integrity オーバーヘッド
write性能 15.7MB/s 8.9MB/s -43.3% 0 5.9MB/s N/A
randwrite性能 14.5MB/s 6.9MB/s -52.5% 0 4.7MB/s N/A

上記の測定結果より、dm-integrityによる書き込みスループットは50%程度減少することが分かった。
これは、dm-integrity用のDevice Mapper層が追加されたことによる命令数の増加や、ジャーナルの書き込みによりI/O数が増加したことが要因として挙げられる。*2

また、dm-cryptとdm-integrityを組み合わた場合、スループットは65%程度減少することが分かった。
そのため、セキュアブートのためにHMACの利用を考えている方は、これらの性能低下を意識してほしい。

f:id:LeavaTail:20210509142331p:plain
read測定結果

上記のグラフは左から、「Device mapperを使用しない場合 」「dm-integrityのみ使用する場合」「dm-verityを使用する場合」「dm-integrityとdm-cryprtの両方を使用する場合」におけるread, readreadのスループットを示している。

normal integrity オーバーヘッド verity crypt+integrity オーバーヘッド
read性能 88.2MB/s 73.9MB/s -12.2% 39.3MB/s 18.4MB/s -53.2%
randread性能 7.2MB/s 6.0MB/s -17.7% 5.9MB/s 5.0MB/s -15.3%

上記の測定結果より、dm-integrityによる読み込みスループットは15%程度減少することが分かった。
これは、dm-integritによる命令数の増加やI/O数が、LInuxのページング機構によって緩和されたため、著しいスループットの減少が見られなかったと考えられる。

一方で、dm-cryptとdm-integrityを組み合わた場合、dm-verityと比較してスループットが大きく減少する(シーケンシャルリードで50%、ランダムリードで15%)ことが分かった。
特にシーケンシャルリード(read性能)に関しては、著しい減少が見られた。
これは、読み込むべきデータが増加したことにより、ページイン・ページアウトの回数が増加したからではないかと考えられる。*3

おわりに

本記事ではRaspberry Pi 4上でdm-integrityのオーバーヘッドを測定した。

  • dm-integrityを導入したことでwrite性能の著しい減少が観測された
  • 改ざん検知機能の間でシーケンシャルリード性能の著しい減少が観測された

参考

付録

カーネルコンフィグ

bcm2711_defconfigから更新した箇所は下記のとおりである。

617a618                           
> # CONFIG_CRYPTO_CRCT10DIF_ARM64_CE is not set
735c736,737            
< # CONFIG_BLK_DEV_INTEGRITY is not set
---                             
> CONFIG_BLK_DEV_INTEGRITY=y
> CONFIG_BLK_DEV_INTEGRITY_T10=y
2199c2201,2203
< # CONFIG_DM_VERITY is not set
---
> CONFIG_DM_VERITY=m
> # CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG is not set
> # CONFIG_DM_VERITY_FEC is not set
2202c2206
< # CONFIG_DM_INTEGRITY is not set
---
> CONFIG_DM_INTEGRITY=m
7414c7418
< CONFIG_CRYPTO_GF128MUL=m
---
> CONFIG_CRYPTO_GF128MUL=y
7448,7449c7452,7453
< # CONFIG_CRYPTO_CFB is not set
< CONFIG_CRYPTO_CTR=m
---
> CONFIG_CRYPTO_CFB=y
> CONFIG_CRYPTO_CTR=y
7452,7454c7456,7458
< # CONFIG_CRYPTO_LRW is not set
< # CONFIG_CRYPTO_OFB is not set
< # CONFIG_CRYPTO_PCBC is not set
---
> CONFIG_CRYPTO_LRW=y
> CONFIG_CRYPTO_OFB=y
> CONFIG_CRYPTO_PCBC=y
7476c7480
< # CONFIG_CRYPTO_CRCT10DIF is not set
---
> CONFIG_CRYPTO_CRCT10DIF=y
7482,7485c7486,7489
< # CONFIG_CRYPTO_RMD128 is not set
< # CONFIG_CRYPTO_RMD160 is not set
< # CONFIG_CRYPTO_RMD256 is not set
< # CONFIG_CRYPTO_RMD320 is not set
---
> CONFIG_CRYPTO_RMD128=y
> CONFIG_CRYPTO_RMD160=y
> CONFIG_CRYPTO_RMD256=y
> CONFIG_CRYPTO_RMD320=y
7487c7491
< CONFIG_CRYPTO_SHA256=m
---
> CONFIG_CRYPTO_SHA256=y
7506c7510
< # CONFIG_CRYPTO_CAST6 is not set
---
> CONFIG_CRYPTO_CAST6=m
7610c7614
< # CONFIG_CRC_T10DIF is not set
---
> CONFIG_CRC_T10DIF=y

環境構築

Device mapperを使用しない場合
  1. デバイスドライバにFATファイルシステムを作成する

     pi@raspberrypi:~ $ sudo mkfs.fat /dev/sda1 
     mkfs.fat 4.1 (2017-01-24)
    
  2. 作成したファイルシステムを/mntにマウントする

     pi@raspberrypi:~ $ sudo mount -t vfat /dev/sda1 /mnt/
    
dm-verityを使用する場合

dm-verityでは、改ざんチェックの対象となるブロックデバイス(データデバイス)とチェックのためのブロックデバイス(ハッシュデバイス)の2つを用意する必要がある。

そこで、測定対象デバイス(USBメモリ) 上でパーティション作成した。
このとき、データデバイス/dev/sda1を16GB、ハッシュデバイス/dev/sda2を残りの領域に割り当てた。

下記の手順にて測定環境を構築した。

  1. USBメモリ上にext4ファイルシステムを作成し、テスト用にデータを埋めておく

     pi@raspberrypi:~ $ sudo mkfs.fat /dev/sda1
     mkfs.fat 4.1 (2017-01-24)
     pi@raspberrypi:~ $ mount /dev/sda1 /mnt
     pi@raspberrypi:~ $ sudo dd if=/dev/urandom of=/mnt/file bs=1M count=4K
    
  2. ハッシュデバイスの構築準備

     pi@raspberrypi:~ $  sudo veritysetup format /dev/sda1 /dev/sda2 
     VERITY header information for /dev/sda2
     UUID:                   68bbac35-32bc-455f-a1f9-ae2a62287847
     Hash type:              1
     Data blocks:            4194304
     Data block size:        4096
     Hash block size:        4096
     Hash algorithm:         sha256
     Salt:                           759b9ed6980255b4469c6732b05519fe09890938b8608ef7f37fa462c9e79a41
     Root hash:              c2ba950245bc4ed2b37de9900f19050467db3c051cd4801a10713ca193a5e1a9
    
  3. device-mapper (/dev/mapper/test)を作成

     pi@raspberrypi:~ $ sudo veritysetup create test /dev/sda1 /dev/sda2 c2ba950245bc4ed2b37de9900f19050467db3c051cd4801a10713ca193a5e1a9
     pi@raspberrypi:~ $ ls -la /dev/mapper/test
     lrwxrwxrwx 1 root root 7 12月 30 04:12 /dev/mapper/test -> ../dm-0
    
  4. 作成したファイルシステムを/mntにマウントする

     pi@raspberrypi:~ $ sudo mount -t vfat -o ro /dev/mapper/dmtest /mnt/
    
dm-integrityのみ使用する場合
  1. integrity target用のMapping tableを作成する

     pi@raspberrypi:~ $ sudo integritysetup format /dev/sda1
    
     Formatted with tag size 4, internal integrity crc32c.
     Wiping device to initialize integrity checksum.
     You can interrupt this by pressing CTRL+c (rest of not wiped device will     contain     invalid checksum).
     Finished, time 18:26.389, 8064 MiB written, speed   7.3 MiB/s   
    
  2. device-mapper (/dev/mapper/test)を作成する

     pi@raspberrypi:~ $ sudo integritysetup open /dev/sda1 test
    
  3. 作成したintegrity targetを確認する

     pi@raspberrypi:~ $ sudo integritysetup status test
     /dev/mapper/test is active.
       type:    INTEGRITY
       tag size: 4
       integrity: crc32c
       device:  /dev/sda1
       sector size:  512 bytes
       interleave sectors: 32768
       size:    16516984 sectors
       mode:    read/write
       failures: 0
       journal size: 67043328 bytes
       journal watermark: 50%
       journal commit time: 10000 ms
    
  4. device mapper tableを表示する (値の意味についてはcryptsetupを参照)

     pi@raspberrypi:~ $ sudo dmsetup table
     test: 0 16516984 integrity 8:1 0 4 J 6 journal_sectors:130944 interleave_sectors:32768 buffer_sectors:128 journal_watermark:50 commit_time:10000 internal_hash:crc32c
    
  5. USBメモリ上にext4ファイルシステムを作成し、テスト用のファイルfileを準備する

     pi@raspberrypi:~ $ sudo mkfs.fat /dev/mapper/test
     mkfs.fat 4.1 (2017-01-24)
    
  6. /mnt以下に作成したファイルシステムをマウントする

     pi@raspberrypi:~ $ sudo mount -t vfat /dev/mapper/test /mnt/
    
dm-integrityとdm-cryprtの両方を使用する場合
  1. crypt target + integrity target用のMapping tableを作成する

     pi@raspberrypi:~ $ sudo cryptsetup luksFormat --type luks2 /dev/sda1 --cipher aes-xts-plain64 --integrity hmac-sha256
    Enter passphrase for /dev/sda1: 
    Verify passphrase: 
    Wiping device to initialize integrity checksum.
    You can interrupt this by pressing CTRL+c (rest of not wiped device will contain invalid checksum).
    Finished, time 33:38.376, 7634 MiB written, speed   3.8 MiB/s
    
  2. 作成したDevice mapperのインスタンスを有効化する

     pi@raspberrypi:~ $ sudo cryptsetup open /dev/sda test
     Enter passphrase for /dev/sda: 
    
  3. 作成したDevice mapperを確認する

     pi@raspberrypi:~ $ sudo cryptsetup status test
     /dev/mapper/test is active.
       type:    LUKS2
       cipher:  aes-xts-plain64
       keysize: 768 bits
       key location: keyring
       integrity: hmac(sha256)
       integrity keysize: 256 bits
       device:  /dev/sda1
       sector size:  512
       offset:  0 sectors
       size:    15634464 sectors
       mode:    read/write
    
  4. USBメモリ上にFATファイルシステムを作成する

     pi@raspberrypi:~ $ sudo mkfs.fat /dev/mapper/test
     mkfs.fat 4.1 (2017-01-24)
    
  5. /mnt以下に作成したファイルシステムをマウントする

     pi@raspberrypi:~ $ sudo mount -t vfat /dev/mapper/test /mnt/
    

測定スクリプト

#!/bin/bash -eu

MNTPOINT=/mnt/
TARGET=FILE.TXT
LOOP=10

function usage() {
        echo "Usage: $(basename $0) {0 | 1}"
        echo "    0: measure for write, randwrite"
        echo "    1: measure for read,  randread"
}

function do_fio() {
        if [ $# != 2 ]; then
                echo "Internal Error: function needs 2 argument."
                exit 1
        fi

        echo 3 > /proc/sys/vm/drop_caches
        fio --filename=${MNTPOINT}${TARGET} --direct=$2 --rw=$1 --bs=4K --ioengine=libaio --runtime=60 --time_based --name=$2 --size=1G --output-format=json >> result_$1.log
}

##
## MAIN FUNCTION
##
array_0=("write" "randwrite")
array_1=("read" "randread")

if [ $# != 1 ]; then
        usage
        exit 1
fi

if [ $1 = '0' ]; then
        for pattern in ${array_0[@]}
        do
                echo "Start ${pattern}"
                touch result_${pattern}.log
                for i in `seq ${LOOP}`
                do
                        do_fio ${pattern} 0
                        rm ${MNTPOINT}${TARGET}
                done
        done
elif [ $1 = '1' ]; then
        for pattern in ${array_1[@]}
        do
                echo "Start ${pattern}"
                touch result_${pattern}.log
                for i in `seq ${LOOP}`
                do
                        do_fio ${pattern} 1
                done
        done
else
        usage
        exit 1
fi

*1:direct=1で計測したかったが、randwrite性能が極端に低速であったため断念した

*2:より詳細に調査するのであれば、blktraceなどで確認したほうが良い

*3:より詳細に調査するのであれば、topなどでメモリ使用量やページイン・ページアウトを確認したほうが良い