https://blogs.oracle.com/linux/post/introduction-to-virtio
SNS界隈で見つけて、面白そうなので読んでみることにした。
VHostについて
これまではVHostという言葉は登場しなかったが、ここで説明しておく必要がある。
パフォーマンスの問題が発生から出てきた機能。
- ドライバがホストに物理ハードウェアで何らかの処理を実行するように要求するたびに、QEMUでコンテキストスイッチが発生する。
- データプレーンを別のホストユーザプロセスまたはそのカーネルにオフロードする。
QEMUのVirtIO
- VirtIOデバイスが大まかにどのように機能するかを確認する。
- 標準のVirtIOデバイスでのVirtQueueとVRingsの機能を確認する。
- virtio-SCSIが分割されたVirtQueueコンフィグレーションとVIRTIO_VRING_F_EVENT_IDX機能ビットが通信を行っている様子を観察する。
Virtio-SCSI
- ハードディスクドライブなどの仮想論理ユニットをグループ化するために使用される。
-device virtio-scsi-pci -device scsi-hd,drive=hd0,bootindex=0 -drive file=/home/qemu-imgs/test.img,if=none,id=hd0
hw/scsi/virtio-scsi.c
がデバイスの動作に関する機能を実装している。
- realizeという意味は、VirtIOデバイスの初期セットアップとコンフィグレーションを示すために使用される。
- unrealizeという言葉はデバイスを破棄するために使用される。
- virtio_scsi_common_realize()では、3つのVirtQueueを作成していることが見て取れる。
// In hw/scsi/virtio-scsi.c void virtio_scsi_common_realize(DeviceState *dev, VirtIOHandleOutput ctrl, VirtIOHandleOutput evt, VirtIOHandleOutput cmd, Error **errp) { ... s->ctrl_vq = virtio_add_queue(vdev, s->conf.virtqueue_size, ctrl); s->event_vq = virtio_add_queue(vdev, s->conf.virtqueue_size, evt); for (i = 0; i < s->conf.num_queues; i++) { s->cmd_vqs[i] = virtio_add_queue(vdev, s->conf.virtqueue_size, cmd); } }
- コントロールVirtQueue (
ctrl_vq
) - イベントVirtQueue (
event_vq
)- virtio-SCSIに接続されたホストからの情報(イベント)を報告するために使用される
- コマンド・リクエストVirtQueue (
cmd_vqs
)- 一般的なSCSIトランスポートコマンドに使用される
コマンドVirtQueue
ファイルの読み取りや書き込みなどの一般的なSCSIトランスポートコマンドを扱うためのVirtQueueである。
- コールバック関数を設定することができる(下記の例では
virtio_scsi_handle_cmd
)
// In hw/scsi/virtio-scsi.c static void virtio_scsi_device_realize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VirtIOSCSI *s = VIRTIO_SCSI(dev); Error *err = NULL; virtio_scsi_common_realize(dev, virtio_scsi_handle_ctrl, virtio_scsi_handle_event, virtio_scsi_handle_cmd, <----* &err); ... } // In hw/virtio/virtio.c VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size, VirtIOHandleOutput handle_output) { ... vdev->vq[i].vring.num = queue_size; vdev->vq[i].vring.num_default = queue_size; vdev->vq[i].vring.align = VIRTIO_PCI_VRING_ALIGN; vdev->vq[i].handle_output = handle_output; // ここの部分 vdev->vq[i].used_elems = g_malloc0(sizeof(VirtQueueElement) * queue_size); return &vdev->vq[i]; }
virtio_scsi_handle_cmd
はvirtio_scsi_handle_cmd_vq()
のラッパーとなっている。
// In hw/scsi/virtio-scsi.c // virtio_scci_handle_cmd()の本体 bool virtio_scsi_handle_cmd_vq(VirtIOSCSI *s, VirtQueue *vq) { VirtIOSCSIReq *req, *next; int ret = 0; bool suppress_notifications = virtio_queue_get_notification(vq); bool progress = false; QTAILQ_HEAD(, VirtIOSCSIReq) reqs = QTAILQ_HEAD_INITIALIZER(reqs); do { if (suppress_notifications) { virtio_queue_set_notification(vq, 0); } while ((req = virtio_scsi_pop_req(s, vq))) { progress = true; ret = virtio_scsi_handle_cmd_req_prepare(s, req); if (!ret) { QTAILQ_INSERT_TAIL(&reqs, req, next); } else if (ret == -EINVAL) { /* The device is broken and shouldn't process any request */ while (!QTAILQ_EMPTY(&reqs)) { ... } } } if (suppress_notifications) { virtio_queue_set_notification(vq, 1); } } while (ret != -EINVAL && !virtio_queue_empty(vq)); QTAILQ_FOREACH_SAFE(req, &reqs, next, next) { virtio_scsi_handle_cmd_req_submit(s, req); } return progress; }