RISC-V OSを作ろう (12) ~ KVM上で動かそう

執筆者 : 高橋 浩和

※ 「RISC-V OSを作ろう」連載記事一覧はこちら
※ 「RISC-V OS」のコードはgithubにて公開しています。


はじめに

この連載で作ってきたOS(Sophia OS)をRISC-V用LinuxのKVM環境内で動かします。

RISC-Vスーパーバイザーモードで動作するOSは、最初から準仮想化(para-virtualized)対応されているようなものです。モニタ(ファームウェア)機能を呼び出す時に利用したSBIを、そのままハイパーバイザー呼び出しに転用できます*1。他のアーキテクチャで言えば、ハイパーバイザーコール命令が最初からOSに埋め込まれているのと同じです。苦労なく動かせるはずです。

動かしてみよう

KVMを動かすには、ハイパーバイザー拡張のあるRISC-Vを搭載したボードが必要です。その上に、Ubuntu24.04 LTS(RISC-V 64ビット用)をインストールします。

現実的には、当面はqemu環境を使うことになると思います。ハイパーバイザー拡張に対応しているqemuであることが必須であるため、ホストのLinuxもUbuntu24.04 LTSにし、qemu v8.2.2上でRISC-V用のUbuntu24.04 LTSを動かすのが楽と思います。参考のために、「おまけ ~ qemu環境へのRISC-V用Ubuntuインストール」にインストール手順のひとつを載せておきます。

Sophia OSを用意します。RISC-V用Ubuntu24.04 LTSの上でビルドしても良いのですが、他の環境で作成済みのバイナリsophiaをコピーして持って来る方が簡単でしょう。

KVMを使う前にkvmカーネルモジュールをLinuxカーネルに組込みます。

$ sudo modprobe kvm

qemuを利用して仮想マシン内でSophia OSを起動します。

  • qemu起動時のオプションで、アクセラレータとしてkvmを指定します。
  • -biosオプションにはnoneを指定します。KVM環境では、ゲストOSに対するBIOSの処理はKVMが行ないます。
$ sudo qemu-system-riscv64 -nographic -machine virt,accel=kvm -m 128M -smp 3 -kernel sophia -bios none
core0: Core0 started.
core2: Core2 started.
core1: Core1 started.
core0: Task1 Eating
core1: Task5 Meditating
core2: Task3 Eating
core0: Timer
core2: Timer
core1: Timer
core2: Inter-Core Interrupt
core1: Inter-Core Interrupt
core2: Task5 Meditating
core0: Timer
core1: Timer
core2: Timer
core1: Inter-Core Interrupt
core2: Inter-Core Interrupt
core0: Task5 Meditating
core1: Task2 Meditating
core2: Inter-Core Interrupt
core0: Inter-Core Interrupt
core2: Task4 Eating
core0: Task3 Meditating
core1: Task1 Eating
core1: Inter-Core Interrupt
core0: Timer

KVMは、先日実装したハイパーバイザーSageVisorと全く同じ位置付けのものです。KVMはゲストOSに貸し与えた仮想CPUをスケジュールし、ゲストOSはSBI呼び出しを通してKVMに要求を出します*2。KVMでも処理しきれない要求はOpenSBIやQemuまでフォールバックします。

KVM上でも動作するゲストOSを作るために

実ハードウェア(ベアメタル)上でも、qemu上でも、KVM環境でも動作するOSを作るには守らなければならないことが幾つかあります。

  • スーパーバイザーモード

    KVMに限らず、ハイパーバイザー上で動かすためにはOSはスーパーバイザーモードで動作するように実装しておく必要があります。

  • 0x80200000番地からの割り付け

    vrit環境では、OSが0x80200000番地に割付いている前提で様々なものが実装されています。RISC-Vのハードウェア的な制約があるわけではなく、ソフトウェア間でのお約束ごとです。OpenSBIもKVMもここにOSが割り付いていることを前提としています。他の実ハードウェアでも0x80200000からの割り付けとなっているものが多いようです。

  • SBI対応

    OSはSBI対応している必要があります。ただし、Ubuntu24.04のLinuxカーネルのKVMでは、レガシー拡張(v0.1)と呼ばれるSBI拡張機能は外されているので、このレガシー拡張のSBIを使わずに実装する必要があります。

    OpenSBI以外のBIOS(ファームウェア)、KVM以外のハイパーバイザーも存在しますが、サポートしているSBI拡張の範囲が異ります。SBIはまだ仕様策定段階で仕様が次々追加されているので、いま暫くは混沌とした状態が続きそうです。

  • ページテーブルエントリの属性

    KVMには、ページテーブルエントリのアクセスビットとダーティビットの更新機能がありません。ゲストOSは、それを前提にした実装にする必要があります。

    詳しくは、次の節「高い実装自由度」で説明します。

  • デバッグ出力

    SBIにはデバッグ出力用のインターフェイスが定義されています。Ubuntu24.04カーネルのKVMはレガシー拡張であるコンソール出力SBIをサポートしていないため、DBCN拡張にあるデバッグ出力SBIを呼び出す必要があります*3

    試しにKVM上でこのDBCN拡張のSBIを使ってみたところ、どこに出力されているか分りませんでした。そこでKVMのコードを調べてたところ、KVMのDBCNデバッグ出力はデフォルトでは無効となっており、有効にするには魔導書 The Definitive KVM (Kernel-based Virtual Machine) API Documentationにはまだ載っていない呪文を唱える必要があることが分りました*4

