Mewz on libkrun - その6 Mewz 追加実装(virtio-console)
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 Console (virtio-console) について
virtio-console はコンソール入出力向けの virtio デバイスである。 一般的に、シリアル経由で行われるコンソール入出力を置き換えるために用いられる。
libkrun においても、コンソールデバイスとして標準で用いられる。 シリアルの場合 x86_64 や aarch64 でそれぞれ対応するデバイスが異なり、ドライバを複数用意する必要がある。 virtio-console を用いることで、そういったデバイスドライバを用意する必要が無くなる。
Mewz においては今のところ x86_64 を対象としているため、IO Port 経由でシリアルデバイスを操作することでコンソールの入出力を行っている。 libkrun は virtio-console を用いることが想定されているため、これについても追加実装を行い対応する。
基本機能の実装
virtio-console は Virtqueue を送受信用で計2つ用意し、他の virtio デバイスと同じ手順で初期化すれば基本的な機能は利用できる。
コンソール出力については transmitq(port0)、入力については receiveq(port0) で扱い、Mewz 側でこれに対応するだけで良い。
QEMU においては、-device virtio-serial -device virtconsole,chardev=my_console -chardev socket,id=my_console,host=127.0.0.1,port=3334,server,nowait をオプションとして与え、別ターミナルで telnet localhost 3334 を実行すれば telnet 経由で virtio-console の入出力を扱うことができる。
VIRTIO_CONSOLE_F_MULTIPORT
libkrun では、VIRTIO_CONSOLE_F_MULTIPORT と呼ばれる拡張機能を前提としているため、上述の単純な virtio-console ドライバでは動作しない。
この拡張はその名の通り、複数 virtio-console ポートを扱うためのものであり、Virtqueue 初期化後に各ポートの初期化処理を行う必要がある。
これらの制御は専用の Virtqueue である control receiveqとcontrol transmitqを介して行われる。
VIRTIO_CONSOLE_F_MULTIPORT feature を利用する場合、control キューでは以下のフォーマットに従い制御通信を行う。
struct virtio_console_control { le32 id; /* Port number */ le16 event; /* The kind of control event */ le16 value; /* Extra information for the event */ };
id はポート番号、event は制御用コード、value はステータスコード等を含める。
ポートの初期化は以下の流れに従う。

