http://git.qemu.org/git/qemu.git v2.8.0

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git 4.4.70

1.VRing的初始化

QEMU下的VRing

VirtQueue在QEMU端的初始化,即virtio_add_queue, virtio最多有1024个虚拟队列,且每个队列最多容纳1024个request单位

QEMU初始化了vring.num和handle_output.

回到guest中的virtio驱动, vm_setup_vq负责建立和qemu对应的virtqueue,

上条代码帮助guest获取了vring.num, vring的内存从guest memory中申请:

vring_new_virtqueue初始化了guest的结构体vring_virtqueue

其中vring_size可以看出vring的组成形式

再次回到QEMU,在virtio_mmio_write下,

在virtio_queue_set_addr中, vdev->vq[n].vring.desc = addr,看到vring.desc是vring的desc部分的头指针也即是vring的gpa头指针.

在virtio_queue_update_rings中则分别更新了vring->avail和vring->used的gpa指针

vring的布局如上,此刻基本的初始化已经完成.

2.Guest对vring的写

guest添加request到vring上的common函数是virtqueue_add, 前后的START_USE(vq)和END_USE(vq), 是debug用的引用计数器, 即vq不可同时有两个request请求.

vq->indirect是由VIRTIO_RING_F_INDIRECT_DESC决定的, 是指request数据是否可以放到guest memory再申请的内存中,然后将指针传递给vring中的desc单元:

如果vring空闲的desc不满足申请的数目, flush virtqueue之后报错.

将sgs中的数据copy到desc空间中

desc读写的流向是由desc[i].flags & VRING_DESC_F_WRITE决定的

desc是否结束是通过desc[i].flags & VRING_DESC_F_NEXT决定的, 有值则继续.

上一次的写入vring的index保存在vring_avail中

当vring的更新次数达到64k后, flush vring内容到QEMU

前面提到-ENOSPC返回值也会触发virtqueue_kick.

在virtqueue_kick下的virtqueue_kick_prepare中,

当前代码中vq->event恒定为1, 即支持virtio速度控制VIRTIO_RING_F_EVENT_IDX功能

先看 vring_avail_event(&vq->vring)

直接读取了used ring的最后一个单元的值, 而在QEMU内, 最后一个值填充的是当前正在处理的last_avail_idx

vring_avail_event返回的即是virtio后端正在pop的last_avail_idx

上图满足条件为真,表示virtio后端已经处理完上一次kick提交的request, 处理速度还可以,此刻前端不需要等待直接kick即可.

参考刘峰同学:http://blog.csdn.net/leoufung/article/details/53584970

3. QEMU对vring的出栈

回到QEMU

virtqueue_pop中, 先判断vq->inuse >= vq->vring.num, 从avail ring里面获取desc的index

virtqueue_get_head(vq, vq->last_avail_idx++, &head)

如果支持VIRTIO_RING_F_EVENT_IDX,则将last_avail_idx保存到VRingUsed末尾,上节已经提到.

vring_desc_read读取head对应的desc并解析, 并处理是否是间接索引VRING_DESC_F_INDIRECT.

virtqueue_map_desc将desc.addr和desc.len映射给VirtQueueElement, 此处有elem->index = head, elem->index是取自vring的desc的index. 然后vq->inuse++;

当virtio device完成具体任务时,virtqueue_push会被调用,

在virtqueue_fill下,virtqueue_unmap_sg解除了virtqueue_map_desc做的映射关系, 将elem->index和len填写到VRingUsedElem, 最终使用vring_used_write写入到VRingUsed.

然后回到virtqueue_flush,更新了VRingUsed idx:

最终virtio_notify发送一个虚拟中断给guest进行通知.

4. guest 对virtio 中断的处理

guest kernel在vp_try_to_find_vqs中选择调用vp_request_intx或vp_request_msix_vectors, 在vp_request_intx中会使用request_irq注册中断,中断处理函数就是vp_interrupt.

从vp_interrupt->vp_vring_interrupt->vring_interrupt一层层调用,最终执行vq->vq.callback(&vq->vq).

而vq->vq.callback是在vring_new_virtqueue中初始化的:vq->vq.callback = callback.

从vring_new_virtqueue<-setup_vq<-vp_setup_vq<-vp_try_to_find_vqs<-vp_find_vqs<-vp_modern_find_vqs<-virtio_config_ops.find_vqs依次往上走

就会看到调用find_vqs的函数下callbacks对应的有virtblk_done, balloon_ack, control_intr, 或者virtscsi_req_done.

 


virtIO vring工作机制分析来自于OenHan

链接为:http://oenhan.com/virtio-vring

发表评论