RISC-V の Hypervisor 拡張で Hypervisor もどきを書く

現実逃避の一環で、以前戯れとして Linux の動く RISC-V CPU を作った時 から気になっていた、RISC-V の Hypervisor Extension で遊びました。しかし、既存実装で遊んだだけでは(外に出すネタとして)面白みが薄そうだったので、ついでに rvvisor という Hypervisor Extension を利用したソフトウェア(≒ Hypervisor もどき)を書いてみています。……という記事です。

はじめに

本記事は TSG Advent Calendar の 5 日目の記事として書かれました。4 日目は hideo54 さんの記事「傷心山陽旅行記」でした。素敵な記事ですね。6 日目も hideo54 さんが「Safari の動画再生のハマりどころ」という記事を投稿しています。これも素敵な記事。

また、筆者はこの領域においてはド素人であることに注意してください。特に Hypervisor Extension に関しては、本来このアドベントカレンダーが投稿されるはずだった 12/5(土)ごろから、土・日・月のスキマ時間に触った程度の経験しかありません。そのため、ここからの記述には色々勘違いがありそうですから、とくに自分で Hypervisor を実装しようと思っている方は、あまり鵜呑みにしないでいただければと思います。誤りの指摘は大歓迎です。

RISC-V Hypervisor Extension

RISC-V には RV32I/RV64I のような基本的な命令セットの他、幾つかの拡張命令セットがあります。とくに Hypervisor に関連する拡張としては、”H” こと、Hypervisor Extension が存在しています。本稿執筆時では v0.6.1 というバージョンのドラフト仕様が存在していていて、その仕様は Volume II: RISC-V Privileged Architectures V1.12-draft などから確認することができます。

Hypervisor Extension の概要は「Developing the RISC-V Hypervisor Extensions in QEMU - Alistair Francis, Western Digital」という講演動画を見るとだいたい掴めます。そのうち特に大事そうな点は以下の 2 点です。

  • 各 Hart に仮想モード(Virtualization Mode; V と書かれる)が設けられる
  • hgatp という CSR に基づき Two-Stage Address Translation が行われる

これらについて少しだけ掘り下げていきます。

Virtualization Mode の導入

まず、Hypervisor Extension がサポートされている場合、各 Hart には仮想モード(Virtualization Mode; V と書かれる)という、 「その Hart がゲストのコンテキストで動いているかどうか」 を示す状態が新しく導入されます。

V が 1 であるときは、その Hart は ゲスト のコンテキストで動作しているものとして取り扱われます。また、V が 1 であり、特権モードが S-mode であるとき、その CPU のモードを VS-mode と表記します。同様に、V=1 かつ U-mode で CPU が動作しているとき、CPU のモードを VU-mode と書き表します。

V が 0 であるときは、その Hart が ホスト のコンテキストで動作しているものとして取り扱われます。また、V=0 かつ特権モードが M-mode / S-mode / U-mode であるとき、CPU のモードは、それぞれ M-mode / HS-mode / U-mode と表記されます。

このような前提のもと、ハイパーバイザは、基本的に HS-mode で動作します。そしてゲスト OS のカーネルは VS-mode で動作し、ゲスト OS のユーザープロセスは VU-mode で動作します。

このような新しい状態を各 Hart に導入した上で、Hypervisor Extension は、いくつかの Hypervisor 向けの CSR を追加します。特に、HS-mode で動作する Hypervisor と VS-mode で動作するゲスト OS の間で CSR を分離するためには、Virtual Supervisor CSR(VS CSR) という CSR が導入されます。Hypervisor はこれらの新しい CSR や仕組みをうまーく使いながら、ゲストと Hypervisor のアイソレーションや、ゲスト間の調停を行うわけです。

Two-Stage Address Translation の導入

Virtualization Mode や CSR の導入により、Hypervisor とゲストの間の権限が分離されました。これと同時に、Hypervisor Extension は、Hypervisor とゲストのメモリ空間の分離のために hgatp という CSR に基づく Two-Stage Address Translation の仕組みを導入します。

Two-Stage Address Translation は、大まかに言えば、 「ゲスト OS がメモリにアクセスする際に、まずは satp (その実は vsatp)に基づくアドレス変換をした後に、さらに hgatp に基づくアドレス変換をする」 という仕組みです。もう少し丁寧に書くと、メモリアクセス時には、以下のようなアドレス変換が行われます。

  1. 「ゲスト上での仮想アドレス」 が、「ゲスト OS が用意したページテーブル」に基づき、「ゲスト上での物理アドレス」に変換される。
  2. 「ゲスト上での物理アドレス」が、「Hypervisor が用意したページテーブル」により、本当の物理アドレスに変換される。

