NetBSDのNVMMを使ってみる
NetBSD 9.0からはNVMMというハイパバイザーを利用できるようになった1.今のところ,NVMMはIntel VT-xやAMD-Vなどのハードウェア仮想化支援機能がある環境で動作するらしい.LinuxでいうKVM的な立ち位置だと思う.
NVMMの有効化
デフォルトでは有効化されていないよう2なので,NVMMを有効化する必要がある.私の環境では,以下のような手順を踏めば有効化できた.
1. NVMMが有効化されているカーネルのビルド
カーネルのコンフィグファイル内にNVMMに関する項目がある.初期状態ではコメントアウトされていると思うので,これを外してビルドする.その後,出来上がったカーネル(netbsd
)を/
に配置する.
$ cd /usr/src $ cat sys/arch/amd64/conf/GENERIC | grep nvmm pseudo-device nvmm # NetBSD Virtual Machine Monitor $ ./build.sh -U -O ./obj kernel=GENERIC $ sudo mv /netbsd /netbsd.old $ sudo cp obj/sys/arch/amd64/compile/GENERIC/netbsd /
2. NVMMのカーネルモジュールのビルド
$ cd /usr/src/sys/modules/nvmm/ $ make $ sudo make install
3. libnvmmのビルド
$ cd /usr/src/lib/libnvmm/ $ make $ sudo make install
ここまでできたら,あとは再起動して,NVMMのカーネルモジュールをロードする.以下のようになってたらOK.
$ sudo modload /stand/amd64/9.0/modules/nvmm/nvmm.kmod $ modstat | grep nvmm nvmm misc builtin - 0 - -
作者であるMaximi Villardさんのブログ記事3によると,libnvmmのテストが用意されてるっぽいので,ビルドして実行してみる.すべてのテストがpassed
になっていればよい.
$ cd /usr/src/tests/lib/libnvmm/ $ make $ sudo ./h_io_assist $ sudo ./h_mem_assist
サンプルの実行
NVMMに関するNetBSD Blogの記事4には,以下のNVMMを使ったサンプルがあるので,試しにビルドして実行してみる.
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <err.h> #include <fcntl.h> #include <sys/mman.h> #include <nvmm.h> #define PAGE_SIZE 4096 /* * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! NOTE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * ---------------------------------------------------------------------------- * Below is an updated version of this example, because the libnvmm API has * changed since I posted the blog entry. See: * https://mail-index.netbsd.org/tech-kern/2019/06/05/msg025101.html * The original version (using the previous API) is available here: * https://www.netbsd.org/~maxv/nvmm/calc-vm-old-api.c * ---------------------------------------------------------------------------- */ /* * A simple calculator. Creates a VM which performs the addition of the two * ints given as argument. * * The guest does EBX+=EAX, followed by HLT. We set EAX and EBX, and then * fetch the result in EBX. HLT is our shutdown point, we stop the VM there. * * We give one single page to the guest, and copy there the instructions it * must execute. The guest runs in 16bit real mode, and its initial state is * the x86 RESET state (default state). The instruction pointer uses CS.base * as base, and this base value is 0xFFFF0000. So we make it our GPA, and set * RIP=0, which means "RIP=0xFFFF0000+0". The guest therefore executes the * instructions at GPA 0xFFFF0000. * * gcc -o calc-vm calc-vm.c -lnvmm * ./calc-vm 3 5 * Result: 8 * * Don't forget to modload the NVMM kernel module beforehand! */ int main(int argc, char *argv[]) { const uint8_t instr[] = { 0x01, 0xc3, /* add %eax,%ebx */ 0xf4 /* hlt */ }; struct nvmm_machine mach; struct nvmm_vcpu vcpu; uintptr_t hva; gpaddr_t gpa = 0xFFFF0000; int num1, num2, ret; if (argc != 3) errx(EXIT_FAILURE, "wrong arguments"); num1 = atoi(argv[1]); num2 = atoi(argv[2]); /* Init NVMM. */ if (nvmm_init() == -1) err(EXIT_FAILURE, "unable to init NVMM"); /* Create the VM. */ if (nvmm_machine_create(&mach) == -1) err(EXIT_FAILURE, "unable to create the VM"); nvmm_vcpu_create(&mach, 0, &vcpu); /* Allocate a HVA. The HVA is writable. */ hva = (uintptr_t)mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0); nvmm_hva_map(&mach, hva, PAGE_SIZE); /* Link the GPA towards the HVA. The GPA is executable. */ nvmm_gpa_map(&mach, hva, gpa, PAGE_SIZE, PROT_READ|PROT_EXEC); /* Install the guest instructions there. */ memcpy((void *)hva, instr, sizeof(instr)); /* Reset the instruction pointer, and set EAX/EBX. */ nvmm_vcpu_getstate(&mach, &vcpu, NVMM_X64_STATE_GPRS); vcpu.state->gprs[NVMM_X64_GPR_RIP] = 0; vcpu.state->gprs[NVMM_X64_GPR_RAX] = num1; vcpu.state->gprs[NVMM_X64_GPR_RBX] = num2; nvmm_vcpu_setstate(&mach, &vcpu, NVMM_X64_STATE_GPRS); while (1) { /* Run VCPU0. */ nvmm_vcpu_run(&mach, &vcpu); /* Process the exit reasons. */ switch (vcpu.exit->reason) { case NVMM_VCPU_EXIT_NONE: /* Nothing to do, keep rolling. */ break; case NVMM_VCPU_EXIT_HALTED: /* Our shutdown point. Fetch the result. */ nvmm_vcpu_getstate(&mach, &vcpu, NVMM_X64_STATE_GPRS); ret = vcpu.state->gprs[NVMM_X64_GPR_RBX]; printf("Result: %d\n", ret); return 0; /* THE PROCESS EXITS, THE VM GETS DESTROYED. */ default: errx(EXIT_FAILURE, "unknown exit reason"); } } return 0; }
起動したVM上で,コマンドライン引数から受け取った2つの数字を足し合わせるサンプルらしいので,実際に実行して確かめてみる.
$ sudo ./calc-vm 5 3 Result: 8 $ sudo ./calc-vm 20 22 Result: 42
ちゃんと動いてるっぽい.上記のコードの解読やNVMMの仕組みに関しては,また別の記事にしたい.
QEMU with NVMM
NetBSDのQEMUではアクセラレータとしてNVMMを使うことができる.少し前までは,pkgsrc-wip内のqemu-nvmm
をインストールする必要があったが,現在は,pkgsrc内のqemu
にNVMMのパッチがマージされたようだ5.ということで,普通にpkg_add(1)
を使って,QEMUをインストールし,QEMU with NVMMを試してみる.
# pkg_add qemu
今回はCirrOSというテスト用のクラウドイメージを使う.起動に成功すれば,以下のようなロゴが表示される.
$ wget http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img $ sudo qemu-system-x86_64 \ -drive file=cirros-0.4.0-x86_64-disk.img,format=qcow2 \ -m 1G \ -nographic \ -accel nvmm ... === datasource: None None === === cirros: current=0.4.0 latest=0.4.0 uptime=277.66 === ____ ____ ____ / __/ __ ____ ____ / __ \/ __/ / /__ / // __// __// /_/ /\ \ \___//_//_/ /_/ \____/___/ http://cirros-cloud.net login as 'cirros' user. default password: 'gocubsgo'. use 'sudo' for root. cirros login:
とりあえず起動には成功したが,本当にNVMMを使っているのかがわからない.NetBSDではnvmmctl(8)
というツールが用意されており,これを使えば,NVMMを使って作られたVMの情報を取得できる.nvmmctl(8)
を使って,QEMUがNVMMを使ってVMを作っていることを確かめてみる.
$ sudo nvmmctl list Machine ID VCPUs RAM Owner PID Creation Time ---------- ----- ---- --------- ------------------------ 0 1 1.0G 4239 Sat Feb 22 23:29:42 2020 $ ps aux | grep 4239 root 4239 2.4 0.7 1349476 30192 pts/0 Sl+ 11:29PM 6:32.52 qemu-system-x86_64 -drive file=cirros-0.4.0-x86_64-disk.img,format=qcow2 -m 1G -nographic -accel nvmm (qemu-system-x86_) fukumoto 5798 0.0 0.0 11416 644 pts/1 O+ 11:48PM 0:00.01 grep 4239