(続)kvm にハイパーコールを追加する

この記事の続き

前回の記事では kvm にハイパーコールのハンドラを記述していたが,様々な事情でホストのユーザーランドでハンドリングしたいかもしれない。 今回はそのような場合の対処を書く。

カーネルへの変更

ベースのコードは Linux 4.18 です

まずはおさらい。このように vmcall の番号とハンドラを追加する。

diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h
index dcf629dd2889..e0f8b786a62a 100644
--- a/include/uapi/linux/kvm_para.h
+++ b/include/uapi/linux/kvm_para.h
@@ -26,6 +26,7 @@
 #define KVM_HC_MIPS_EXIT_VM            7
 #define KVM_HC_MIPS_CONSOLE_OUTPUT     8
 #define KVM_HC_CLOCK_PAIRING           9
+#define KVM_HC_TEST                 100

 /*
  * hypercalls use architecture specific
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 2b812b3c5088..5158880899f0 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -6740,6 +6740,10 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
                ret = kvm_pv_clock_pairing(vcpu, a0, a1);
                break;
 #endif
+       case KVM_HC_TEST:
+           ret = 0;
+           some_process();
+           vcpu->run->exit_reason = KVM_EXIT_HYPERCALL;
+           break;
        default:
                ret = -KVM_ENOSYS;
                break;

前回の記事では書いてなかったが,今回はハンドラの中で exit reason として KVM_EXIT_HYPERCALL を追加してハイパーコールが原因で脱出したことを明記している

続いて,ユーザーランドのモニタプロセス(一般的には QEMU)でハンドリングできるようにする。

diff --git a/arch/x86/kvm/vmx.c b/arch/x86/kvm/vmx.c
index 5d8e317c2b04..5aa014cb96e2 100644
--- a/arch/x86/kvm/vmx.c
+++ b/arch/x86/kvm/vmx.c
@@ -7128,7 +7128,10 @@ static int handle_halt(struct kvm_vcpu *vcpu)

 static int handle_vmcall(struct kvm_vcpu *vcpu)
 {
-       return kvm_emulate_hypercall(vcpu);
+       int ret = kvm_emulate_hypercall(vcpu);
+       if (vcpu->run->exit_reason == KVM_EXIT_HYPERCALL)
+               return 0;
+       return ret;
 }

 static int handle_invd(struct kvm_vcpu *vcpu)

ハイパーコールのハンドラである kvm_emulate_hypercall() は普通真値を返す。 このハンドラは arch/x86/kvm/x86.c:5953 kvm_skip_emulated_instruction() の実行結果を返り値にするのだが, これはさらに arch/x86/kvm/x86.c:5931 kvm_vcpu_do_singlestep() を呼び出しており, この関数の中ではゲストがシングルステップでデバッグ実行されているかどうかをチェックしている。

このチェックが通ったときにのみ返り値が偽値となり exit reason が KVM_EXIT_DEBUG に設定されるため, 上記コードの handle_vmcall() の返り値も偽となり,結果ハイパーコールのハンドラがユーザーランドにエスカレーションされる。

今回は追加したハイパーコールもモニタプロセスでハンドリングするために,exit reason が KVM_EXIT_HYPERCALL の 場合にも偽値を返すように変更を加えた。

ちなみに,x86/x64限定ではあるがこのアーキテクチャで KVM_EXIT_HYPERCALL を exit reason に設定するハイパーコールは 存在しないはずなのでこのコードが既存の挙動を破壊することはない(はず)

ユーザーランドのモニタプロセスへの変更

QEMU v2.10.1 へ追加を行なった

以下,コード

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 46ce479dc3..06eddcf1f3 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -2089,6 +2089,10 @@ int kvm_cpu_exec(CPUState *cpu)
                 break;
             }
             break;
+        case KVM_EXIT_HYPERCALL:
+            DPRINTF("hypercall is handled in QEMU!\n");
+            ret = 0;
+            break;
         default:
             DPRINTF("kvm_arch_handle_exit\n");
             ret = kvm_arch_handle_exit(cpu, run);

非常に簡単である。kvm のアクセラレータのコードで exit reason が KVM_EXIT_HYPERCALL の場合に処理を追加している