tcmu でブロックデバイスを作る

tcmu は LIO のバックエンドの一つで、UIO を使って SCSI をコマンドをユーザーランドと通信する。 ドキュメントがかなり詳しく書いているのでわかりやすい。 https://www.kernel.org/doc/Documentation/target/tcmu-design.txt

SCSI コマンドやデータのやり取りは、UIO デバイスmmap したメモリ領域に書き込むことで行う。 このメモリ領域は、メタデータ、コマンドキュー、データ領域にわけられている。 キューにコマンドがエンキューされたことは、UIO デバイスを read または poll することで検知できる。 またコマンドを処理したことをカーネルに伝えるには、UIO デバイスに 4 byte write してやることで実現する。

NBD と違い、SCSI コマンドを解釈する必要があるので実装は面倒くさいが、 カーネルとの通信は mmap した領域に書き込むだけなのでオーバーヘッドが少ないことが予想される。

いつものように rust でメモリバックエンドのデバイスを実装してみた。

github.com

SCSI コマンドは linux がデバイスを動かせる最低限しか実装していない。 それでもかなり面倒くさかったのでデバイスを作る開発者には頭が下がる。

さて、fio でのベンチマークは以下となる。 バージョンやパラメータが以前と異なるが、まあ大きく問題ではないはず。

rw: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
...
fio-3.1
Starting 3 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)
Jobs: 3 (f=24): [m(3)][100.0%][r=166MiB/s,w=167MiB/s][r=42.4k,w=42.7k IOPS][eta 00m:00s]
rw: (groupid=0, jobs=3): err= 0: pid=1031: Sun Oct 15 02:40:33 2017
   read: IOPS=50.0k, BW=195MiB/s (205MB/s)(11.5GiB/60020msec)
    slat (nsec): min=1006, max=8783.3k, avg=11616.59, stdev=53570.35
    clat (usec): min=20, max=300906, avg=1902.37, stdev=8166.04
     lat (usec): min=26, max=300908, avg=1914.10, stdev=8167.55
    clat percentiles (usec):
     |  1.00th=[   347],  5.00th=[   486], 10.00th=[   529], 20.00th=[   594],
     | 30.00th=[   668], 40.00th=[   734], 50.00th=[   938], 60.00th=[  1188],
     | 70.00th=[  1729], 80.00th=[  2245], 90.00th=[  2704], 95.00th=[  3032],
     | 99.00th=[  4359], 99.50th=[ 74974], 99.90th=[149947], 99.95th=[160433],
     | 99.99th=[210764]
   bw (  KiB/s): min= 1680, max=150592, per=33.35%, avg=66720.66, stdev=21592.10, samples=360
   iops        : min=  420, max=37648, avg=16680.15, stdev=5398.03, samples=360
  write: IOPS=49.0k, BW=195MiB/s (205MB/s)(11.4GiB/60020msec)
    slat (nsec): min=1489, max=8732.8k, avg=12720.29, stdev=53613.83
    clat (usec): min=9, max=301225, avg=1910.20, stdev=8189.98
     lat (usec): min=24, max=301228, avg=1923.05, stdev=8191.48
    clat percentiles (usec):
     |  1.00th=[   347],  5.00th=[   486], 10.00th=[   529], 20.00th=[   594],
     | 30.00th=[   676], 40.00th=[   734], 50.00th=[   947], 60.00th=[  1188],
     | 70.00th=[  1729], 80.00th=[  2245], 90.00th=[  2704], 95.00th=[  3064],
     | 99.00th=[  4424], 99.50th=[ 74974], 99.90th=[149947], 99.95th=[160433],
     | 99.99th=[210764]
   bw (  KiB/s): min= 1480, max=150488, per=33.35%, avg=66684.45, stdev=21528.53, samples=360
   iops        : min=  370, max=37622, avg=16671.08, stdev=5382.13, samples=360
  lat (usec)   : 10=0.01%, 50=0.01%, 100=0.06%, 250=0.37%, 500=6.07%
  lat (usec)   : 750=34.44%, 1000=12.39%
  lat (msec)   : 2=21.49%, 4=24.07%, 10=0.30%, 20=0.23%, 50=0.07%
  lat (msec)   : 100=0.33%, 250=0.20%, 500=0.01%
  cpu          : usr=4.07%, sys=16.96%, ctx=1419522, majf=0, minf=43
  IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.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.0%, 32=0.0%, 64=0.1%, >=64=0.0%
     issued rwt: total=3002198,3000568,0, short=0,0,0, dropped=0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
   READ: bw=195MiB/s (205MB/s), 195MiB/s-195MiB/s (205MB/s-205MB/s), io=11.5GiB (12.3GB), run=60020-60020msec
  WRITE: bw=195MiB/s (205MB/s), 195MiB/s-195MiB/s (205MB/s-205MB/s), io=11.4GiB (12.3GB), run=60020-60020msec

Disk stats (read/write):
  sdb: ios=2966618/2964788, merge=26473/26652, ticks=4184010/4205964, in_queue=8400100, util=99.91%

これを見ると NBD を使った実装よりも遅いことがわかる。 perf などで調べてみたが、UIO デバイスへの write、すなわち kernel への command completion の通知が重いらしい。

Samples: 31K of event 'cycles:ppp', Event count (approx.): 29291463791
  Children      Self  Command   Shared Object       Symbol
+   71.40%     0.22%  tcmu-mem  [kernel.vmlinux]    [k] entry_SYSCALL_64_fastpath
+   70.75%     0.22%  tcmu-mem  libpthread-2.26.so  [.] __libc_write
+   70.08%     0.08%  tcmu-mem  [kernel.vmlinux]    [k] sys_write
+   69.78%     0.56%  tcmu-mem  [kernel.vmlinux]    [k] vfs_write
+   69.09%     0.24%  tcmu-mem  [kernel.vmlinux]    [k] __vfs_write
+   68.79%     0.58%  tcmu-mem  [uio]               [k] uio_write
+   67.84%     0.20%  tcmu-mem  [target_core_user]  [k] tcmu_irqcontrol
+   56.89%     2.83%  tcmu-mem  [target_core_user]  [k] tcmu_handle_completions
+   31.39%     0.00%  tcmu-mem  [unknown]           [k] 0x0000000000000001
+   22.22%    21.87%  tcmu-mem  libc-2.26.so        [.] __memmove_avx_unaligned_erms
+   21.27%     0.83%  tcmu-mem  [target_core_mod]   [k] target_complete_cmd
+   20.45%     1.52%  tcmu-mem  [kernel.vmlinux]    [k] preempt_schedule
+   20.28%     1.17%  tcmu-mem  [kernel.vmlinux]    [k] ___preempt_schedule
+   19.63%     1.66%  tcmu-mem  [kernel.vmlinux]    [k] try_to_wake_up
+   19.47%     1.28%  tcmu-mem  [kernel.vmlinux]    [k] __schedule
+   19.05%     0.59%  tcmu-mem  [kernel.vmlinux]    [k] queue_work_on
+   18.86%     0.40%  tcmu-mem  [kernel.vmlinux]    [k] preempt_schedule_common
+   17.40%     1.08%  tcmu-mem  [kernel.vmlinux]    [k] _raw_spin_unlock
+   16.96%     1.89%  tcmu-mem  [kernel.vmlinux]    [k] __queue_work
+   15.17%     0.77%  tcmu-mem  [kernel.vmlinux]    [k] insert_work
+   14.36%     0.12%  tcmu-mem  [kernel.vmlinux]    [k] wake_up_process
+   13.08%     4.32%  tcmu-mem  tcmu-mem            [.] tcmu_mem::main