virtio 是一种 I/O 半虚拟化解决方案,是一套通用 I/O 设备虚拟化的程序,是对半虚拟化 Hypervisor 中的一组通用 I/O 设备的抽象。对比其他设备有宿主计算机模拟,virtio设备效率更高。
virtio架构图:

最上面一排是不同的设备,如块设备,网络设备,控制台等
virtio 层属于控制层,负责设备跟宿主OS之间的通知机制(kick,notify)和控制流程,而 virtio-vring 则负责具体数据流转发。
vring 包含三个部分,描述符数组 desc,可用的 available ring 和使用过的 used ring。
- struct vring_desc {
- /* Address (guest-physical). */
- u64 addr;
- /* Length. */
- u32 len;
- /* The flags as indicated above. */
- u16 flags;
- /* We chain unused descriptors via this, too */
- u16 next;
- };
vring描述符结构。
- struct vring_avail {
- u16 flags;
- u16 idx;
- u16 ring[];
- };
- struct vring_used_elem {
- /* Index of start of used descriptor chain. */
- u32 id;
- /* Total length of the descriptor chain which was used (written to) */
- u32 len;
- };
-
- struct vring_used {
- u16 flags;
- u16 idx;
- struct vring_used_elem ring[];
- };
avail跟used结构。
- static inline void vring_init(struct vring *vr, unsigned int num, void *p,
- unsigned long align)
- {
- vr->num = num;
- vr->desc = p;
- vr->avail = (void *)((char *)p + num*sizeof(struct vring_desc));
- vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + sizeof(u16)
- + align-1) & ~(align - 1));
- }
-
- static inline unsigned vring_size(unsigned int num, unsigned long align)
- {
- return ((sizeof(struct vring_desc) * num + sizeof(u16) * (3 + num)
- + align - 1) & ~(align - 1))
- + sizeof(u16) * 3 + sizeof(struct vring_used_elem) * num;
- }
-
vring_size计算vring需要的内存大小,num是vring的描述符数量,为2^N,如128,256,512等。
首先放的是num个描述符vring_desc,然后是vring_avail,在vring_avail结尾有个16字位的used_event,因此是是(3+num)。之后放得是vring_used,同样,后面有个16位的avail_event,因此也是sizeof(u16) * 3。
vring_used需要4K对齐。
used_event用来通知宿主os,读取到哪里了。avail_event用来通知宿主os,写到哪里了。
设置avail_event,用来通知宿主os有新命令需要处理。宿主os完成之后产生中断,处理完成之后设置used_event,表示数据处理过了,这样宿主os才会有数据的时候继续发生中断。
desc 用于存储一些关联的描述符,每个描述符记录一个对 buffer 的描述,available ring 则用于 guest 端表示当前有哪些描述符是可用的,而 used ring 则表示 host 端哪些描述符已经被使用。
Virtio 使用 virtqueue 来实现 I/O 机制,每个 virtqueue 就是一个承载大量数据的队列,具体使用多少个队列取决于需求,例如,virtio 网络驱动程序(virtio-net)使用两个队列(一个用于接受,另一个用于发送),而 virtio 块驱动程序(virtio-blk)仅使用一个队列。
比如读取硬盘。取得virtqueue队列,virtio-blk就一个队列。然后往里面添加3条结构化数据。
- struct addr_size {
- unsigned long vp_addr; /* 物理地址 */
- u32 vp_size; /* 大小 */
- u32 vp_flag; /*标记,如读,写*/
- };
第一条是请求命令数据:
- struct blk_outhdr {
- /* VIRTIO_BLK_T* */
- u32 type;
- /* io priority. */
- u32 ioprio;
- /* Sector (ie. 512 byte offset) */
- u64 sector;
- };
告诉驱动是读还是写(type),优先级,扇区号。
第二条是输出缓冲区,即扇区读取到哪里,缓冲区大小。
第三条就1个字节,用来指示操作结果,0表示成功。
module/blk/virtio_blk.c
- struct blk_req {
- struct blk_outhdr hdr;
- uchar status;
- };
- static struct blk_req rq;
- static void virtio_blk_read(ulong sector)
- {
- struct addr_size phys[3];
- rq.hdr.type = VIRTIO_BLK_T_IN;
- rq.hdr.ioprio = 0;
- rq.hdr.sector = sector;
- phys[0].vp_addr = V2P((ulong)&rq.hdr);
- phys[0].vp_size = sizeof(rq.hdr);
- phys[0].vp_flag = VRING_DESC_F_READ;
- phys[1].vp_addr = V2P((ulong)buf);
- phys[1].vp_size = 512;
- phys[1].vp_flag = VRING_DESC_F_WRITE;
- uchar status = 0;
- phys[2].vp_addr = V2P((ulong)&rq.status);
- phys[2].vp_size = sizeof(status);
- pyhs[2].vp_flag = VRING_DESC_F_WRITE;
- virtio_to_queue(to_virtio_dev_t(&pci_vblk), 0, phys, 3, &rq);
-
- }
virtio_to_queue把3个描述符写入vring,然后kick通知宿主os。
libs/libvirtio/virtio.c
- int
- virtio_to_queue(virtio_dev_t dev, int q