Mewz (WebAssembly x Unikernel) を libkrun で動かしてみた - 外部記憶装置 の詳細を記すシリーズ
- その1 libkrun を試す
- その2 libkrun の構造
- その3 Mewz 追加実装(Linux zeropage, kernel cmd params)
- その4 Mewz 追加実装(Virtio MMIO) ← この記事
- その5 Mewz on libkrun してみた
- その6 Mewz 追加実装(virtio-console)
- その7 virtio-vsock について
- その8 Mewz 追加実装(virtio-vsock)
- その9 TSI の仕組み
目次
Virtio MMIO について
正しくは Virtio Over MMIO である。 PCI 経由で Virtio デバイスを提供するのではなく、Memory Mapped IO として提供する。 PCI デバイスのスキャン等の必要がなく、デバイスのレジスタが存在するアドレスを指定すればよいため Virtio Over PCI よりもシンプルである。
Mewz への追加実装
Mewz は既に Virtio Over PCI な virtio-net ドライバを持つ。 そのため、Virtqueue などのプリミティブなバッファ系は既に実装されているため、それらを流用し Virtio MMIO な virtio-net デバイスへ対応する。
MMIO Device Register Layout
Virtio MMIO はデバイスごとに指定されたデバイスについて Device Register を持つ。この Device Register から DeviceID 等を読み取り、必要に応じてデバイスを初期化する。
Virtual I/O Device (VIRTIO) Version 1.3
Zig においては packed struct
を用いることでメモリマップドなレジスタ群についても容易に扱うことができる。実際には以下のような実装となっている。
pub const DeviceRegister = packed struct { magic: u32, // 0x000 version: u32, // 0x004 device_id: u32, // 0x008 vendor_id: u32, // 0x00C device_features: u32, // 0x010 device_features_sel: u32, // 0x014 _pad1: u64, // 0x018 driver_features: u32, // 0x020 driver_features_sel: u32, // 0x024 _pad2: u64, // 0x28 queue_sel: u32, // 0x30 queue_size_max: u32, // 0x34 queue_size: u32, // 0x38 _pad3: u64, // 0x3C ...
Vritio MMIO デバイスの初期化
デバイスの初期化についても、Virtio PCI なデバイスとは異なる手順となっている。Virtqueue の用意を行う部分が異なり、残りの部分については "Further initialization MUST follow the procedure described in 3.1 Device Initialization." とあるように Virtio PCI と変わらないため、これについても既存のドライバを流用しつつ実装した。
Virtual I/O Device (VIRTIO) Version 1.3
なお、そのまま実装すると正しく動作しなかったため、mfence
命令をはさみ記述どおりの順番でメモリへの読み書きが行われるようにした。
const virtqueues = try allocator.alloc(common.Virtqueue, queue_num); for (0..queue_num) |i| { const queue_index = @as(u16, @intCast(i)); transport.common_config.queue_sel = queue_index; x64.mfence(); if (transport.common_config.queue_ready != 0) { log.fatal.printf("virtio.mmio: virtqueue[{}] is not ready", .{i}); @panic("virio.mmio is not available"); } const queue_size: u16 = @intCast(transport.common_config.queue_size_max & 0xFFFF); log.debug.printf("virtio.mmio: virtqueue[{}] queue size is {}\n", .{ i, queue_size }); if (queue_size == 0) { log.fatal.printf("virtio.mmio: virtqueue[{}] is not avaiable", .{i}); @panic("virio.mmio is not available"); } const virtqueue = try common.Virtqueue.new(queue_index, queue_size, allocator); transport.common_config.queue_size = queue_size; const desc = @as(u64, @intFromPtr(virtqueue.desc)); const driver = @as(u64, @intCast(virtqueue.avail.addr())); const device = @as(u64, @intCast(virtqueue.used.addr())); transport.common_config.queue_desc_low = @intCast(desc & 0xFFFFFFFF); transport.common_config.queue_desc_high = @intCast(desc >> 32); transport.common_config.queue_driver_low = @intCast(driver & 0xFFFFFFFF); transport.common_config.queue_driver_high = @intCast(driver >> 32); transport.common_config.queue_device_low = @intCast(device & 0xFFFFFFFF); transport.common_config.queue_device_high = @intCast(device >> 32); x64.mfence(); transport.common_config.queue_ready = 1; virtqueues[i] = virtqueue; x64.mfence(); }
割り込み周りの修正
Virtio MMIO では、割り込み終了後明示的に Device Register の InterruptACK
にフラグを立てる必要がある。
Virtio MMIO の場合についてのみ該当処理を行う形で対応した。
fn handleIrq(frame: *interrupt.InterruptFrame) void { _ = frame; log.debug.print("interrupt\n"); if (virtio_net) |vn| { vn.receive(); // acknowledge irq switch (vn.virtio) { .mmio => |*m| m.transport.common_config.interuupt_ack = 1, else => {}, }
Virtio PCI との共存
Mewz は QEMU、つまり Virtio PCI を利用する環境がデフォルトとして想定されている。 QEMU は Virtio MMIO についてもサポートしているため、Virtio PCI 自体をサポートしないという選択肢も考えられるが、今回は Virtio MMIO と Virtio PCI の両者に対してドライバを対応させた。
Virtio PCI と Virtio MMIO の違いとしてはそれぞれのデバイス初期化等の設定周りがほとんどであり、比較的容易に共存させることができる。 Zig では struct の union が提供されているため、今回はそれを利用した。
pub fn Virtio(comptime DeviceConfigType: type) type { return union(enum) { pci: VirtioPCI(DeviceConfigType), mmio: VirtioMMIO(DeviceConfigType), }; }
呼び出し時は関数名が同じならば Zig 0.10.0 からサポートされた inline else
を使うことで以下のように簡潔に記述できる。
fn receiveq(self: *Self) *common.Virtqueue { switch (self.virtio) { inline else => |v| return &v.virtqueues[0], } }
その他の修正
Mewz では、IP アドレスとデフォルトゲートウェイ(DGW)のアドレスは kernel cmd params 経由で受け取る。 libkrun では環境変数を自由に設定することができるため、環境変数としてアドレス類を受け渡すように対応した。
libkrun chroot_vm.c の修正
これまで libkrun の実行に利用している chroot_vm.c では、標準では virtio-net ではなく TSI と呼ばれる virtio-vsock 経由の通信機能を提供する。
virtio-net を利用する場合は --net=passt
を指定することで virtio-net を利用できる。
--net=passt
は QEMU における -netdev user
と類似した機能であり、ゲスト側は指定された IP アドレスと DGW を使う必要がある。
本来は DHCP Client を実装すればよいが、面倒なためとりあえず環境変数経由で指定することにした。
chroot_vm.c 側においても明示的に利用するアドレス類を指定するようにした。
また、ポートを公開する設定として、-t 1234
を指定している。
これは QEMU の hostfwd
に相当するオプションであり、ホスト側の1234/tcp
ポートに対する接続をゲスト側の1234/tcp
へと中継する設定である。
ここまでの動作確認
通信機能を持つアプリケーションとして、Mewz が example として提供している hello_server
を利用した。
環境は前回の動作確認した時の環境を使いまわしている。
$ sudo apt install passt $ cd mewz/examples/hello_server $ cargo build --target wasm32-wasi $ wasker target/wasm32-wasi/debug/hello_server.wasm $ cd ../../ $ zig build -Dapp-obj=examples/hello_server/wasm.o -Dlog-level=info -Denable-pci=false libkrunfw $ cd ../libkrun/examples $ make $ sudo LD_LIBRARY_PATH=../lib ./chroot_vm --net=passt dummy dummy Don't run as root. Changing to nobody... No routable interface for IPv6: IPv6 is disabled Template interface: eth0 (IPv4) MAC: host: 00:15:5d:f5:94:7a DHCP: assign: 192.168.10.2 mask: 255.255.255.0 router: 192.168.10.1 DNS: 10.255.255.254 DNS search list: tailbffcc.ts.net flets-west.jp iptvf.jp [LOG INFO]: booted with linux zero page [LOG INFO]: zeropage: e820_entries=2 [LOG INFO]: E820 Entry [1] addr=0x0 size=0x9fc00 type=1 [LOG INFO]: E820 Entry [2] addr=0x100000 size=0x20310001 type=1 [LOG INFO]: available memory: 14747000 - 20410001 [LOG INFO]: virtio_mmio device detected: addr=0xd0000000 size=0x1000 IRQ=5 [LOG INFO]: virtio_mmio device detected: addr=0xd0001000 size=0x1000 IRQ=6 [LOG INFO]: virtio_mmio device detected: addr=0xd0002000 size=0x1000 IRQ=7 [LOG INFO]: virtio_mmio device detected: addr=0xd0003000 size=0x1000 IRQ=8 [LOG INFO]: virtio_mmio device detected: addr=0xd0004000 size=0x1000 IRQ=9 [LOG INFO]: virtio.net: found mmio device [LOG INFO]: mac: 5a:94:ef:e4:c:ee [LOG INFO]: virtio.net: initialized mmio device Listening on http://0.0.0.0:1234
別ターミナルで curl を実行する。
$ curl localhost:1234 Hello World!
となり、無事 Mewz で動作しているサーバーからレスポンスが得られた。