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

Stack Overflow のこの投稿 https://stackoverflow.com/questions/33590843/implementing-a-custom-hypercall-in-kvm のほぼそのままです。 いつもググるか grep でコードの場所を探してしまうので備忘録。
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 b7618b30b7d6..5c82ac8f4b38 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -6714,6 +6714,9 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
   ret = kvm_pv_clock_pairing(vcpu, a0, a1);
   break;
 #endif
+    case KVM_HC_TEST:
+        some_process();
+        break;
  default:
   ret = -KVM_ENOSYS;
   break;
このように,ハイパーコールの番号を追加し,アーキテクチャ固有のコードにハイパーコールの番号で switch している箇所があるのでそこに処理を追加すれば良い。 ちなみにこのハイパーコールの処理を追加している箇所の関数 kvm_emulate_hypercall は x86 では arch/x86/kvm/vmx.chandle_vmcall() から呼ばれており,この関数は同ソース内にある kvm_vmx_exit_handlers という関数ポインタの配列で [EXIT_REASON_VMCALL] に登録されている。つまり, VM exit の理由が vmcall だった場合に呼ばれる関数だ。

このハイパーコールの呼びかたはアーキテクチャによって異なるが,x86 では簡単で eax レジスタにハイパーコール番号を積んでから vmcall 番号を発行すれば良いので,
asm volatile ("vmcall" : "eax"(100));
とすれば良い。 ただ,arch/x86/include/asm/kvm_para.h では便利な関数が次のように定義されており,
static inline long kvm_hypercall0(unsigned int nr)
{
 long ret;
 asm volatile(KVM_HYPERCALL
       : "=a"(ret)
       : "a"(nr)
       : "memory");
 return ret;
}
これは #include <uapi/linux/kvm_para.h> とすれば読み込めるためアーキテクチャに依存せずハイパーコールを呼べるため直接アセンブリ命令を発行するよりも賢明かと思われる。 引数を増やしたければ kvm_hypercall0() となっている部分の 0 を別の数字にすれば良い。4 まで増やせる。
あたりまえだが,kvm_para.h をインクルードするにしても vmcall 命令を発行するにしても Linux カーネルの中でやらなければいけない。


ところで,kvm の VM exit ハンドラの配列なのだけれども,
static int (*const kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {
 [EXIT_REASON_EXCEPTION_NMI]           = handle_exception,
 [EXIT_REASON_EXTERNAL_INTERRUPT]      = handle_external_interrupt,
 [EXIT_REASON_TRIPLE_FAULT]            = handle_triple_fault,
 [EXIT_REASON_NMI_WINDOW]       = handle_nmi_window,
 [EXIT_REASON_IO_INSTRUCTION]          = handle_io,
        ...
};
がなんでコンパイルが通るかよくわかってません。GNU 拡張で配列の特定の添字の場所に要素を入れて初期化できる記法でもあるのかな,とは思いますが。 どなたか教えてくださるとありがたいです。

追記:やっぱりそういう拡張あった https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html

このブログの人気の投稿

GRUBとEFIの組み合わせで使うときのメモ またはEFI全般のtips

prnというフォルダが作れない理由がビル・ゲイツのあだ名というのはガセである件

BIOS+MBRなWindows7/8を、データコピーやデータ移動まったくなしで UEFI+GPTな環境に移行する