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 qi