FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

VirtIOのドキュメントを読む (2)

https://blogs.oracle.com/linux/post/introduction-to-virtio

SNS界隈で見つけて、面白そうなので読んでみることにした。

VHostについて

これまではVHostという言葉は登場しなかったが、ここで説明しておく必要がある。

パフォーマンスの問題が発生から出てきた機能。

  • ドライバがホストに物理ハードウェアで何らかの処理を実行するように要求するたびに、QEMUコンテキストスイッチが発生する。
  • データプレーンを別のホストユーザプロセスまたはそのカーネルにオフロードする。
    • これにより、QEMUのプロセスをバイパスし、レイテンシを削減してパフォーマンスを向上させる。
    • パフォーマンスが向上する代わりに、セキュリティ上の問題が発生する可能性がある。
    • 図を見る限り、QEMUを介さずに、ホストのユーザプロセスと直接通信をするということ?
      • VirtIOのバックエンドが不要となるということか。
https://blogs.oracle.com/content/published/api/v1.1/assets/CONT4FCF86F243514632B4117AD41E683DC4/Medium?cb=_cache_2f34&format=jpg&channelToken=3189ef66cf584820b5b19e6b10792d6f
https://blogs.oracle.com/content/published/api/v1.1/assets/CONTAFDE3146FCA448DCBFF0E83C24119D25/Medium?cb=_cache_2f34&format=jpg&channelToken=3189ef66cf584820b5b19e6b10792d6f

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)
    • virtio-SCSIバイスの起動、シャットダウン、リセットなどのタスク管理機能(TMF)
    • 非同期通知のサブスクライブとクエリ
  • イベント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_cmdvirtio_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;
}