高い実装自由度

様々な環境でSophia OSを動かしましたが、それら環境に少しづつ差がありそれぞれ用にSophia OSを微調整する必要がありました。

RISC-Vの基本機能(RV64I整数命令またはRV32I基本命令)以外の拡張機能(Extension)のどれを採用するかは、実装者依存です。更にそれぞれの拡張の中に、実装者が自由に仕様を選択できる部分があります。

RISC-V実装の自由度が高いことはハードウェア実装者にとって嬉しいことですが、どのRISC-Vでも動作できるような汎用ソフトウェアを実装することは大変になります。

ページテーブルエントリ(PTE)のA(アクセス)ビットとD(ダーティ)ビット

A(アクセス)ビットはページにアクセスがあったか否かを示すフラグです。一度Aビットを落した後、暫くしてAビットが立っていた場合、このページは利用頻度が高そうであることが分ります。A(アクセス)ビットが立ったPTEが指すページは、メモリ解放処理の対象から外すための指標になります。

RISC-Vアーキテクチャ仕様では、PTEのAビットの更新をハードウェアで実現してもよいし、ソフトウェアに丸投げしても良いとなっています。後者の場合はページへの初回アクセス時にページフォルト例外が発生します。ページフォルト例外ハンドラは、対応するPTEのAビットを立てる処理を行なうことが期待されます。

D(ダーティ)ビットはページに書きこみがあったことを示すフラグです。Dビットが立ったページは、メモリを解放する前にその上にあるデータを退避させるための指標などに利用します。

RISC-Vアーキテクチャ仕様では、PTEのDビットの更新もハードウェアで実現してもよいし、ソフトウェアに丸投げしても良いとなっています。後者の場合はページへの初回書きこみ時にページフォルト例外が発生します。ページフォルト例外ハンドラは、対応するPTEのDビットを立てる処理を行なわなければなりません。

筆者のKVM環境では、例外発生により例外ハンドラが呼び出されたとき、例外ハンドラのある空間をマップするPTEにまだAビットが無いため、ハンドラを呼び出し時にページフォルト例外が発生し、再度例外ハンドラを呼び出そうとする悲しい状態が発生しました。KVMは、Aビット更新をソフトウェアに丸投げする実装だった訳です。

例外ハンドラがある空間をマップするPTEは、最初からAビットを立てておかねばなりませんでした。

rdtime命令

mtimeレジスタは現在時刻を保持していますが、基本的にはマシンモード以外からはアクセス出来ません。マシンモード以外のモードでも時刻を取得できるようrdtime命令が用意されています。

しかし、このrdtime命令がmtimeレジスタの値を読み出すようにハードウェア実装するか、rdtime命令実行時に不正命令例外を発生させるだけにするかはRISC-V実装者が選択できます。

筆者がUbuntu22.04のQemu v6.2.0を使っていた頃は、rdtime命令は期待通りに動いていたのですが、Ubuntu24.04のQemu v8.2.2環境でのrdtime命令は不正命令例外を発生するようになりました。

RISC-V実装が許せばMCOUNTERENレジスタにて、スーパーバイザーモードからmtimeレジスタの値を読み出せるように設定できるのですが、Qemu v8.2.2はこの操作を許可しません(ハードウェア的に読み出せない実装であることを選択している。)。モニタやハイパーバイザーの例外ハンドラに、rdtime命令をエミュレートするコードを追加する必要がありました。

KVM環境では、KVMがrdtime命令実行のエミュレーションを行なってくれるため、ゲストOS側では考慮する必要はありません。

最後に

本連載のOSでは利用しませんでしたが、0x82200000番地にはDTB(デバイスツリー情報)が配置されています。QEMU がvirt ボードに合せて生成したDTBをここに配置します。

