ハイパーバイザの作り方~ちゃんと理解する仮想化技術~ 第8回 Intel VT-xを用いたハイパーバイザの実装その3「vmm.koによるVMEntry」
前回は、/usr/sbin/bhyveの初期化とVMインスタンスの実行機能の実装について解説しました。 今回はvmm.koがVM_RUN ioctlを受け取ってからVMEntryするまでの処理を解説します。
本連載では、FreeBSD-CURRENTに実装されているBHyVeのソースコードを解説しています。 このソースコードは、FreeBSDのSubversionリポジトリから取得できます。 リビジョンはr245673を用いています。
お手持ちのPCにSubversionをインストールし、次のようなコマンドでソースコードを取得してください。
svn co -r245673 svn://svn.freebsd.org/base/head src
/usr/sbin/bhyveは仮想CPUの数だけスレッドを起動し、それぞれのスレッドが/dev/vmm/${name}に対してVM_RUN ioctlを発行します(図1)。 vmm.koはioctlを受けてCPUをVMX non root modeへ切り替えゲストOSを実行します(VMEntry)。
VMX non root modeでハイパーバイザの介入が必要な何らかのイベントが発生すると制御がvmm.koへ戻され、イベントがトラップされます(VMExit)。
イベントの種類が/usr/sbin/bhyveでハンドルされる必要のあるものだった場合、ioctlはリターンされ、制御が/usr/sbin/bhyveへ移ります。 イベントの種類が/usr/sbin/bhyveでハンドルされる必要のないものだった場合、ioctlはリターンされないままゲストCPUの実行が再開されます。 今回の記事では、vmm.koにVM_RUN ioctlが届いてからVMX non root modeへVMEntryするまでを見ていきます。
vmm.koがVM_RUN ioctlを受け取ってからVMEntryするまでの処理について、順を追って見ていきます。 リスト1、リスト2、リスト3、リスト4にソースコードを示します。 白丸の数字と黒丸の数字がありますが、ここでは白丸の数字を追って見ていきます1。
(19)でCPUはVMX non-root modeへ切り替わりゲストOSが実行されますが、ここからVMExitした時にCPUはどこからホストOSの実行を再開するのでしょうか。 直感的にはvmlaunchの次の命令ではないかと思いますが、そうではありません。 VT-xでは、VMEntry時にVMCSのGUEST_RIPからVMX non- root modeの実行を開始し、VMExit時にVMCSのHOST_RIPからVMX root modeの実行を開始することになっています。 GUEST_RIPはVMExit時に保存されますが、HOST_RIPはVMEntry時に保存されません。
このため、VMCSの初期化時に指定されたHOST_RIPが常にVMExit時の再開アドレスとなります。 では、VMCSのHOST_RIPがどのように初期化されているか、順を追って見ていきます。 リスト1、リスト2、リスト3、リスト4にソースコードを示します。 今度は黒丸の数字を追って見ていきます。
vmm_dev.cは、sysctlによる/dev/vmm/${name}の作成・削除と/dev/vmm/${name}に対するopen(), close(), read(), write(), mmap(), ioctl()のハンドラを定義しています。 ここでは/dev/vmm/${name}の作成とVM_RUN ioctlについてのみ見ていきます。
......(省略)......
137: static int
138: vmmdev_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
139: struct thread *td) (1)
140: {
......(省略)......
234: switch(cmd) {
235: case VM_RUN: (2)
236: vmrun = (struct vm_run *)data;
237: error = vm_run(sc->vm, vmrun); (3)
238: break;
......(省略)......
371: }
......(省略)......
382: }
......(省略)......
471: static int
472: sysctl_vmm_create(SYSCTL_HANDLER_ARGS) [1]
473: {
......(省略)......
490: vm = vm_create(buf); [2]
517: }
vmm.cは、IntelVT-xとAMD-Vの2つの異なるハードウェア仮想化支援機能のラッパー関数を提供しています(このリビジョンではラッパーのみが実装されており、AMD-Vの実装は行われていません)。 Intel/AMD両アーキテクチャ向けの各関数はvmm_ops構造体で抽象化され、207〜210行目のコードCPUを判定してどちらのアーキテクチャの関数群を使用するかを決定しています。
......(省略)......
119: static struct vmm_ops *ops;
......(省略)......
123: #define VMINIT(vm) (ops != NULL ? (*ops->vminit)(vm): NULL) [4]
124: #define VMRUN(vmi, vcpu, rip) \
125: (ops != NULL ? (*ops->vmrun)(vmi, vcpu, rip) : ENXIO) (5)
......(省略)......
195: static int
196: vmm_init(void)
197: {
......(省略)......
207: if (vmm_is_intel())
208: ops = &vmm_ops_intel;
209: else if (vmm_is_amd())
210: ops = &vmm_ops_amd;
......(省略)......
216: return (VMM_INIT());
217: }
......(省略)......
261: struct vm *
262: vm_create(const char *name)
263: {
......(省略)......
275: vm->cookie = VMINIT(vm); [3]
......(省略)......
287: }
......(省略)......
672: int
673: vm_run(struct vm *vm, struct vm_run *vmrun)
674: {
......(省略)......
681: vcpuid = vmrun->cpuid;
......(省略)......
688: rip = vmrun->rip;
......(省略)......
701: error = VMRUN(vm->cookie, vcpuid, rip); (4)
......(省略)......
757: }
intel/ディレクトリにはIntel VT-xに依存したコード群が置かれています。 vmx.cはその中心的なコードで、vmm.cで登場したvmm_ops_intelもここで定義されています。
......(省略)......
666: static void *
667: vmx_vminit(struct vm *vm) [6]
668: {
......(省略)......
725: for (i = 0; i < VM_MAXCPU; i++) { [7]
......(省略)......
735: error = vmcs_set_defaults(&vmx->vmcs[i], [8]
736: (u_long)vmx_longjmp,
737: (u_long)&vmx->ctx[i],
738: vtophys(vmx->pml4ept),
739: pinbased_ctls,
740: procbased_ctls,
741: procbased_ctls2,
742: exit_ctls, entry_ctls,
743: vtophys(vmx->msr_bitmap),
744: vpid);
......(省略)......
770: }
771:
772: return (vmx);
773: }
......(省略)......
1354: static int
1355: vmx_run(void *arg, int vcpu, register_t rip) (7)
1356: {
......(省略)......
1366: vmxctx = &vmx->ctx[vcpu];
......(省略)......
1375: VMPTRLD(vmcs);
......(省略)......
1385: if ((error = vmwrite(VMCS_HOST_CR3, rcr3())) != 0)
1386: panic("vmx_run: error %d writing to VMCS_HOST_CR3", error);
1387:
1388: if ((error = vmwrite(VMCS_GUEST_RIP, rip)) != 0) (8)
1389: panic("vmx_run: error %d writing to VMCS_GUEST_RIP", error);
1390:
1391: if ((error = vmx_set_pcpu_defaults(vmx, vcpu)) != 0)
1392: panic("vmx_run: error %d setting up pcpu defaults", error);
1393:
1394: do { (9)
1395: lapic_timer_tick(vmx->vm, vcpu);
1396: vmx_inject_interrupts(vmx, vcpu);
1397: vmx_run_trace(vmx, vcpu);
1398: rc = vmx_setjmp(vmxctx); (10)
......(省略)......
1402: switch (rc) {
1403: case VMX_RETURN_DIRECT: (12)
1404: if (vmxctx->launched == 0) { (13)
1405: vmxctx->launched = 1;
1406: vmx_launch(vmxctx); (14)
1407: } else
1408: vmx_resume(vmxctx);
1409: panic("vmx_launch/resume should not return");
1410: break;
......(省略)......
1458: } while (handled);
......(省略)......
1480: VMCLEAR(vmcs);
1481: return (0);
......(省略)......
1490: }
......(省略)......
1830: struct vmm_ops vmm_ops_intel = {
1831: vmx_init,
1832: vmx_cleanup,
1833: vmx_vminit, [5]
1834: vmx_run, (6)
1835: vmx_vmcleanup,
1836: ept_vmmmap_set,
1837: ept_vmmmap_get,
1838: vmx_getreg,
1839: vmx_setreg,
1840: vmx_getdesc,
1841: vmx_setdesc,
1842: vmx_inject,
1843: vmx_getcap,
1844: vmx_setcap
1845: };
vmcs.cはVMCSの設定に関するコードを提供しています。 ここではHOST_RIPの書き込みに注目しています。 なお、vmwriteを行う前にvmptrld命令を発行していますが、これはCPUへVMCSポインタをセットしてVMCSへアクセス可能にするためです。 同様に、vmwriteを行った後にvmclear命令を発行していますが、これは変更されたVMCSをメモリへライトバックさせるためです。
......(省略)......
309: int
310: vmcs_set_defaults(struct vmcs *vmcs,
311: u_long host_rip, u_long host_rsp, u_long ept_pml4,
312: uint32_t pinbased_ctls, uint32_t procbased_ctls,
313: uint32_t procbased_ctls2, uint32_t exit_ctls,
314: uint32_t entry_ctls, u_long msr_bitmap, uint16_t vpid)
315: {
......(省略)......
325: /*
326: * Make sure we have a "current" VMCS to work with.
327: */
328: VMPTRLD(vmcs);
......(省略)......
416: /* instruction pointer */
417: if ((error = vmwrite(VMCS_HOST_RIP, host_rip)) != 0) [9]
418: goto done;
......(省略)......
446: VMCLEAR(vmcs);
447: return (error);
448: }
vmx_support.SはC言語で記述できない、コンテキストの退避/復帰やVT-x拡張命令の発行などのコードを提供しています。 ここでは、ホストレジスタの退避(vmx_setjmp)とVMEntry(vmx_launch)の処理について見ています。
......(省略)......
69: #define VMX_GUEST_RESTORE \ (17)
70: movq %rdi,%rsp; \
71: movq VMXCTX_GUEST_CR2(%rdi),%rsi; \
72: movq %rsi,%cr2; \
73: movq VMXCTX_GUEST_RSI(%rdi),%rsi; \
74: movq VMXCTX_GUEST_RDX(%rdi),%rdx; \
75: movq VMXCTX_GUEST_RCX(%rdi),%rcx; \
76: movq VMXCTX_GUEST_R8(%rdi),%r8; \
77: movq VMXCTX_GUEST_R9(%rdi),%r9; \
78: movq VMXCTX_GUEST_RAX(%rdi),%rax; \
79: movq VMXCTX_GUEST_RBX(%rdi),%rbx; \
80: movq VMXCTX_GUEST_RBP(%rdi),%rbp; \
81: movq VMXCTX_GUEST_R10(%rdi),%r10; \
82: movq VMXCTX_GUEST_R11(%rdi),%r11; \
83: movq VMXCTX_GUEST_R12(%rdi),%r12; \
84: movq VMXCTX_GUEST_R13(%rdi),%r13; \
85: movq VMXCTX_GUEST_R14(%rdi),%r14; \
86: movq VMXCTX_GUEST_R15(%rdi),%r15; \
87: movq VMXCTX_GUEST_RDI(%rdi),%rdi; /* restore rdi the last */
88:
89: #define VM_INSTRUCTION_ERROR(reg) \
90: jnc 1f; \
91: movl $VM_FAIL_INVALID,reg; /* CF is set */ \
92: jmp 3f; \
93: 1: jnz 2f; \
94: movl $VM_FAIL_VALID,reg; /* ZF is set */ \
95: jmp 3f; \
96: 2: movl $VM_SUCCESS,reg; \
97: 3: movl reg,VMXCTX_LAUNCH_ERROR(%rsp)
......(省略)......
107: ENTRY(vmx_setjmp) (11)
108: movq (%rsp),%rax /* return address */
109: movq %r15,VMXCTX_HOST_R15(%rdi)
110: movq %r14,VMXCTX_HOST_R14(%rdi)
111: movq %r13,VMXCTX_HOST_R13(%rdi)
112: movq %r12,VMXCTX_HOST_R12(%rdi)
113: movq %rbp,VMXCTX_HOST_RBP(%rdi)
114: movq %rsp,VMXCTX_HOST_RSP(%rdi)
115: movq %rbx,VMXCTX_HOST_RBX(%rdi)
116: movq %rax,VMXCTX_HOST_RIP(%rdi)
117:
118: /*
119: * XXX save host debug registers
120: */
121: movl $VMX_RETURN_DIRECT,%eax
122: ret
123: END(vmx_setjmp)
......(省略)......
223: ENTRY(vmx_launch) (15)
224: VMX_DISABLE_INTERRUPTS
225:
226: VMX_CHECK_AST
227:
228: /*
229: * Restore guest state that is not automatically loaded from the vmcs.
230: */
231: VMX_GUEST_RESTORE (16)
232:
233: vmlaunch (18)
234:
235: /*
236: * Capture the reason why vmlaunch failed.
237: */
238: VM_INSTRUCTION_ERROR(%eax) (19)
239:
240: /* Return via vmx_setjmp with return value of VMX_RETURN_VMLAUNCH */
241: movq %rsp,%rdi
242: movq $VMX_RETURN_VMLAUNCH,%rsi
243:
244: addq $VMXCTX_TMPSTKTOP,%rsp
245: callq vmx_return
246: END(vmx_launch)
vmm.koがVM_RUN ioctlを受け取ってからVMEntryするまでにどのような処理が行われているかについて、ソースコードを解説しました。 次回はこれに対応するVMExitの処理について見ていきます。
Copyright (c) 2014 Takuya ASADA. 全ての原稿データ は クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています。
データ化における注釈: 機種依存文字を避けるため黒丸囲み数字を[]、白丸囲み数字を()に変更しています。↩