仮想マシンで重要なメモリのアイソレーション

yasukata氏(以下、yasukata):yasukataといいます。「仮想マシンのためのVMFUNC命令を使ったメモリ共有手法」についてお話しします。

(スライドを示して)こちらは、2023年3月に「ASPLOS」という学会で発表したものです。内容は、「Exit-Less, Isolated, and Shared Access」、ELISAという仮想マシンのためのメモリ共有手法についてです。こちらのリンクに、論文、スライド、ソースコード、解説などがあるので、よければご参照ください。

まず、仮想マシンについての話です。メモリ、CPU、I/Oデバイス、リソースがあって、こちらが、仮想マシンですね。

仮想マシンがすごくたくさん使われている理由はこちらですね。物理リソースをバシッとパーティションして、任意の仮想マシンに好きなだけアサインできるというのがすばらしいところです。

(スライドの)真ん中のやつは、物理メモリのアドレススペースとして見てもらいたいのですが、メモリをみんなに分けて使える。特にこれがすばらしい。その中で、メモリのアイソレーション、分離というプロパティがすごく重要です。

どういうことかというと、例えば、青い仮想マシンが黄色や赤の仮想マシンにアサインされてメモリに触られると困るので、それができないようになっているというのが基本的な構成です。

複数の仮想マシンで同一のメモリを触りたい場合の問題点

一方で、たまにいくつかの仮想マシンで同じメモリを触りたい場面があります。例えば(スライドを示して)こういうDMAのような、メモリを通してI/Oデバイスを操作する時には、同じメモリ領域の仮想マシンに触れる必要があります。

では、具体的にこれをどうやって実装するか。考えられる方法の1つとしては、DMA領域を直接仮想マシンに見せてやるというのがあると思いますが、この中に悪いやつがいたら、ここを通して、ほかのマシンに嫌なことができる可能性があります。

方法2のほうがよく採用されていると思っていて、これはホストが仲介します。仮想マシンがホストに対してリクエストを送って、そのホストが仮想マシンの代わりに共有メモリにアクセスしていくというかたちです。

ですが、ご存じのとおり、こちらは各リクエストごとに仮想マシンのコンテキストがexitを発生させてしまうことがあります。なぜかというと、リクエストがホストでハンドルされる必要があるからです。このexitというのは、CPUにとって負荷の高い処理なので、パフォーマンスにネガティブな影響を及ぼすという点があります。

まとめると、(スライドを示して)こういう問題があります。この2つの方法には、それぞれ良くないポイントがあって、直接見せるパターンだと、速いけれどアイソレーションがない。ホストを仲介するパターンだと、アイソレーションはあるけどちょっと負荷が高い。

なので今回は、この中間を取りたい。アイソレーションができて、オーバーヘッドも低いやつが欲しいよねというのが、今回のお話です。

仮想マシンのためのメモリ共有手法「ELISA」

今回の提案手法のELISAでは、Extended Page Table、仮想マシンのためのページテーブルを分割することで共有オブジェクトをアイソレートして、かつ、仮想マシンはEPT Pointer switchingという、VMFUNCというCPU命令の機能を使って共有オブジェクトにアクセスするようにします。VMFUNCはVMEXITを起こさないので、結果として速く、ELISAではアイソレーションを低いコストで提供できます。

もう少し詳しく説明します。まず、「EPT、何ぞや?」ということなんですけれども、EPTはページテーブルで、ホストがコンフィグして各仮想マシンにアサインするというかたちです。VMは、EPTに入っている、書かれている物理メモリ領域にしかアクセスできないというのがポイントです。

Intel CPUの機能なんですけど、Intel CPUだと、ホストは複数のEPTを1つの仮想マシンにアサインすることができます。ここでEPTP switchingとVMFUNCが出てくるのですが、VMFUNCを使うと、仮想マシンは今アクティブなEPTから別のEPTに切り替えることができます。

例えばこうやってVMFUNCと仮想マシンが実行すると、アクティブなEPTのマッピングが変わって、見えるメモリ領域が変わります。

(スライドを示して)ポイントはここですね。ページテーブルが別になっているので、この2つのメモリ領域はアイソレートされています。普通のユーザー空間プロセスの間がアイソレートされているのと同じ原理になります。

もう1点、VMFUNCは先ほど申し上げたとおり、exitが発生しないので、コストが低くなっていて、VMEXITを発生させるVMCALLという命令と比べて5.3倍速かった感じでした。

「ELISA」の挙動を説明

これを使っていくのが提案手法です。まず、脅威モデルとして、VM自体は基本的に全部を信用していない一方で、この共有オブジェクトのあるところの共有オブジェクト自体と、その中で動くプログラムはtrustedとしています。

どうやってオブジェクトにアクセスするかというと、こんな感じですね。仮想マシンはVMFUNCを実行して、コンテキストを切り替えて、共有オブジェクトのあるコンテキストに入れます。ほかの仮想マシンも同じようにできるので、こうやってデバイスを共有したりする時に使えます。ここは同じものに触るので、ここではスピンロックなどを実装して排他制御などができます。

仕事が終わったらまたVMFUNCを呼んで、元のコンテキストに戻るという挙動になります。

