/dev/null

脳みそのL1キャッシュ

NetBSDのNVMMを使ってみる

NetBSD 9.0からはNVMMというハイパバイザーを利用できるようになった1.今のところ,NVMMはIntel VT-xやAMD-Vなどのハードウェア仮想化支援機能がある環境で動作するらしい.LinuxでいうKVM的な立ち位置だと思う.

NVMMの有効化

デフォルトでは有効化されていないよう2なので,NVMMを有効化する必要がある.私の環境では,以下のような手順を踏めば有効化できた.

  1. NVMMが有効化されているカーネルをビルド
  2. NVMMのカーネルモジュールをビルド
  3. libnvmmをビルド

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

NetBSDQEMUではアクセラレータとして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 

参考文献