外部記憶装置

外付け記憶装置

Mewz on libkrun - その7 virtio-vsock について

Mewz (WebAssembly x Unikernel) を libkrun で動かしてみた - 外部記憶装置 の詳細を記すシリーズ

目次

Virtio Socket (virtio-vsock) について

docs.oasis-open.org

Virtio Socket (virtio-vsock) はゲストとホスト間をいわゆるソケットを用いて通信を行うためのデバイスである。 virtio-vsock のデバイス自体は Virtuqueue を3つ(rx, tx, event) 持つだけのシンプルな構造である。 ただし、ソケット相当の機能を提供するためにそれらの Virtqueue 上でソケットの multiplexing やフロー制御を行う必要がある。

virtio-vsock の概要

virtio-vsock では、ホスト・ゲストの識別子として 64bit 長の Context ID (CID) を持つ。また、ポートの概念も持ち、32bit 長の識別子で扱う。

これらの識別子については TCP, UDP のようにヘッダに格納されペイロードと合わせてパケットとして Virtqueue を流れる。

https://docs.oasis-open.org/virtio/virtio/v1.3/csd01/virtio-v1.3-csd01.html#x1-4800006

virtio-vsock ではソケットの管理やAPIについては言及されていないため、ゲスト側でソケットと送受信するパケットを適切に振り分ける仕組みが別途必要となる。 そのため、これまでの virtio-net や virtio-console と異なり、ドライバ単体だけでなく OS 側のソケットのサブシステム自体にも手をいれることになる。

通信の種類

virtio-vsock では、VIRTIO_VSOCK_TYPE_STREAMVIRTIO_VSOCK_TYPE_SEQPACKET の2種類の通信方式が定義されている。

VIRTIO_VSOCK_TYPE_STREAM はいわゆる SOCK_STREAM に相当するものであり、順序保証付きコネクション指向型のメッセージ境界がない(=ストリーム形式) 通信を提供する。プロトコルとしては TCP 相当の機能を提供する。

VIRTIO_VSOCK_TYPE_SEQPACKET はいわゆる SOCK_SEQPACKET に相当するものであり、順序保証付きコネクション指向型でメッセージ境界が存在する通信を提供する。IP で利用されるプロトコルとしては SCTP が近い。

virtio-vsock ではこれらの通信を用途に応じて使い分けることができる。

プロトコル

virtio-vsock はコネクション指向型の通信を提供するため、セッションの確立および切断について上図に示すプロトコルが存在する。

接続

接続の起点は VIRTIO_VSOCK_OP_REQUESTを送信するところからである。 dst_cid,dst_portに対応するソケットが存在し接続を受け入れる場合は VIRTIO_VSOCK_OP_RESPONSE を返す。接続を拒否する場合は VIRTIO_VSOCK_OP_RST を返す。接続については以上で完了する。

データの送受信

その後、VIRTIO_VSOCK_OP_RW でデータの送受信を行う。virtio-vsock はロスレスな完全性が保証された通信路を前提とするため、Ack を行う必要はない。 データの送受信中、ピアのソケットバッファの状況を把握するためのリクエストとして VIRTIO_VSOCK_OP_CREDIT_REQUEST を送信すると、ピアは VIRTIO_VSOCK_OP_CREDIT_UPDATE としてバッファの状況をヘッダに記して返信する。VIRTIO_VSOCK_OP_CREDIT_UPDATE についてはリクエストがない場合一方的に送っても構わない。

切断

切断については VIRTIO_VSOCK_OP_SHUTDOWN により行う。フラグとして、ピアは以降いかなるデータも受信しないことを示す VIRTIO_VSOCK_SHUTDOWN_F_RECEIVE と、いかなるデータも送信しないことを示す VIRTIO_VSOCK_SHUTDOWN_F_SEND がある。これらのフラグを用いて、ピアに対してデータのこぼれが生じないように終了処理を行う。 いかなるデータの送受信が行われることが無いことが確認された時点で、切断処理は完了する。つまり、必要に応じて複数回のVIRTIO_VSOCK_OP_SHUTDOWN がやり取りされる場合がある。例えば、VSockA→VSockBでVIRTIO_VSOCK_SHTDOWN_F_RECEIVEとし、VSockB→VSockA で VIRTIO_VSOCK_SHTUDOWN_F_RECEIVEとした場合、両者ともにデータの受信を行わないことを通知しているため切断とみなすことができ、VSockA→VSockB でVIRTIO_VSOCK_OP_RSTを送信して切断処理を完了する。なお、一定時間VIRIO_VSOCK_OP_RSTが届かなかった場合、VIRTIO_VSOCK_OP_RSTを送信して終了とする。

SEQPACKET について

VIRTIO_VSOCK_TYPE_SEQPACKET では、2種類のメッセージ境界(VIRTIO_VSOCK_SEQ_EOM, VIRTIO_VSOCK_SEQ_EOR)を扱うことができる。最小単位は "Message" であり、メッセージを複数の RW パケットに分割後、最後のパケットに VIRTIO_VSOCK_SEQ_EOM フラグを立てることで境界となる。

もう一つのメッセージ境界としては、複数のメッセージをまとめたものである "Record" がある。Record については 一連の連続した Message をまとめ、Record 間の区切りは VIRTIO_VSOCK_SEQ_EOR フラグを RW パケットに立てることで行う。

フロー制御

virtio-vsock はパケットロスが一切許容されないロスレスな通信路を前提としているため再送機構を持たない。 そのため、相手のバッファ溢れによるパケットロスが生じないようにフロー制御を行っている。

フロー制御としてはシンプルなものであり、ピアから通知されるバッファサイズや転送カウントを元に、バッファの空き状況に応じてデータの送信可否を決定する。

https://docs.oasis-open.org/virtio/virtio/v1.3/csd01/virtio-v1.3-csd01.html#x1-4800006

実際の計算では、(ピアのバッファサイズ) - (送信済みバイト数 - ピアが処理したバイト数) となる。 つまり、RW パケット送信時点で相手が少なくとも持ちうる空き容量を計算している。 ピアのバッファサイズや処理したバイト数については、ピアが送信する RW パケットや VIRTIO_VSOCK_OP_CREDIT_UPDATE パケットヘッダから得ることができる。

まとめ

virtio-vsock のデバイス自体はシンプルであるが、その上にソケットの仕組みを実装することになる。 そのため、カーネルが持つソケット相当のサブシステムに対して上手く組み込む必要がある。 次回は実際に Mewz に対してソケットとの統合も含めて virtio-vsock の実装を行う。