Virtqueue の初期化後、VIRTIO_CONSOLE_DEVICE_READY を libkrun(Device) に対して通知することでデバイスの初期化が完了する。
その後、Device からポートの追加要求(VIRTIO_CONSOLE_DEVICE_ADD) を受け取る。id で指定されたポートの準備が完了した後、成功を示す value=1 を持つVIRTIO_CONSOLE_PORT_READYメッセージで返答する。
ポートをコンソールの入出力として利用する場合、Device は VIRTIO_CONSOLE_CONSOLE_PORT により Driver に通知を行う。Driver はこれを受けて VIRTIO_CONSOLE_PORT_OPEN に value=1 を入れて返答する。※なお、QEMU においては VIRTIO_CONSOLE_PORT_OPEN を返答しなくとも動くようではあるが、Virtio の仕様上は "MUST" となっているため必ず返答する必要がある。
その後、Device 側から VIRTIO_CONSOLE_PORT_OPEN を受け取ると、該当ポートは既に利用可能な状態であるため、Driver はコンソールの入出力を対応する Virtqueue に流せば良い。
Mewz への実装
drivers/virtio/console.zig に Multiport 対応のドライバを実装した。
libkrun については標準で port0 だけでコンソールの入出力を扱えるため、port0 のみ受け付け、それ以外のポートは拒否するように実装した。
fn handleCtrl(self: *Self, ctrl: *VirtioConsoleControl) void { switch (ctrl.event) { .VIRTIO_CONSOLE_DEVICE_ADD => { // only accept port 0 if (ctrl.id == 0) { log.info.printf("virtio.console: ctrl port added (port={})\n", .{ctrl.id}); self.port0_added = true; self.ctrlTransmit(ctrl.id, .VIRTIO_CONSOLE_PORT_READY, 1); } else { log.warn.printf("virtio.console: ctrl port is not ready (port={})\n", .{ctrl.id}); self.ctrlTransmit(ctrl.id, .VIRTIO_CONSOLE_PORT_READY, 0); } }, .VIRTIO_CONSOLE_CONSOLE_PORT => { if (ctrl.id != 0 or !self.port0_added) { log.warn.printf("virtio.console: cannot use port{} as a console: not added\n", .{ctrl.id}); } else { self.port0_console = true; log.info.print("virtio.console: port0 is specified as a console\n"); self.ctrlTransmit(0, .VIRTIO_CONSOLE_PORT_OPEN, 1); } }, .VIRTIO_CONSOLE_PORT_OPEN => { if (ctrl.id != 0 or !self.port0_added) { log.warn.printf("virtio.console: cannot open port{}: not added\n", .{ctrl.id}); } else { if (ctrl.value == 1) { self.port0_opened = true; log.info.print("virtio.console: port0 is opened\n"); } else if (ctrl.value == 0) { self.port0_opened = false; log.info.print("virtio.console: port0 is closed\n"); } else { log.warn.printf("virtio.console: invalid value: {}\n", .{ctrl}); } } }, .VIRTIO_CONSOLE_RESIZE => { log.warn.print("virtio.console: VIRTIO_CONSOLE_RESIZE is ignored\n"); },
UART(シリアル)との共存については、virtio-console の port0 がコンソールとして利用かつ、既に OPEN な状態にある場合は port0 を利用し、それ以外はシリアルにコンソール出力を流すようにした。
他にも、virtio-console の PCI BAR が IO space mapped な場合への対応等を行い、QEMU と libkrun のどちらでも動作するようにした。
pci: Handle IO space mapped BAR · naoki9911/mewz@8495787 · GitHub
ここまでの動作確認
前回の環境を引き続き利用し、動作確認を行った。なお、 GitHub - naoki9911/mewz-on-libkrun の submodule は更新済みであり、必要に応じて pull すること。
$ cd mewz $ zig build -Dapp-obj=wasm.o -Dlog-level=info -Denable-pci=false libkrunfw $ cd ../libkrun/examples $ sudo LD_LIBRARY_PATH=../lib ./chroot_vm --net=passt dummy dummy [sudo] password for naoki: UART: [LOG INFO]: booted with linux zero page UART: [LOG INFO]: zeropage: e820_entries=2 UART: [LOG INFO]: E820 Entry [1] addr=0x0 size=0x9fc00 type=1 UART: [LOG INFO]: E820 Entry [2] addr=0x100000 size=0x200b0001 type=1 UART: [LOG INFO]: available memory: 144eb000 - 201b0001 UART: [LOG INFO]: virtio_mmio device detected: addr=0xd0000000 size=0x1000 IRQ=5 UART: [LOG INFO]: virtio_mmio device detected: addr=0xd0001000 size=0x1000 IRQ=6 UART: [LOG INFO]: virtio_mmio device detected: addr=0xd0002000 size=0x1000 IRQ=7 UART: [LOG INFO]: virtio_mmio device detected: addr=0xd0003000 size=0x1000 IRQ=8 UART: [LOG INFO]: virtio_mmio device detected: addr=0xd0004000 size=0x1000 IRQ=9 UART: [LOG INFO]: virtio.console: found mmio device UART: [LOG INFO]: virtio.console: initialized mmio device UART: [LOG INFO]: virtio.net: found mmio device UART: [LOG INFO]: mac: 5a:94:ef:e4:c:ee UART: [LOG INFO]: virtio.net: initialized mmio device UART: [LOG INFO]: virtio.console: ctrl port added (port=0) UART: [LOG INFO]: virtio.console: port0 is specified as a console VC: [LOG INFO]: virtio.console: port0 is opened VC: [LOG WARN]: virtio.console: VIRTIO_CONSOLE_RESIZE is ignored VC: Hello, wasker
UART 経由の出力については UART:、virtio-console 経由の出力については VC:を先頭に付与する実装としている。
virtio-console ドライバで port0 が open となった時点で出力が virtio-console となっており、意図した通り動作していることがわかる。