接前一篇文章:VirtIO实现原理之数据结构与数据传输演示(2)
本文内容参考:
特此致谢!
一、数据结构总览
2. 相关数据结构
前文书介绍了《Virtual I/O Device (VIRTIO) Version 1.3》规范的“2.6 Virtqueues”一节中提到的相关数据结构。再来回顾一下上一回的那3个结构:
为了便于理解,这里还是以老版本进行讲解。先来看一下VirtIO数据结构的总览图:
(2)可用环(Available Ring)
存放Decriptor Table索引,指向Descriptor Table中的一个entry。当Guest Driver向Vring中添加buffer时,可以一次添加一个或多个buffer,所有buffer组成一个Descriptor chain(就是Descriptor Table),Guest Driver添加buffer成功后,需要将Descriptor chain头部的地址记录到Avail Ring中,让Host端能够知道新的可用的buffer是从VRing的哪个地方开始的。
Host查找Descriptor chain头部地址,需要经过两次索引Buffer Adress = Descriptor Table[Avail Ring[last_avail_idx]],last_avail_idx是Host端记录的Guest上一次增加的buffer在Avail Ring中的位置。Guest Driver每添加一次buffer,就将Avail Ring的idx加1,以表示自己工作在Avail Ring中的哪个位置。Avail Ring是Guest维护,提供给Host用。
可用环(Available Ring)对应的结构为struct vring_avail。struct vring_avail的定义在QEMU源码中有不止一处(qemu-8.1.4版本一共3处),这里以roms/u-boot/include/virtio_ring.h中的为例,代码如下:
struct vring_avail {
__virtio16 flags;
__virtio16 idx;
__virtio16 ring[];
};
Guest通过Avail Ring向Host提供buffer,指示Guest增加的buffer位置和当前工作的位置。
成员说明
- __virtio16 flags
用于指示Host当它处理完buffer,将Descriptor index写入Used Ring之后,是否通过注入中断通知Guest。如果flags为0,Host每处理完一次buffer就会中断通知Guest,从而触发VMExit,增加开销;如果flags为1,不通知Guest。这是一种比较粗糙的方式,要么不通知,要么通知。还有一种比较优雅的方式,叫做VIRTIO_F_EVENT_IDX特性,它根据前后端的处理速度,来判断是否进行通知。如果该特性开启,那么flags的意义将会改变,Guest必须把flags设置为0,然后通过used_event机制实现通知。
- __virtio16 idx
指示Guest下一次添加buffer时的在Avail Ring所处的位置,换句话说,idx存放的是ring数组索引,ring[idx]存放的才是下一次添加的buffer头在Descriptor Table的位置。
- __virtio16 ring[]
存放Descriptor Table索引的环,是一个数组,长度是队列深度加1个,其中最后一个用作Event方式通知机制。VirtIO实现了两级索引(Buffer Adress = Descriptor Table[Avail Ring[last_avail_idx]]):一级索引指向Descriptor Table中的元素,Avail Ring和Used Ring代表的是一级索引,核心就是这里的ring[]数组成员;二级索引指向buffer的物理地址,Descriptor Table是二级索引。
至此,第2个数据结构——可用环(Available Ring)对应的vring_avail结构就讲解完了。下一回继续讲解更多数据结构。