OpenSBIの起動メッセージにあるDomain0 Next Arg1は、DTBの場所を示していたことが分りました。

Domain0 Next Address      : 0x0000000080200000
Domain0 Next Arg1         : 0x0000000082200000
Domain0 Next Mode         : S-mode

qemuをDTB無しで起動する方法は無さそうなので、プログラムのロード域がDTB域とぶつからないように注意する必要があります*5

OpenSBIやKVM以外にも、SBI対応している、もしくは対応中のプラットホームがあります。それらの上にもSophia OSを移植してみるのも良いかもしれません。XenbhyveのRISC-V対応の状況は気になるところです。NetBSDも、仮想マシン機能NVMMも含めてRISC-V対応の進捗が気になります。

おまけ ~ qemu環境へのRISC-V用Ubuntuインストール

ハイパーバイザー拡張まで供えたRISC-Vを搭載したボードを入手することは難しいため、殆どの方はqemuを利用してLinuxを動かすことになると思います。

qemu環境へのRISC-V用Ubuntuのインストールは、Ubuntuが用意しているプレインストールイメージを利用すると簡単です。

まず、Ubuntu24.04 LTSがインストールされたホストを用意し、そこにRISC-V用の各種パッケージをインストールします。

$ sudo apt install u-boot-qemu opensbi qemu-system-misc

Ubuntuのプレインストールイメージをダウンロードします*6。ダウンロードできたら解凍しておきます。

$ wget https://cdimage.ubuntu.com/releases/24.04/release/ubuntu-24.04-preinstalled-server-riscv64.img.xz
$ xz -dk ubuntu-24.04-preinstalled-server-riscv64.img.xz

このプレインストールイメージはディスク容量が最低限しか確保されていないので、追加でパッケージをインストールできるようディスク容量を少し増やしておくと都合が良いです。

$ qemu-img resize -f raw ubuntu-24.04-preinstalled-server-riscv64.img +40G
$ partd ubuntu-24.04-live-server-riscv64.img
  (parted) p
  (parted) resizepart 1 100%
  (parted) q

qemuコマンドを直接指定してUbuntu24.04 LTSを起動します。

$ qemu-system-riscv64 -machine virt -nographic -m 2048 -smp 4 -bios /usr/lib/riscv64-linux-gnu/opensbi/generic/fw_jump.bin -kernel /usr/lib/u-boot/qemu-riscv64_smode/uboot.elf -device virtio-net-device,netdev=eth0 -netdev user,id=eth0 -device virtio-rng-pci -drive file=ubuntu-24.04-preinstalled-server-riscv64.img,format=raw,if=virtio
Ubuntu 24.04 LTS ubuntu ttyS0
    :
    :
ubuntu login: ubuntu
Password: ubuntu
Password:
You are required to change your password immediately (administrator enforced).
Changing password for ubuntu.
Current password:
New password:
Retype new password:
Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.8.0-31-generic riscv64)

qemu上で動作しているLinux上でqemuを使うと、qemuを制御することが難しくなるため、ここで立ち上げたUbuntu24.04 LTSはvirshまたはvirt manager経由でゲストOSとして立ち上げるようにした方が使い勝手が良いです。

virt-installコマンドを使って、virsh/virt manager用のxml形式設定ファイルを生成できます*7

$ virt-install --name ubuntu24.04-rv64 --memory 4096 --vcpus 4 --virt-type qemu --arch riscv64 --machine virt --osinfo "ubuntu24.04" --boot "kernel=/usr/lib/u-boot/qemu-riscv64_smode/uboot.elf" --disk "path=/home/riscv/ubuntu-riscv/ubuntu-24.04-preinstalled-server-riscv64.img,format=raw" --import --nographics --network "network=default"

*1:このSBIにより、一部の例外を除き特定の命令やレジスタ操作に対してその都度例外を発生させ、それをハイパーバイザーが捕捉してエミュレートするという面倒な処理を行なう必要がありません

*2:ゲストOSは、SBI呼び出しの先にあるのがOpenSBIなのかKVMなのか、それともSageVisorなのかを知りません。

*3:一方、Qemuに組み込まれている少し古い版のOpenSBIは、DBCN拡張に対応していません。

*4:ゲストOSの実在しない、とあるレジスタを更新するようにKVMに要求するとDBCN機能が有効になる。DBCNを利用するには、qemuにこの機能の呼び出しが実装されるのを待つ必要がある。

*5:暗黙で動作する処理で躓くことが多いです。DTB自動生成とBIOS自動組み込み以外に気を付けるものはないだろうか?

*6:URLが変っていた場合は、変更願います。

*7:virshのdomxml-from-nativeオプションは廃止されており使えません