qemu-kvmから追う仮想マシンの一生
前回はLinux KVMのソースコードを読んできましたが、Linux KVMの理解をより深めるために、今回はLinux KVMの呼び出し元であるqemu-kvmのソースコードも読んでみましょう。
qemu-kvmは、オープンソースのCPUエミュレータであるQEMU に、Linux KVMに対応するための修正が加えられたバージョンです。Linux KVMがユーザモードプロセスであるqemu-kvmから「どのように制御されているのか」を併せて確認すると、仮想マシンが生成・実行される流れがよくわかるでしょう。
qemu-kvmの入手先
qemu-kvmの開発プロジェクトによる一次成果物は、現在はSourceForge.netから入手できます。
- kernel virtual machine - SourceForge.net
- URL:http://sourceforge.net/projects/kvm/
本記事は、執筆時点の最新安定版であるqemu-kvm-0.14.0を基に解説します。
qemu-kvmの一生 = 仮想マシンの一生
qemu-kvmは、仮想マシンを1つ起動するごとに、プロセスが1つ発生します。すなわち、仮想マシンはqemu-kvmプロセスの起動と同に発生し、プロセスの終了とともに消滅します。
qemu-kvmプロセスは、Linux KVMを利用する場合、kvm_init()関数を通してLinux KVMの初期化を行います。この関数では、Linux KVMのAPIにアクセスするために、必要な/dev/kvmファイルをオープンします。以後、Linux KVMのカーネルモジュールで仮想マシンや仮想プロセッサ(vcpu)を作成したり、メモリなどの資源を割り当てたり、また仮想プロセッサを実行する操作は、ここで得られたfdに対するioctl()システムコールで実現することになります。
kvm_init() 関数は、Linux KVMがオープンできたら、続けてkvm_create_context()関数を呼び出して、Linux KVMへ仮想マシンの作成を指示します。この処理は、さっそくオープンしたばかりの/dev/kvmデバイスを通してKVM_CREATE_VM APIを呼び出すことで実現されています。
この後、qemu-kvmプロセスは、仮想プロセッサの割り当てやメモリの割り当て、デバイスエミュレータの登録などさまざまな処理を行います。今回は、仮想プロセッサに着目して、仮想プロセッサがどのように実行されているかを追ってきましょう。
qemu-kvmのスレッドモデル
以下に、qemu-kvmがLinux KVMを使った仮想マシンを実行する場合のスレッドモデルを示します。
qemu-kvmは、プロセス起動後に必要な仮想マシン分の仮想プロセッサを作成し、またスレッドを立ち上げます。以後、各スレッドは仮想プロセッサとしてひとつひとつが独立して動くイメージです。
仮想プロセッサの生成
仮想プロセッサの初期化処理は、qemu-kvm-0.14.0/hw/pc.cにあります。pc_cpus_init()関数は、仮想マシンに割り当てられた数の仮想プロセッサを初期化します。
pc_cpus_init()関数は、各仮想プロセッサを初期化するためにpc_new_cpu()関数を呼び出していますが、その過程でLinux KVM利用時にはkvm_init_vcpu()関数に制御が移ります。
kvm_init_vcpu()関数では、各仮想プロセッサごとにスレッドが生成され、実行が開始されます。
上記の過程により、仮想プロセッサが動きだします。
仮想プロセッサの起動
各スレッドは、Linux KVMの機能を利用して、仮想マシン上に仮想プロセッサを生成します。そして、メインスレッド側で仮想マシン実行準備が完了するまで待ち合わせてから、実際に仮想マシンの実行を開始します。
仮想プロセッサのメインループ
kvm_main_loop_cpu()関数は動作中の仮想プロセッサのメインループです。Linux KVMのAPIによって仮想プロセッサを実行します。
kvm_cpu_exec()関数は、実際にLinux KVMを使って仮想マシンを実行するための関数です。/dev/kvmデバイスに対して実際にKVM_RUNを発行し、仮想マシンを実行します。仮想マシンの実行が停止すると、仮想マシンからの復帰理由が通知されるため、その値から判断し適宜必要な処理を行い、仮想プロセッサの実行を再開します。
KVM_EXIT_*発生後のイベントディスパッチ
仮想プロセッサを実行してしばらくすると、何らかの理由により仮想プロセッサが停止し、Linux KVMからqemu-kvmに制御が戻ることになります。たとえば、仮想マシン上でデバイスI/Oが発生してLinux KVMがハードウェアエミュレーションの処理をしなければならない場合、一定時間仮想マシンの実行をしたため別の仮想マシンやプロセスへ切り替えする場合などです。
Linux KVMのカーネルモジュール内にはVMEXIT発生時のイベントハンドラが含まれていました。基本的に、それらのハンドラにより仮想プロセッサが停止した原因を除去し実行を再開可能なら、Linux KVMが内部的に対処し仮想マシンの実行を継続します。しかし、Linux KVMが自体で原因を除去できないような理由でVMEXITが発生した場合には、ioctl()の呼び出し元であるqemu-kvmへ制御を戻します。 qemu-kvmは、VMEXITの発生理由を除去して、再度ioctl()で仮想プロセッサの実行をLinux KVMに指示します。
仮想マシン上でデバイスI/Oが発生したケースを考えてみましょう。I/Oが発生すると、プロセッサの仮想化支援機能により、仮想マシンからホストへモード遷移が発生します。Linux KVMは、VMEXITが発生した理由を確認し、それがデバイスI/Oであると認識します。しかし、Linux KVM自体はハードウェアエミュレーションの機能を持っていませんので、qemu-kvmにKVM_EXIT_IOを通知し、デバイスI/Oエミュレーションの処理を要求します。
kvm_cpu_exec()関数を追ってみましょう。この関数では、KVM_EXIT_IOが発生した場合には、仮想マシンのデバイスI/Oをエミュレーション処理を行うkvm_handle_io()関数を呼び出します。
この先でどのような処理が行われているかは、デバイスI/Oの回にて改めて説明したいと思います(が、ここまで読まれた方であれば、だいたい想像がつきますよね)。
プロセッサ仮想化のまとめ
第4回では、仮想マシンにおいて仮想プロセッサがどのようなものであるべきか、また、それを実現するための手法としてtrap-and-emulateを紹介しました。また、仮想化支援機能を持たないx86プロセッサでシステム仮想マシンを実現するにはどのような障壁があったのかについて解説しました。
そのような状況の中で、仮想化支援機能を搭載したx86プロセッサが各社よりリリースされました。第5回は、x86プロセッサが持つ仮想化支援機能がどのようなものであるかについて解説しました。
現在x86システムの世界で使われているx86システム仮想マシンは、ほぼ全てが仮想化支援機能を用いています。第6回、そして第7回(今回)は、仮想化支援機能を搭載したプロセッサを前提に、trap-and-emulateによるシステム仮想マシンの実装の一例として、Linux KVMおよびqemu-kvmのソースコードを追いました。
今回をもって、Intel VTやAMD-V、そしてLinux KVMを組み合わせることにより、仮想プロセッサを実現する仕組みについて解説しました。読者の皆さんには、仮想マシンの仕組みについて大体ご理解いただけたと思うのですが、いかがでしょうか?
次回予告
これまで、コンピュータを構成する三大コンポーネントのうち、プロセッサの仮想化に焦点をあてて解説してきました。今回をもって仮想プロセッサの概念からいったん離れ、次回からはシステムメモリの仮想化手法について紹介したいと思います。