Linux の 6.0 では Userspace block device driver(ublk driver)という機能が追加された。
名前の通り、ユーザーランドでブロックデバイスを実装するためのドライバになっている。
ドキュメントは以下にある。 https://github.com/torvalds/linux/blob/v6.0/Documentation/block/ublk.rst
このドキュメントによると、既存の仮想ブロックドライバをユーザーベースに移行したいというモチベーションがあるらしい。
利点としては以下が挙げられている。
- カーネルで使えない言語やライブラリを利用できる
- アプリケーション開発者がなれているデバッグツールが使える
- クラッシュしてもカーネルパニックしない
- カーネルに依存せず更新できる
- バグがあってもカーネルよりもセキュリティリスクが少ない
- テストやデバッグでパラメータを変えて実験できる
ユーザースペースプログラムのことを、ublk serverと呼称している。
実装には、blk-mq
リクエストベースドライバを利用していて、各 IO に tag が振られる。
ublk serverも同様に tag を割り当てて、それが一対一にマッピングされるらしい。
リクエストのやり取りには io_uring
を利用していて、ドライバとのやり取りで使うだけでなく、
ublk server内の IO にも使うことが推奨されている。
ここに実装の一例がある。 https://github.com/ming1/ubdsrv
楽に実装するためのライブラリもある。 https://github.com/ming1/ubdsrv/tree/master/lib
このドライバをコントロールするためのグローバルなコントロール用のデバイスとして、/dev/ublk-control
がある。
上述の実装を見ると、このデバイスとのやり取りも io_uring
を使うらしい。
新しいデバイスを追加するためには、ublk serverが新しいデバイスとの IO をやり取りするためのデバイスを用意する必要がある。そのためのコマンドをコントロールデバイスに発行すると、/dev/ublkc*
が作られる。
その後、ブロックデバイスの追加コマンドを発行すると/dev/ublkb*
にブロックデバイスが作られる。
ublk serverはキューごとにスレッドを作成し、io_uring
をハンドルする必要がある。
server がUBLK_IO_FETCH_REQ
を発行すると、ドライバがIOリクエストの転送を始める。
IOはublksrv_io_desc
にエンコードされており、serverが処理を終えたら結果をUBLK_IO_COMMIT_AND_FETCH_REQ
コマンドを使って返す。このコマンドで次のリクエスト処理も開始される。
将来の開発計画として、コンテナから ublk を使えるようにしたり、zero copy できるようにしたいそうだ。
それでは実際に使ってみる。実行環境は 2022/11/23 時点の最新の arch linux。
デフォルトではキューの数が1だったので、ublk add
の-q
オプションでキューの数を32個に増やしている
sudo pacman -S autoconf-archive git clone https://github.com/ming1/ubdsrv.git cd ubdsrv autoreconf -i ./configure make sudo modprobe brd rd_size=4194304 sudo modprobe ublk_drv sudo ./ublk add -t loop -q 32 -f /dev/ram0 sudo mkfs.ext4 /dev/ublkb0 mkdir -p t sudo mount /dev/ublkb0 t cd t sudo fio --name=rw --size=250M --rw=randrw --nrfiles=8 --ioengine=libaio --iodepth=16 --direct=1 --overwrite=1 --runtime=60 --time_based --numjobs=4 --group_reporting
rw: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=16 ... fio-3.33 Starting 4 processes rw: Laying out IO files (8 files / total 250MiB) rw: Laying out IO files (8 files / total 250MiB) rw: Laying out IO files (8 files / total 250MiB) rw: Laying out IO files (8 files / total 250MiB) Jobs: 4 (f=32): [m(4)][100.0%][r=703MiB/s,w=703MiB/s][r=180k,w=180k IOPS][eta 00m:00s] rw: (groupid=0, jobs=4): err= 0: pid=32492: Wed Nov 23 15:35:29 2022 read: IOPS=180k, BW=704MiB/s (738MB/s)(41.3GiB/60001msec) slat (nsec): min=1150, max=116402, avg=8394.65, stdev=4924.49 clat (nsec): min=940, max=766996, avg=167269.66, stdev=49871.85 lat (usec): min=16, max=775, avg=175.66, stdev=52.93 clat percentiles (usec): | 1.00th=[ 67], 5.00th=[ 77], 10.00th=[ 86], 20.00th=[ 143], | 30.00th=[ 159], 40.00th=[ 167], 50.00th=[ 174], 60.00th=[ 178], | 70.00th=[ 184], 80.00th=[ 192], 90.00th=[ 206], 95.00th=[ 253], | 99.00th=[ 322], 99.50th=[ 343], 99.90th=[ 388], 99.95th=[ 420], | 99.99th=[ 498] bw ( KiB/s): min=599648, max=1156024, per=100.00%, avg=721015.53, stdev=21708.24, samples=476 iops : min=149912, max=289006, avg=180253.88, stdev=5427.06, samples=476 write: IOPS=180k, BW=703MiB/s (738MB/s)(41.2GiB/60001msec); 0 zone resets slat (nsec): min=1200, max=176111, avg=8791.47, stdev=5048.88 clat (nsec): min=910, max=783474, avg=168873.39, stdev=50225.29 lat (usec): min=18, max=799, avg=177.66, stdev=53.40 clat percentiles (usec): | 1.00th=[ 68], 5.00th=[ 77], 10.00th=[ 87], 20.00th=[ 145], | 30.00th=[ 161], 40.00th=[ 169], 50.00th=[ 176], 60.00th=[ 180], | 70.00th=[ 186], 80.00th=[ 192], 90.00th=[ 208], 95.00th=[ 255], | 99.00th=[ 326], 99.50th=[ 343], 99.90th=[ 392], 99.95th=[ 424], | 99.99th=[ 502] bw ( KiB/s): min=599456, max=1154560, per=100.00%, avg=720465.01, stdev=21605.03, samples=476 iops : min=149864, max=288640, avg=180116.25, stdev=5401.26, samples=476 lat (nsec) : 1000=0.01% lat (usec) : 2=0.01%, 4=0.01%, 10=0.01%, 20=0.01%, 50=0.23% lat (usec) : 100=13.17%, 250=81.43%, 500=5.16%, 750=0.01%, 1000=0.01% cpu : usr=8.49%, sys=31.36%, ctx=29876275, majf=0, minf=52 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=100.0%, 32=0.0%, >=64=0.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.1%, 32=0.0%, 64=0.0%, >=64=0.0% issued rwts: total=10813820,10805346,0,0 short=0,0,0,0 dropped=0,0,0,0 latency : target=0, window=0, percentile=100.00%, depth=16 Run status group 0 (all jobs): READ: bw=704MiB/s (738MB/s), 704MiB/s-704MiB/s (738MB/s-738MB/s), io=41.3GiB (44.3GB), run=60001-60001msec WRITE: bw=703MiB/s (738MB/s), 703MiB/s-703MiB/s (738MB/s-738MB/s), io=41.2GiB (44.3GB), run=60001-60001msec Disk stats (read/write): ublkb0: ios=10807636/10799329, merge=0/42, ticks=255538/258038, in_queue=513576, util=99.89%
以前作ったNBDベースのメモリーバックエンドのユーザーランドブロックデバイスで同様にベンチマークを取ると以下のようになった。 こちらもublkと同様にサーバは32スレッドで動かしている。
rw: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=16 ... fio-3.33 Starting 4 processes rw: Laying out IO files (8 files / total 250MiB) rw: Laying out IO files (8 files / total 250MiB) rw: Laying out IO files (8 files / total 250MiB) rw: Laying out IO files (8 files / total 250MiB) Jobs: 4 (f=32): [m(4)][100.0%][r=655MiB/s,w=653MiB/s][r=168k,w=167k IOPS][eta 00m:00s] rw: (groupid=0, jobs=4): err= 0: pid=49755: Wed Nov 23 16:16:34 2022 read: IOPS=166k, BW=648MiB/s (680MB/s)(38.0GiB/60001msec) slat (nsec): min=1730, max=664245, avg=10800.29, stdev=5755.01 clat (nsec): min=1920, max=1455.3k, avg=180044.58, stdev=27830.83 lat (usec): min=19, max=1472, avg=190.84, stdev=28.91 clat percentiles (usec): | 1.00th=[ 120], 5.00th=[ 137], 10.00th=[ 147], 20.00th=[ 159], | 30.00th=[ 165], 40.00th=[ 174], 50.00th=[ 180], 60.00th=[ 186], | 70.00th=[ 194], 80.00th=[ 202], 90.00th=[ 215], 95.00th=[ 227], | 99.00th=[ 255], 99.50th=[ 269], 99.90th=[ 306], 99.95th=[ 326], | 99.99th=[ 379] bw ( KiB/s): min=625760, max=714384, per=100.00%, avg=664019.76, stdev=4227.49, samples=476 iops : min=156440, max=178596, avg=166004.92, stdev=1056.89, samples=476 write: IOPS=166k, BW=648MiB/s (679MB/s)(37.9GiB/60001msec); 0 zone resets slat (nsec): min=1660, max=532044, avg=11640.12, stdev=6046.44 clat (usec): min=12, max=1441, avg=182.55, stdev=27.93 lat (usec): min=29, max=1460, avg=194.19, stdev=29.04 clat percentiles (usec): | 1.00th=[ 124], 5.00th=[ 141], 10.00th=[ 149], 20.00th=[ 161], | 30.00th=[ 169], 40.00th=[ 176], 50.00th=[ 182], 60.00th=[ 188], | 70.00th=[ 196], 80.00th=[ 204], 90.00th=[ 217], 95.00th=[ 231], | 99.00th=[ 260], 99.50th=[ 273], 99.90th=[ 310], 99.95th=[ 334], | 99.99th=[ 392] bw ( KiB/s): min=626976, max=717352, per=100.00%, avg=663490.35, stdev=4212.33, samples=476 iops : min=156744, max=179338, avg=165872.59, stdev=1053.08, samples=476 lat (usec) : 2=0.01%, 10=0.01%, 20=0.01%, 50=0.01%, 100=0.05% lat (usec) : 250=98.53%, 500=1.42%, 750=0.01%, 1000=0.01% lat (msec) : 2=0.01% cpu : usr=5.53%, sys=34.42%, ctx=23019937, majf=0, minf=44 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=100.0%, 32=0.0%, >=64=0.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.1%, 32=0.0%, 64=0.0%, >=64=0.0% issued rwts: total=9955048,9947240,0,0 short=0,0,0,0 dropped=0,0,0,0 latency : target=0, window=0, percentile=100.00%, depth=16 Run status group 0 (all jobs): READ: bw=648MiB/s (680MB/s), 648MiB/s-648MiB/s (680MB/s-680MB/s), io=38.0GiB (40.8GB), run=60001-60001msec WRITE: bw=648MiB/s (679MB/s), 648MiB/s-648MiB/s (679MB/s-679MB/s), io=37.9GiB (40.7GB), run=60001-60001msec Disk stats (read/write): nbd0: ios=9935485/9927606, merge=0/42, ticks=239988/241003, in_queue=480991, util=99.89% sudo fio --name=rw --size=250M --rw=randrw --nrfiles=8 --ioengine=libaio 13.68s user 83.45s system 157% cpu 1:01.61 total
ublkは700MiB/s程度出ているが、nbdでは650MiB/s程度なのでublkのほうが少し速い。 またnbdではサーバ内で確保したメモリーに書くだけなので、ublkも同様にすればより速くなるかもしれない。
なお実験は、CPUがAMD Ryzen 9 3950X 16Coreでメモリーは32GiB(DDR4-2133)の環境を使った。