ポイントのおさらいですが、このtrustedなコンテキストは、untrustedなコンテキストからページテーブルの分割によってアイソレートされていて、かつ、VMはVMFUNCを使ってコンテキストの切り替えをしているので、それで速くできる、軽量にできるというのがポイントです。

これを作る上でけっこういろいろ考えることがあって、それについてのソリューションや、どうやってプログラミングするとよさそうかという話が論文に書いてあるので、よければ論文もご参照ください。

性能評価は「VMCALLより3.5倍ほど軽量」

性能評価に移ります。まず、コンテキスト切り替えのオーバーヘッドですね。ベースライン、VMCALLというVMEXITを起こすものがあるのですが、そちらの挙動を説明します。

今、私たちは仮想マシンの中にいるとします。ここでVMCALLを実行するとexitが発生して、ホストにコンテキストが切り替わります。ここでリクエストがあったら処理できるのですが、なんにもなかったらこのままReturnします。これだけで、だいたい私たちのハード(ウェア)では699ナノセカンドかかりました。

一方、提案手法ではどんな感じになるかというと、VMFUNCを実行して、コンテキストを切り替えて、なにかやって、VMFUNCを呼んでまた元に戻ります。これでだいたい同じですが、これにかかった時間は、196ナノセカンド。

つまり、提案手法はVMCALLより3.5倍ほど軽量であるという結果になりました。

ユースケースとして仮想マシン通信のシステムを開発

この軽量さは、共有オブジェクトに頻繁にアクセスするようなシステムにとってありがたいです。「じゃあ、具体的にどんなの?」というと、仮想I/Oのシステムとかがありますよねということで、ユースケースとして、仮想マシン通信のシステムを作ってみました。

(スライドを示して)こちらは、ベースラインでホストが仲介するパターンなんですけど、こういう場合は仮想スイッチがホストの中に実装されていて、私たちが仮想マシンに中にいたら、exitを起こしてリクエストを送り、ホストは仮想スイッチを実行して、その上でDMAの領域にアクセスしてパケットを飛ばしたりして、仕事が終わったら、最後にまたコンテキストに戻ってくる、みたいな感じです。

私たちの場合だとどういう挙動になるかというと、仮想スイッチがtrustedなコンテキストに実装されていて、これにアクセスしたい場合、仮想マシンはVMFUNCを実行してコンテキスト切り替えて、そこのコンテキストに入った上で、パケットI/Oを仮想スイッチを実行した上で行っていきます。

これは、ほかの仮想マシンでも同様にできるので、このデバイスを共有できるようになります。

さらにこれは、普通の仮想スイッチの実装なので、この仮想マシン間のコミュニケーションもサポートできます。

性能を測ると、受信に関しては、VMFUNCのパターンと比べて最大54パーセント性能が高くなり、転送の場合は49パーセント性能が上がりました。VM間通信だと163パーセント良くなりました。

「memcached」をこの仮想マシン上でこの提案手法で動かすと、VMCALLのケースと比べて、スループットがある程度サチった(限界に達した)ところで44パーセントぐらい、99パーセンタイルの遅延を抑えることができました。

まとめ

まとめます。ELISAは、仮想マシンのためのメモリ共有手法で、EPTを分割することによってアイソレーションをします。VMFUNCの命令を使ってコンテキストの切り替えをするので軽量である、ということになります。

「仮想メモリ・ページテーブル、何ぞや?」という感じであれば、解説を用意したので、よかったらこちらをご参照ください。

あと、もう1点お知らせです。2021年11月のKernel/VM探検隊でお話しした内容を「USENIX ATC」という学会で論文として発表してきました。Best Paper Awardをいただいたので、よければこちらもご参照ください。

(会場拍手)

yasukata:ありがとうございます(笑)。以上です。ありがとうございます。

質疑応答

司会者:ありがとうございます。すごい。Kernel/VM探検隊は最先端ですよ(笑)。

会場から質問やコメントがある方はいらっしゃいませんか?

質問者1:メモリ空間を完全に切り替えると、そこにあるコードとデータしか見られなくなるから安全という話だと思うんですが、パラメーターはどうやって渡すんですか?

yasukata:すみません、それははしょってしまったのですが、引数でレジスタに載せます。

絵では描いていないのですが……(スライドを示して)実はこの緑と、青、黄色、赤の間で共有メモリを作れるようにしてあって、そこにデータを載せて、切り替えて、そこでNICのレジスタに触ってパケットを飛ばすという感じになっています。すみません、ちょっといろいろはしょっていて、正確ではなかったです。

質問者1:あと、もう1つ質問なんですけど、これだと例えばスピンロックを取ってVMの排他制御をするとなると、1個VMが乗っ取られちゃったりしたら、DoS攻撃が可能になったりしないんですかね?

yasukata:おっしゃるとおりです。なので、このtrustedなところに入っているところでは必ず入ったら、絶対そこの実装はホスト側から提供しているので、そこは必ず出られるという想定になっています。

質問者2:具体的にEPTは何個まで、1つのVMに置けるんですか?

yasukata:ありがとうございます。これはIntelの仕様で、512個まで置けます。

質問者2:ならけっこう、やりたいことごとにEPTを作るということができるっぽい。

yasukata:そうですね。

質問者2:わかりました。

司会者:ほかに、いますか?

では、お時間もありますので、終わりにしたいと思います。ありがとうございました。

yasukata:ありがとうございます。

(会場拍手)