この際、アドレス変換には、既存のアルゴリズム(e.g. Sv39 等)を微妙に拡張したアルゴリズム(e.g. Sv39x4 等)が用いられます。もっとも、拡張といっても、少しビット幅が変わるくらいなはずですが。

このように、Hypervisor はゲストごとに異なるページテーブルを用意してやることで、ゲスト間のメモリ空間、そしてゲストと Hypervisor のメモリ空間を分離してやることができるようになります。

既存の Hypervisor 実装の例

サクッと動かせる RISC-V 向け Hypervisor 実装の例としては、Type1 Hypervisor である xvisor や、Type2 Hypervisor である Linux KVM があります1

そのうち xvisor の方に関しては、Xvisor: Embedded Hypervisor for RISC-V - Anup Patel, Western Digital あたりの動画でデザイン面が紹介されており、xvisor/riscv-virt-qemu.txt at master · xvisor/xvisor あたりに手元で動かすための方法が書かれています。シュッっと動くと思います。

KVM の方に関しては、KVM RISCV64 on QEMU あたりを参考にすれば、直ちにローカルで動かすことができます。本稿執筆時点では、xvisor よりも、こちらの方がさくっと動かせる度が高いかもしれません。

rvvisor: 自作 Hypervisor もどき

先述のような実装例は非常に大きいので、読むのが結構大変です(勉強になるけれども)。また、案外仕様と齟齬のありそうな実装も含まれてい(る気がし)ます。

そこで、3.5 日前から余暇の時間を使って、Hypervisor Extension を利用した RISC-V 向けの Hypervisor もどきを実装してみています。まだ Hypervisor といえるほどの機能はないので、Hypervisor もどきです。

本稿を書いている時点での最新版 では、およそ以下のようなことができるようになっています。

  1. 適当に M-mode から HS-mode に遷移する
  2. (QEMU の -drive オプションでマウントした)ゲスト OS のカーネルが詰められた ELF ファイルをとってきて、それを(適当にページを用意しながら)メモリ中に展開する
  3. Guest Physical Address Translation のためのページテーブルを作成し、そのページテーブルの 0x8000_0000 からの領域を、3 で ELF を展開したページにマップする
  4. hgatp に 4 で用意したページテーブルを設定しつつ、CPU の特権レベルを VS-mode に変更しながら 0x8000_0000 にジャンプする

つまり、QEMU にマウントしてある ELF を VM 用のメモリ領域に展開しつつ、VM の動作を開始する、みたいなことをしている、というわけです。以下は実際に小さなゲストを用意して、それに遷移している様子です(現時点では特に情報量がないけど……)。

動いている様子

もっとも複数のゲストを動かすことや、それらをスイッチしたり、ということはできていません。また、CSR 書き込み時の例外処理や各種ハードウェアを仮想化してゲストに見せる、ということもできていません。その他諸々実装が終わっていません。したがって、先に述べた通り、現時点では Hypervisor とよべるような代物ではないと言えます。逆に、この辺をちまちま実装していけば、いい感じに小さめのサンプル Hypervisor 実装ができそうだな、と見込んでいます2

一方、現時点でも、hgatp の振る舞いや CPU の特権レベルの変更処理がどのように行われるのかの雰囲気を理解するには、十分なサンプルになっていると思います。興味がある方がいれば、ぜひ動かしながら、CPU モードの遷移を追いかけてみてください。

おわりに

やっぱり、こういう不安定な仕様周りの実装例を見たり、自分が実装してみたりしていると、結構「これ大丈夫なんか?」みたな実装・仕様にぶち当たりますね。これが不安定なものと遊ぶ醍醐味だなあと思います。最近日々に刺激が足りなかったのでちょうど良かったなあと。

また、2020 年は色々に追われて趣味の時間がほぼ取れていなかったので、こうした形で息抜きができる状態になったのは喜ばしいな、とも思っています(もちろん本稿の準備や rvvisor の実装に取り組んでいた土・日・月の間も、ちゃんと進めるべきことは進めているので、関係各位はご安心ください)。

では、みなさんもよい RISC-V Hypervisor Extension ライフを!

  1. Hypervisor Extension を利用せずに頑張っている実装もあるようです。例えば diosixRVirt など。僕は読んだことがないのですが、もし詳しい人がいたら、ぜひ教えてください。 

  2. 基本的に面倒なことが多いので、続きをやるかやらないかは、迷いどころですが。どうしようかなあ。 

Written on December 4, 2020