bobuhiro11/gokvm - GitHub

はじめに

gokvmをvhost-userに対応させるにあたり、初期化部分について調査したのでメモとして残しておく。 QEMUのドキュメントVhost-user Protocol 1 に詳しくまとまっているが、 実際に動かしてみないことには分からない部分(例外処理だったり、リクエストの順番だったり、ログの落ち方だったり) もあると思うので動かしてみた。

色々試行錯誤してみたものの、結局 QEMU と DPDK の2つだけで手軽に試すことができた。 ここでは QEMU をサーバモード、DPDK をクライアントモードで動かした。 サーバモードが vhost-user 用の Unix Domain Socket の生成、クライアントがその Socket への接続を担当する。

dpdk-skeleton のビルド

DPDK にはいくつかサンプルプログラムが用意されているが、ここでは dpdk-skeleton を利用することにした。 私の使っているディストリビューションではそれは DPDK パッケージに同梱されていなかったので、以下でビルドした。

$ git clone git@github.com:DPDK/dpdk.git
$ cd dpdk
$ meson setup -Dexamples=skeleton build
$ cd build
$ ninja
$ file ./examples/dpdk-skeleton
./examples/dpdk-skeleton: ELF 64-bit LSB shared object, x86-64, ...

仮想マシンの起動

軽量なVMイメージであるCirrosをQEMUからブートさせた。 ここでpath=$HOME/vhost-net0 がUnix Domain Socket に対応する。 logfile=$HOME/vhost-net0.log でその通信内容をログに吐くことができる(これは後の調査で必要になる)。 今回はサーバモードで動かすので server=on とした。

$ wget https://github.com/eprasad/virt-cirros/raw/master/virt-cirros-0.3.4-x86_64-disk.img
$ sudo qemu-system-x86_64 -enable-kvm \
  -hda ./virt-cirros-0.3.4-x86_64-disk.img \
  -nographic -serial mon:stdio \
  -chardev socket,id=char0,path=$HOME/vhost-net0,server=on,logfile=$HOME/vhost-net0.log \
  -netdev type=vhost-user,id=mynet0,chardev=char0,vhostforce=on \
  -device virtio-net-pci,mac=aa:aa:aa:aa:aa:aa,netdev=mynet0

dpdk-skeleton の起動

dpdk-skeleton は起動するとすぐに QEMU が準備した Socket への接続を試みる。 接続後は Vhost-user Protocol に従ってネゴシエーションが走る。 標準出力をみるとどんなメッセージがやりとりされているか確認できる。

$ sudo bash -c "echo 1024 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages"
$ sudo ./examples/dpdk-skeleton -l 1 --no-pci \
  --vdev=net_tap0,iface=tap0 \
  --vdev=net_vhost0,iface=$HOME/vhost-net0,client=1,queues=1
...
VHOST_CONFIG: vhost-user client: socket created, fd: 43
VHOST_CONFIG: new device, handle is 0, path is /home/bobuhiro11/vhost-net0

Core 1 forwarding packets. [Ctrl+C to quit]
VHOST_CONFIG: read message VHOST_USER_GET_FEATURES
VHOST_CONFIG: read message VHOST_USER_GET_PROTOCOL_FEATURES
VHOST_CONFIG: read message VHOST_USER_SET_PROTOCOL_FEATURES
VHOST_CONFIG: negotiated Vhost-user protocol features: 0xcbf
VHOST_CONFIG: read message VHOST_USER_GET_QUEUE_NUM
VHOST_CONFIG: read message VHOST_USER_SET_SLAVE_REQ_FD
VHOST_CONFIG: read message VHOST_USER_SET_OWNER
VHOST_CONFIG: read message VHOST_USER_GET_FEATURES
VHOST_CONFIG: read message VHOST_USER_SET_VRING_CALL
VHOST_CONFIG: vring call idx:0 file:48
VHOST_CONFIG: read message VHOST_USER_SET_VRING_CALL
VHOST_CONFIG: vring call idx:1 file:49
VHOST_CONFIG: read message VHOST_USER_SET_VRING_ENABLE
VHOST_CONFIG: set queue enable: 1 to qp idx: 0
VHOST_CONFIG: read message VHOST_USER_SET_VRING_ENABLE
VHOST_CONFIG: set queue enable: 1 to qp idx: 1
VHOST_CONFIG: read message VHOST_USER_SET_FEATURES
VHOST_CONFIG: negotiated Virtio features: 0x7000ffc3

ネゴシエーション内容の確認

先ほどのログをみていく。

