論文紹介:A Binary-Compatible Unikernel ー Unikernelの解説も添えて

元論文:https://dl.acm.org/citation.cfm?id=3313817

Linuxアプリケーションをそのまま動かせるバイナリコンパチブルなUnikernelを構築しました、という話です。
前回の記事に引き続きUnikernel関連な話題ですが、意外と自分のブログではUnikernelについての話題を触れていなかったので関連論文も合わせて紹介したいと思います。

論文概要

  • タイトル:A binary-compatible unikernel
  • 著者:Pierre Olivier, Pierre Olivier, Pierre Olivier et. al.
  • 会議:Proceedings of the 15th ACM SIGPLAN/SIGOPS International Conference on Virtual Execution Environments (VEE 2019)

仮想環境関連のトップカンファレンスであるVEEでの論文です。

Unikernelについて

Unikernelという概念が初めてでてきたのはASPLOS 2013でのMirageOSの論文です。
Unikernels: library operating systems for the cloud
仮想環境上でアプリケーションを走らせるために、アプリケーションごとに独立した仮想Linuxマシンを立ち上げるのは、どうしてもたくさんの計算資源を消費してしまいます。
しかし、Dockerなどのコンテナ仮想化では、特にマルチテナントな仮想環境ではセキュリティの懸念が残ります。
そこで彼らのMirageOS、アプリケーションを走らせるためのOSをライブラリとして直接リンクさせるライブラリOS形式を用います。
通常、OSは複数のアプリケーションを動かすために計算資源の仮想化を行うわけですが、シングルアプリケーションのためならそのような抽象化レイヤーはいらないという考えです。
ライブラリOSの概念自体は古くからあり、SOSP ‘95で発表されたExokernelでも用いられている概念です。
ただし、当時はあまりにも極端すぎるアプローチで、特にそのライブラリOS用のデバイスドライバは個別で開発する必要がありました。
しかし、仮想環境上ならば、デバイスは仮想デバイスという形でVMM側から共通のインターフェースで与えられるので、仮想デバイス用のドライバさえつくってしまえばこの問題をクリアできます。
要するに、計算資源の仮想化をVMM側に完全に任せることで上に乗っかるOSはさほどがんばる必要がない、という考え方です。
このアプローチはかなり強力で、評価では起動時間・バイナリサイズを大幅に削減することが示されています。これはシステムコールが関数呼び出しとして実現できる・必要な関数のみリンクさせることでサイズを小さくできるなどによるものです。
MirageOSはOCamlで書かれていてGCのコストや型安全性の保障のためのオーバーヘッドがあるはずですが、それを上回るメリットがMirageOSの設計にはあったというわけです。

MirageOSには1つ大きな問題があり、それはアプリケーションをOCamlでMirageOS用に書き直さなければならない、ということです。
彼らの主張としては、今までの型安全ではない言語でかかれたシステムはバグを含んでいることがしばしばあるので、むしろ積極的に書き直すべきだという立場をとっています。
一理あるかもしれませんが、やはり何もかも今までの資産を切り捨てるというのは難しいのがUnikernelの課題としてありました。

Unikernelやそれに類似したシステムは今までいろいろと提案されてきました。
Usenix ATC ‘14で発表されたOSvでは、Linux ABIを提供することでLinuxアプリケーションを動かしたり、JVMを提供したりと、より広く使われている言語をサポートしていますが、アプリケーションそのものは再ビルドする必要があります。
RumpkernelはNetBSDのデバイスドライバを抽出して、OSなしでもアプリケーションで使うことができるというものですが、これもアプリケーションは再ビルドする必要がありました。
つまりこれらの手法はソースコードが入手できることが前提のもので、コンパイル済みのバイナリはサポートできません。

Unikernelを用いたシステムとしては以前紹介したJitsuや前回のLightVMの論文でもいくつか提案はされています。
Unikernelの小さいバイナリサイズ・実行オーバーヘッドの少なさがあってのシステムなのですが、アプリケーション構築のためのエンジニアリングコストをいかに小さくできるか、という課題があったわけです。
LightVMの論文ではTinyxというLinuxディストリビューションを必要最低限に小型化する方法が提案されていましたが、この形式ではUnikernelの恩恵はあまり得られず、論文でもこれは折衷案であるということを認めていました。

提案手法:HermiTux

