接前一篇文章: QEMU源码全解析 —— 块设备虚拟化(14)
本文内容参考:
《 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启动过程中的块设备虚拟化。