$ hexdump vhost-net0.log
0000000 0001 0000 0001 0000 0000 0000 000f 0000
0000010 0001 0000 0000 0000 0010 0000 0001 0000
0000020 0008 0000 0cbf 0000 0000 0000 0011 0000
0000030 0001 0000 0000 0000 0015 0000 0009 0000
0000040 0000 0000 0003 0000 0001 0000 0000 0000
0000050 0001 0000 0001 0000 0000 0000 000d 0000
0000060 0001 0000 0008 0000 0000 0000 0000 0000
0000070 000d 0000 0001 0000 0008 0000 0001 0000
0000080 0000 0000 0012 0000 0001 0000 0008 0000
0000090 0000 0000 0001 0000 0012 0000 0001 0000
00000a0 0008 0000 0001 0000 0001 0000 0002 0000
00000b0 0001 0000 0008 0000 ffc3 7000 0000 0000
00000c0

Vhost-user Protocolはバイナリ形式で、仕様 2 と照らし合わせることで解析できる。 複数の可変長のメッセージが連続したような構造となっており、各メッセージは下記のように ヘッダとペイロードから構成される。

  • ヘッダ(32bitのリクエスト種別、32bitのフラグ、32bitのサイズ)
  • ペイロード(ヘッダのサイズフィールドのサイズに一致)

これを踏まえて、ログを先頭から順番に見ていくと以下のように読み解ける。

  1. VHOST_USER_GET_FEATURES (1) がQEMUからDPDKに対して送られる。フラグは1で、サイズは0。DPDKからのレスポンスは64ビット整数で各ビットがFeatureの有無を意味する。例えばFeatureにはVHOST_USER_F_PROTOCOL_FEATURESが含まれる。
0001 0000 0001 0000 0000 0000
  1. VHOST_USER_GET_PROTOCOL_FEATURES (15) が送られる。レスポンスとして64ビット整数を受け取る。
000f 0000 0001 0000 0000 0000
  1. VHOST_USER_SET_PROTOCOL_FEATURES (16) が送られる。フラグは1で、サイズは8。つまりQEMUがDPDKに対して、ネゴシエーションの結論としてペイロード0xcbfを DPDK に送る。
0010 0000 0001 0000 0008 0000 0cbf 0000 0000 0000

0xcbfの各ビットは下記で解釈できる。

#define VHOST_USER_PROTOCOL_F_MQ                    0
#define VHOST_USER_PROTOCOL_F_LOG_SHMFD             1
#define VHOST_USER_PROTOCOL_F_RARP                  2
#define VHOST_USER_PROTOCOL_F_REPLY_ACK             3
#define VHOST_USER_PROTOCOL_F_MTU                   4
#define VHOST_USER_PROTOCOL_F_BACKEND_REQ           5
#define VHOST_USER_PROTOCOL_F_CROSS_ENDIAN          6
#define VHOST_USER_PROTOCOL_F_CRYPTO_SESSION        7
#define VHOST_USER_PROTOCOL_F_PAGEFAULT             8
#define VHOST_USER_PROTOCOL_F_CONFIG                9
#define VHOST_USER_PROTOCOL_F_BACKEND_SEND_FD      10
#define VHOST_USER_PROTOCOL_F_HOST_NOTIFIER        11
#define VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD       12
#define VHOST_USER_PROTOCOL_F_RESET_DEVICE         13
#define VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS 14
#define VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS  15
#define VHOST_USER_PROTOCOL_F_STATUS               16
  1. VHOST_USER_GET_QUEUE_NUM (17)でバックエンド(DPDK)がどれくらいのキュー数を持つか調べる。
0011 0000 0001 0000 0000 0000
  1. VHOST_USER_SET_BACKEND_REQ_FD (21) が送られる。これは古くはVHOST_USER_SET_SLAVE_REQ_FD と呼ばれていたことに注意する。
0015 0000 0009 0000 0000 0000
  1. VHOST_USER_SET_OWNER (3) が送られる。QEMUをフロントエンドとする。
0003 0000 0001 0000 0000 0000
  1. VHOST_USER_GET_FEATURES (1) が送られる。
0001 0000 0001 0000 0000 0000
  1. VHOST_USER_SET_VRING_CALL (13) が送られる。
000d 0000 0001 0000 0008 0000 0000 0000 0000 0000
  1. 再度VHOST_USER_SET_VRING_CALL (13) が送られる。
000d 0000 0001 0000 0008 0000 0001 0000 0000 0000
  1. VHOST_USER_SET_VRING_ENABLE (18) が送られる。バックエンドであるDPDKに対して vring の有効化を伝える。
0012 0000 0001 0000 0008 0000 0000 0000 0001 0000
  1. VHOST_USER_SET_VRING_ENABLE (18) が送られる。
0012 0000 0001 0000 0008 0000 0001 0000 0001 0000
  1. VHOST_USER_SET_FEATURES (2)が送られる。ペイロードは0x7000ffc3となる。VHOST_USER_F_PROTOCOL_FEATURES (30) などが該当する。
0002 0000 0001 0000 0008 0000 ffc3 7000 0000 0000

終わりに

DPDK付属のサンプルアプリとQEMUを組み合わせることでネゴシエーション処理を追うことができた。 ここまで分かれば gokvm での実装に手をつけられそうだ。