彼らはこのアプリケーションの移植コストというものを減らすために、再ビルドなしで完全なLinuxでのバイナリコンパチビリティを保ちつつUnikernelの恩恵も得られるものという手法を提案しています。
提案手法のプロトタイプをHermiTuxと名付けています。これはHermitCoreというUnikernelをベースにつくられているからです。

HermiTuxはLinuxのABIのルールに従い、ロードおよびランタイムにエミュレートしてあげる必要があります。
ソースコードの入手を前提としていないため、Linuxシステムの呼び出しを事前に置き換えておいたり、違うライブラリをリンクすることはできません。
また、アプリケーションはライブラリを動的にも静的にもリンクしてある可能性があるので、その両方のパターンを考慮しなければなりません。
HermiTuxでは、uHyveという軽量なハイパーバイザをVMM上(今回はKVMを用いている)をまずは知らせ、その上でLinuxアプリケーションとHermiTuxのカーネルを動かしています。

ロードの仕方も論文には書かれていますが、Linuxシステムコールをどう処理するかが特におもしろかったので、今回はそこだけを見ていきます。
システムコールハンドラは通常であれば、特権レベルの切り替えが発生するのですが、HermiTuxではその必要がありません。そのため、その切り替えがない分、システムコールを速く処理できます。
しかし、呼び出しそのものはsyscall命令によって行われているため、普通の関数呼び出しとしてい実現される通常のUnikernelよりはどうしても遅くなってしまいます。動的にCライブラリがリンクされている場合は、Cライブラリそのものの中にあるシステムコールを予め関数呼び出しに置き換えることによりこの問題を回避できます。
Cライブラリ実装のひとつでであるMuslはシステムコールがマクロを経由して呼び出されているため、Coccinelleというコード変換ツールを使い自動的に置き換えられるとしています。

静的にリンクされている場合は、実行時にバイナリを置き換えるというテクニックを用いています。x86の命令は残念なことに可変長でsyscallは2バイト、通常の関数呼び出しであるcall命令は5バイトです。
そのため、syscall命令の次の命令までまとめて別のスニペットへのjmp命令として置き換えて、スニペット中で関数呼び出しと次の命令を実行して戻ってくるという方法で実現されています。

ひとつの問題として、システムコールの種類は非常に多く、すべてサポートするのは大変ということです。この研究でもすべてのシステムコールはサポートしきれていませんが、それでもいくつかのメジャーなアプリケーションをサポートできています。
システムコールのサポートを追加していくのは実装コストだけでなく、カーネルサイズの増大も引き起こします。そこでこの研究ではそれぞれのシステムコールをモジュール化し、必要なシステムコールのみをカーネルに含められるようにしています。
問題はアプリケーションがどのシステムコールを使うかです。straceなどのツールで実際にどのシステムコールが呼ばれるかを解析するのは、そのテスト実行のコードカバレッジに依存するため安全ではありません。
静的にライブラリがリンクされている場合、静的解析を行うことでかなりの部分を解析することができます。動的にリンクされる場合は、どのライブラリ関数が呼ばれているかを解析することにより、その関数から呼ばれているシステムコールを解析することにより特定することができます。
これらのテクニックにより大幅にカーネルサイズを減らせます。

また、HermiTux用のデバッガとプロファイラといった開発ツールのサポートも行われています。

評価

評価では、通常のLinux VM、Dockerコンテナ、OSv、Rumprunと各種ベンチマークを用いた比較を行っています。
ベンチマークにはLMbench、PARSEC、redis、SQLiteなどが用いられています。
Hello Worldアプリケーションではバイナリサイズ・起動時間・メモリ使用量を大幅に減らせることが示されていて、他のベンチマークのパフォーマンスでもかなりの性能を示しています。

まとめ・感想

Linuxアプリケーションをバイナリコンパチブルで動かせるHermiTuxの手法について解説しました。

Unikernelはかなり攻めた手法でなかなか実運用されているケースは聞かないですが、段々とその活用の仕方と移植コストの軽減策が出てきていて非常におもしろいですね。
将来、コンテナ型仮想化に代わる選択肢として広く使われることになるのか楽しみです。

Unikernelについてもっと掘り下げて知りたい方は、Wikipedia(英語)でも記事になっている他、こちらのQitta記事とかがいいかもしれません。