ハードウェア仮想化支援とは何か

佐伯 学哉氏:入門セッション3つ目は『Armの仮想化支援機構』についての入門セッションです。どうぞよろしくお願いします。

本発表のスタートとゴールです。VMwareとかQemuとか使ったことあるけど仮想マシンの仕組みなんも知らんというところがまずスタートになっています。

1個目のゴールは、最近のVMのざっくりした仕組みとハードウェア仮想化支援とは何かということがわかること。そしてその話のあとに実際にArmの仮想化支援機構の概要を説明し、Armの仮想化支援機構の概要がわかるようになるというのが2つ目のゴールです。

最後におまけとして、Apple Hypervisor FrameworkというmacOSの組み込みのハイパーバイザーフレームワーク、KVMみたいなやつがあるんですけど、それのMac対応の話をします。

国際会議VEEで論文が通る

まず自己紹介です。@nullpo_head(ヌルポヘッド)と言います。本名は佐伯学哉です。最初のオープニングでも言ったんですけどソフトウェアエンジニアをやっています。MS(*)でWebエンジニアをやっていて……。仕事はWebのほうで低レイヤーのほうじゃないです。LegalScapeというベンチャーで副業のエンジニアもやっています。(* 発表当時。2021年現在、サンフランシスコのサービスメッシュに関するスタートアップ、Tetrate.io (https://www.tetrate.io/) に所属。)

過去のおもしろげなプロジェクトとしては、CPU実験でXv6をMIPSと自作CPUのアーキテクチャに移植したやつとか。未踏の2016年では、Noahという仮想化を乱用すればWSL1をカーネル空間じゃなくてユーザー空間だけで誰でも実装できるぞという手法をやりました。

VEEという著名な仮想化に関する国際会議で論文が通っていて、その話を2020年の8月22日のContainer Runtime Meetupでやるので、興味がある方はどうぞ聞いてください。それから小ネタとして、WSLでWindows Helloでsudoするやつとか書いているので、ぜひ使ってください。

ハードウェア仮想化支援とは

さっそく本題に入っていこうと思います。まずハードウェア仮想化支援についてです。ハードウェア仮想化支援ってよく聞くんですけど、いったい何なんでしょう? という話から始めます。

ハードウェア仮想化支援とは、いわゆる仮想マシンの基盤技術にあたるものです。ここで言う「いわゆる仮想マシン」とは、ソフトウェアによって仮想的なハードウェア上で動く、別のOSをエミュレートすることとします。ただし別のOSというのは語弊がありまして、OS以外にいろいろなものが動き得るんですけれども、話を簡単にするために今回は別のOSだという話で統一していきますね。

具体的なソフトウェアとしてはQemu-KVM、VMware、そしてMacのParallelsとか、WindowsのHyper-Vとかがあります。現代の仮想マシンは、ハイパーバイザーを通じて、こうしたハードウェア仮想化支援機構を利用しています。

ハイパーバイザーというのはKVMとかHyper-Vのことなんですけど。仮想化支援機構としてはIntelのVT-Xとか、AMD-V……ヴイなんですかね、これ。読み方。それからArmのArm Virtualization Extensionsなどが存在します。

仮想マシンとは何なのか

ここで仮想マシンの解剖というか、仮想マシンとは何なのかという話からします。現代の仮想マシンをざっくり2つに分けるなら、CPUの仮想化とハードウェアの仮想化に分かれます。CPUというのはメモリとか割り込みも含んでいて、ハードウェアというのは例えばディスクとかNIC、ネットワークカードとかですね。

このうちCPUの仮想化が重要でして、仮想マシンのコアの部分と言うことができます。CPUの仮想化とは何なのかと言うと、ソフトウェア的に別の仮想的なCPUをエミュレートすることだと言えます。この上でゲストOSがブートするのがいわゆる仮想マシンです。

一番わかりやすいCPUの仮想化の方法は、機械語のシミュレータを書いてしまうことです。例えばLinux kernelのバイナリがあったら、その機械語を1個1個読んでいってちまちまx86の状態をシミュレーションしていく。エミュレーションしていくっていう方式ですね。

性能がかなり悪いんですけど、これはわかりやすくたしかに仮想的なCPUができます。この方式で作られたソフトウェアがBochsというものですね。自作OSをする人はけっこう知っているやつです。

ハードウェア仮想化支援機構で効率化する

このCPUの仮想化支援機構……このような仮想CPUを作る際に1個1個シミュレーションをしていたらかなり効率が悪い。

実はCPUのハードウェア仮想化支援機構というものを使うと大半の命令はいちいちインタプリタで解釈する必要はなくて。実際に直接CPU上で実行してしまえばよくて。仮想化の必要がある命令というのはいくつかだけあって、そういう命令だけトラップしてKVMなどのハイパーバイザーで仮想化の必要がある命令をエミュレーションすることで高速な仮想CPUを実現します。

具体的に仮想化の必要がある命令とはどんなものかと言うと、例えばディスクのアクセス。IOとかは仮想化してディスクをエミュレーションしなければならないので、そのままCPUで実行するだけではうまく動きません。

最近のVMの仕組み

それを踏まえたうえで最近のVMの仕組みを考えます。この図で真ん中にCPUがあります。これが最近のCPUだということにしまして、Qemu-KVMでゲストOSとしてLinux kernelを動かす場合を考えます。

仮想化された最近のCPUでは、ハードウェア仮想化支援機構のおかげで仮想CPUというものができていて、その中でゲストOSのLinuxとそのゲストOSのアプリケーションが存在して動いているわけですね。

この状態でQemu-KVMがゲストOSを動かすときに何をしているかと言うと、まずQemuは大半の命令をこの実CPUの上でそのまま実行しています。なのでKVMに対して仮想CPUでLinux kernelの実行をしてくれという要求を送信します。

するとKVMはハードウェア仮想化支援機構を利用して仮想CPUを実行します。すると仮想CPUが実行してくれて、このとき仮想CPUの中のゲストOS、ゲストLinux kernelとゲストLinux kernelのアプリとかの状態をどこかのメモリから持ってきて、自分の実行状況をその仮想化したものに変えて、そのまま中で実行してくれます。x86とかではこういうことがVMENTRYと呼ばれますね。

中でゲストプロセスがシステムコールなどを叩いたら、中のゲストOSのLinuxが対処します。どんどんどんどん動いていくんですけど、あるときシステムコールとかの影響でゲストOSのLinuxがデバイスIO、例えばディスクアクセスを発行しましたとしましょう。こういうのはあとで説明するセンシティブ命令と呼ばれます。このときハードウェア仮想化支援機構を使ったCPUは、このディスクIOをトラップして、ディスクIOを要求していますということをVMEXITという仕組みを通じてKVMに知らせます。

するとKVMは、ディスクのエミュレーションが必要らしいということを今度はQemuに教え直します。これはエミュレーションが必要な命令なので、ここでQemuがなにか〇〇させるディスクとかの動きを実際にデバイスシミュレーションして、その結果をまた同じくVCPUを実行するかたちで返すことになります。

これが現代の、おおまかな最近の仮想化の仕組みで、こうやって高速にゲストOSを動かすことができます。

センシティブ命令

さて、ここで先ほどから仮想化の必要がある命令ということを言っているんですけれども、具体的には仮想化の必要がある命令とは何だろうという話です。これはテクニカルタームでセンシティブ命令と呼ばれます。センシティブ命令とは仮想CPU上では実CPUと動作を変えなければいけない命令のことと定義できます。

これがちょっと堅くてわかりづらい定義なのですが、要は仮想CPUが自分を実マシン上のCPUだと思い込むために動作を細工する必要がある命令のことです。

例えばなんですけど、CoreファミリーCPU上でPentiumCPUマシンをエミュレートすることを考えます。

x86のCPUには、例えばCPUIDという命令があるんですが、この状態で普通にCPUIDの本当の値を返してしまうとCoreファミリーだという値が返ってしまうんですね。なのでこの値をPentiumのものに書き換えて仮想マシンに返してあげる必要があります。

CPUの仮想化支援機構はそういうセンシティブ命令をトラップする仕組みだと言うことができます。この仮想化支援機構を使ったCPUでは、ハイパーバイザーは普通の命令はCPUに直接実行させて、大抵の命令は直接実CPUで命令して、センシティブな命令だけ命令エミュレーションすればいいということができます。

またさらに最近の仮想化支援機構では、典型的なセンシティブ命令はハイパーバイザーのエミュレーションの力を借りずともCPUで完結することもできるようなものが多いです。例えばメモリマッピングの変更とかはハイパーバイザーがなにかしなくてもCPUの中で完結するIntel EPTとかが存在します。

PopekとGlodbergの仮想化要件

ここでセンシティブ命令の話をしていたんですが、実はもともとすべてのセンシティブ命令がOSとかの特権レベルをトラップできるようなアーキテクチャなら、特別な仮想化支援機構がなくても効率的に仮想化を実現できるという話がありまして、これはPopekとGoldbergの仮想化要件という名前で知られています。

なんでこんな要件が満たされていたら仮想化が実現できるのかというと、ユーザーレベルでゲストOSを直接実行してセンシティブ命令をホストがトラップすれば、ハイパーバイザーが実現できてしまうからなんですね。

どういうことかと言うと、システムコールや割り込みがゲストOSのアプリケーションを発行したとします。ゲストOSのアプリケーションはユーザーレベルで動いているんですけど、本当はこのとき当然センシティブ命令はすべてトラップできるので、システムコールは一旦ホストカーネルに飛んでいきます。

ホストカーネルが今度はそれを受けてゲストOSのアプリケーションからゲストOSに割り込まれたようにうまく書き換えてゲストOSのカーネルにプロキシしてあげるとシステムコールのフックを実現できます。

ほかにも例えば、ゲストOSから権限レベルの読み取りがきたら、本当は今ユーザーレベルで動かしているんだけれど、特権レベルだと回答する必要があるんですね。こういうことをやってあげると仮想化されたCPUが実現できます。

ちなみにこの権限レベルの読み取りはx86だとセンシティブ命令なんですけど、特権レベル命令ではない。つまりOSがトラップできないので、これが実現できないです。具体的にはCSレジスタを見ると、今特権レベルで動いているのかユーザーレベルで動いているのかがわかるんですが、x86だとトラップできないので、それを書き換えられないんですね。ただし現代の仮想化支援機構はPopekとGoldbergの仮想化要件だけじゃなくて、こういう処理に相当することを全部ハードウェアがやってくれることもあるからもっと便利で高速です。

現代のハードウェア仮想化支援機構のまとめ

現代のハードウェア仮想化支援機構の簡単なまとめです。現代の仮想マシンは高速なCPUの実行のためにハードウェア仮想化支援機構を利用しています。CPUの仮想化支援機構は大半の命令を実際にCPUで直接実行しますが、センシティブ命令をトラップしてハイパーバイザーに処理を任せることで高速な仮想CPUを実現できます。

PopekとGoldbergの仮想化要件として、すべてのセンシティブ命令が特権命令であるようなアーキテクチャは、特別な支援機構がなくても割合高速な仮想化CPUが実現できることが知られています。

ただし現代の仮想化支援機構はハードウェアで仮想化が完結するセンシティブ命令がたくさんあって、ただの仮想化可能なCPUアーキテクチャよりもっと効率的に実現できるようになっています。

(後半へつづく)