QEMU源码全解析 —— 块设备虚拟化(15)

接前一篇文章: QEMU源码全解析 —— 块设备虚拟化(14)

本文内容参考:

《趣谈 Linux操作系统 》 —— 刘超, 极客时间

QEMU /KVM源码解析与应用》 —— 李强,机械工业出版社

特此致谢!

QEMU初始化阶段的块设备虚拟化

本回解析virtio_blk_device_realize函数中的virtio_add_queue函数,相关代码片段如下:

    for (i = 0; i < conf->num_queues; i++) {
        virtio_add_queue(vdev, conf->queue_size, virtio_blk_handle_output);
    }

virtio_add_queue函数在hw/virtio/virtio.c中,代码如下:

VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size,
                            VirtIOHandleOutput handle_output)
{
    int i;

    for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
        if (vdev->vq[i].vring.num == 0)
            break;
    }

    if (i == VIRTIO_QUEUE_MAX || queue_size > VIRTQUEUE_MAX_SIZE)
        abort();

    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_new0(VirtQueueElement, queue_size);

    return &vdev->vq[i];
}

在每个VirtQueue中,都有一个vring,用来维护这个队列里边的数据。这一点也可以从VirtQueue结构的定义中印证。VirtQueue结构的定义在hw/virtio/virtio.c中,如下:

struct VirtQueue
{
    VRing vring;
    VirtQueueElement *used_elems;

    /* Next head to pop */
    uint16_t last_avail_idx;
    bool last_avail_wrap_counter;

    /* Last avail_idx read from VQ. */
    uint16_t shadow_avail_idx;
    bool shadow_avail_wrap_counter;

    uint16_t used_idx;
    bool used_wrap_counter;

    /* Last used index value we have signalled on */
    uint16_t signalled_used;

    /* Last used index value we have signalled on */
    bool signalled_used_valid;

    /* Notification enabled? */
    bool notification;

    uint16_t queue_index;

    unsigned int inuse;

    uint16_t vector;
    VirtIOHandleOutput handle_output;
    VirtIODevice *vdev;
    EventNotifier guest_notifier;
    EventNotifier host_notifier;
    bool host_notifier_enabled;
    QLIST_ENTRY(VirtQueue) node;
};

可以看到,struct VirtQueue的第1个成员就是VRing vring。VRing结构的定义在hw/virtio/virtio.c中,如下:

typedef struct VRing
{
    unsigned int num;
    unsigned int num_default;
    unsigned int align;
    hwaddr desc;
    hwaddr avail;
    hwaddr used;
    VRingMemoryRegionCaches *caches;
} VRing;

这里重点关注struct VirtQueue中的成员VirtIOHandleOutput handle_output。它对应的实参为virtio_blk_handle_output,即handle_output指向的是virtio_blk_handle_output函数。这里留个伏笔,后文书还会用到这个virtio_blk_handle_output函数。现在只要大致了解它是用于处理数据写入的就可以了。

​    for (i = 0; i < conf->num_queues; i++) {
        virtio_add_queue(vdev, conf->queue_size, virtio_blk_handle_output);
    }

VirtIODevice、VirtQueue、VRing之间的关系如下图所示:

至此,QEMU初始化阶段的块设备虚拟化就讲完了。下一回开始,讲解QEMU启动过程中的块设备虚拟化。