ユーザランドIOの展望

Rustで仮想ブロックデバイスを作る!userland-ioの紹介

で、ユーザランドで仮想ブロックデバイスを作れるようになることの価値の説明と、 初期実装の報告を行った。

Linux 5.1から入ったio-uringは、カーネルとの間でリングバッファを 共有して、それを通じて効率的なIOを行う仕組みであるが、 これはRust界隈では非常に盛り上がっていて、tokio/io-uringをはじめ、開発が盛んだ。

ちょうどこの前、Tokio 1.0が出たが、

Announcing Tokio 1.0

io_uring is a new Linux interface providing asynchronous operations for all types of I/O, including disk, while reducing the required number of syscalls. Currently, on Linux, Tokio uses the epoll(7) interface, which notoriously does not work for disk access. To work around this, Tokio provides asynchronous filesystem APIs by dispatching synchronous operations to a thread pool. While this works, it is not optimal.

By leveraging io_uring, Tokio will be able to provide genuinely asynchronous filesystem operations. Additionally, early benchmarks of io_uring are very promising and we are hopeful that these performance improvements will carry over to Tokio.

と言った具合に、次に取り組む重点テーマとしても挙げられている非常に熱い領域だ。

このio-uringがあるから、ユーザランドIOは実現すると言っても過言ではなく、 そのためには、十分に効率的なフレームワークが求められる。 RustによるユーザランドIOをどう実現するかが、人類の課題だ。

さらに調査すると、今まで色々な方法でユーザランドIOは試されてきたことがわかった。

  • NBDを使ったものはどれも実装としてはいくつか実験的(実装が糞のマイルドな言い方)なものが存在する。
  • TCMUというLIOとUIOを使ったものが存在する。どういう仕組みなのか詳しくはわからないが、NBDよりも遅いそうだし使い勝手も悪い。
  • dm-userというDMターゲットが提案されているが、これはどうもリジェクトの目が濃い。

もう1つ、名前はどうかと思うが"ABUSE"というのが存在した。

これは

  1. 2009年:パッチが投げられたが一瞬でリジェクト https://lwn.net/Articles/343514/
  2. 2015年:btrfsに貢献してるnaotaさんがmqべースに書き直してカーネルモジュール化 https://github.com/naota/abuse-kmod

という歴史をたどって、その後放置されていたものだが、 ユーザランドIOを実現するためにはもっとも素直な実装がされていて 面白いと思ったので、これを復活&改善して、使ってやろうという気持ちになった。

ところで、ユーザランドIOを実現するに当たって、難しいのは、バッファをどうするかだ。 io-uringはゼロコピー。これはカーネル側からユーザのメモリ空間にアクセスすることは可能だからだ。しかし逆は出来ない。 (ユーザが確保したページに対してカーネルから直接アクセスしてるのだと理解している。 理解が誤っていたら教えてください)

従って、コピーしたりすることが必要となる。 事実、ABUSEの初期のコードにも、ユーザランドからリードデータを書き戻すためにbvecに書き戻すようなコードがある。

+		if (read)
+			ret = copy_from_user(kaddr + bvec->bv_offset, 
+				(void *)ab->ab_xfer[i].ab_address,
+				bvec->bv_len);

当然、NBDを使った場合でも、TCPのパケットを経由する必要があり、 どこかしらコピーは避けられない。

しかし、ABUSEを使うならば、ネットワークは介在しないし、 bvecのpageをユーザランドからmmapすれば、コピーを避けること「だけ」は出来る。 mmap自体にオーバーヘッドがある上に内部的にsemaphoreをとっていて並列化が出来ないところなので、 もはやコピーした方がましということにもなりかねないが、 とりあえずこの線でやってみた。 ちなみに、bvecというのは、IOに使う領域であり、 リードであればその領域に書く必要があるし、ライトであれば その領域のデータを使う必要がある。 このbvecが指すページの物理アドレスをユーザランドが知ることが出来れば、 そのページをユーザのアドレス空間にmmapすることが出来るだろというのだ。

ramdiskを実装して、badblocksで検査OK出来るところまでは行ったが、 ddでリードを測定してみると、 IOサイズが小さい場合ではNBD版よりも4倍くらいは速いが、 IOサイズが大きくなるとほぼ変わらなくなってしまうことがわかった。 (IOサイズ1MBで1GB/sくらい) これは、カーネルから伝わってくるbvecがほとんど(あるいはすべてか) 4KB粒度なので、mmapの回数がとても多くなって、これによって コピーと差がなくなっていったんだろうと考察している。

従って、次の仕事はmmapのオーバーヘッドを減らすことか、 あるいはそもそもmmapしなくていい仕組みを作ることなのだが、 オーバーヘッドを減らすという目的でRust側から並列実行してみたが、 これはいい結果が得られなかった。

というわけで、性能としてはNBDを使うよりは良いけど、 死ぬほどいいわけではないというレベルで少し残念なのだけど、 io-uringのようにメッセージのやりとりをpollではなくリングバッファにするという最適化は可能ではあるし、 もう少しは改善出来るはずだ。

今後どうするかだけど、 ABUSE(改)を使った実装で一本化していこうと思う。

さきほど、ユーザランドIOを実現するならば、ABUSEのように ブロック層から直接ユーザランドとやりとりするのが一番素直だと言ったが、 これは、このレイヤーで実装するのが性能が一番出るとかいうこともあるが、 リクエストのフラグをそのままユーザランドに晒せるという利点もある。 例えばNBDでは、リクエストのフラグはNBD独自のものに置き換わっており、 これ自体が複雑で、とても仮想ブロックデバイスを作るために適した解像度ではないが、 Linuxカーネル内部で使われているリクエストのフラグをそのままユーザランドに渡せば、 ユーザランドに「このフラグが伝わってこない」と言ったような不利が起こらなくなる。 この方が、フレームワークの設計としてはより適切なはずだ。

応援してね。 https://github.com/akiradeveloper/userland-io


このエントリーをはてなブックマークに追加

See also