赞
踩
数据结构的介绍,我们参照的virtio spec的定义(参考Virtual I/O Device Version 1.0 spec 第二章virtqueues介绍),围绕下面的示意图进行介绍,介绍层次从下到上,从最接近数据buffer到最接近内核块设备进行。有些数据结构初次看可能不是很理解,可以先记住一些概念,后面会通过VirtIO数据传输的流程演示和代码分析,详细解释这些数据结构怎么用,以此加深理解。
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 Rring是Guest维护,提供给Host用/* Virtio ring descriptors: 16 bytes. These can chain together via "next". */
struct vring_desc {
/* Address (guest-physical). */
__virtio64 addr;
/* Length. */
__virtio32 len;
/* The flags as indicated above. */
__virtio16 flags;
/* We chain unused descriptors via this, too */
__virtio16 next;
};
addr:数据的物理地址
len:数据的长度
flags:标记数据对于Host是可读还是可写,如果buffer用于发送数据,对Host只读,否则,对Host只写。解释如下:
/* This marks a buffer as continuing via the next field.
* 表示该buffer之后还有buffer,所有buffer可以通过next连成一个Descriptor chain
*/
#define VRING_DESC_F_NEXT 1
/* This marks a buffer as write-only (otherwise read-only).
* 表示该buffer只能写,当buffer用于接收数据时,需要向Host提供buffer,这个时候就标记buffer为写。反之是发送数据,标记为读
*/
#define VRING_DESC_F_WRITE 2
/* This means the buffer contains a list of buffer descriptors.
* 不做讨论
*/
#define VRING_DESC_F_INDIRECT 4
next:存放下一个buffer在Descriptor Table的位置。
注意,next不是存放的物理地址,通过其类型不难判断,next是存放的下一个buffer在Descriptor Table的索引
struct vring_avail {
__virtio16 flags;
__virtio16 idx;
__virtio16 ring[];
};
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机制实现通知。used_event机制会在后面进行介绍。
idx
:指示Guest下一次添加buffer时的在Avail Ring所处的位置,换句话说,idx存放的ring[]
数组索引,ring[idx]
存放才是下一次添加的buffer头在Descriptor Table的位置。
ring
:存放Descriptor Table索引的环,是一个数组,长度是队列深度加1个。其中最后一个用作Event
方式通知机制,见下图。VirtIO实现了两级索引,一级索引指向Descriptor Table中的元素,Avail Ring和Used Ring代表的是一级索引,核心就是这里的ring[]
数组成员。二级索引指向buffer的物理地址,Descriptor Table是二级索引
struct vring_used {
__virtio16 flags;
__virtio16 idx;
struct vring_used_elem ring[];
};
/* u32 is used here for ids for padding reasons. */
struct vring_used_elem {
/* Index of start of used descriptor chain. */
__virtio32 id;
/* Total length of the descriptor chain which was used (written to) */
__virtio32 len;
};
flags
:用于指示Guest当它添加完buffer,将Descriptor index写入Avail Ring之后,是否发送notification通知Host。如果flags设置为0,Guest每增加一次buffer就会通知Host,如果flags为1,不通知Host。Used Ring flags的含义和Avail Ring flags的含义类似,都是指示前后端数据处理完后是否通知对方。同样的,当VIRTIO_F_EVENT_IDX
特性开启时,flags必须被设置成0,Guest使用avail_event方式通知Host
idx
:指示Host下一次操作的buffer在Used Ring所的位置
ring
:存放Descriptor Table索引的环。意义和Avail Ring中的ring类似,都是存放指向Descriptor Table的索引。但Used Ring不同的是,它的元素还增加了一个len字段,用来表示Host在buffer中处理了多长的数据。这个字段在某些场景下有用。这里不做介绍。
struct vring {
/* VRing的队列深度,表示一个VRing有多少个buffer */
unsigned int num;
/* 指向Descriptor Table */
struct vring_desc *desc;
/* 指向Avail Ring */
struct vring_avail *avail;
/* 指向Used Ring */
struct vring_used *used;
};
/** * virtqueue - a queue to register buffers for sending or receiving. * @list: the chain of virtqueues for this device * @callback: the function to call when buffers are consumed (can be NULL). * @name: the name of this virtqueue (mainly for debugging) * @vdev: the virtio device this queue was created for. * @priv: a pointer for the virtqueue implementation to use. * @index: the zero-based ordinal number for this queue. * @num_free: number of elements we expect to be able to fit. * * A note on @num_free: with indirect buffers, each buffer needs one * element in the queue, otherwise a buffer will need one element per * sg element. */ struct virtqueue { struct list_head list; void (*callback)(struct virtqueue *vq); const char *name; struct virtio_device *vdev; unsigned int index; unsigned int num_free; // virtqueue中剩余的buffer数量,初始化时该大小是virtqueue深度 void *priv; };
1: libvirt配置
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' queues='4'/>
...
</disk>
2: qemu配置
-device virtio-blk-pci,num-queues=4...
VIRTIO_BLK_F_MQ
特性,表示后端支持多队列,如果前端guest驱动也支持多队列,那么多队列可以设置成功,如果前端驱动不支持多队列特性,那么队列会回退到默认值1if (s->conf.num_queues > 1) {
virtio_add_feature(&features, VIRTIO_BLK_F_MQ); // 添加多队列特性
}
static Property virtio_blk_properties[] = {
DEFINE_PROP_UINT16("num-queues", VirtIOBlock, conf.num_queues, 1) // virtio-blk默认队列数为1
...
}
成功设置磁盘多队列之后,虚拟机内部查看如下:
多队列可以提高IO性能,libvirt的官方推荐配置是多队列个数与vcpu个数相同,让每个vcpu可以处理一个队列,当虚拟机IO压力大的时候,IO数据可以平均到各个队列分别让每个cpu单独处理,从而提高传输效率
struct vring_virtqueue { struct virtqueue vq; /* 1 */ /* Actual memory layout for this queue */ struct vring vring; /* 2 */ /* Can we use weak barriers? */ bool weak_barriers; /* Other side has made a mess, don't try any more. */ bool broken; /* Host supports indirect buffers */ bool indirect; /* Host publishes avail event idx */ bool event; /* 3 */ /* Head of free buffer list. */ unsigned int free_head; /* 4 */ /* Number we've added since last sync. */ unsigned int num_added; /* 5 */ /* Last used index we've seen. */ u16 last_used_idx; /* Last written value to avail->flags */ u16 avail_flags_shadow; /* Last written value to avail->idx in guest byte order */ u16 avail_idx_shadow; /* 6 */ /* How to notify other side. FIXME: commonalize hcalls! */ bool (*notify)(struct virtqueue *vq); ...... };
1. 设备看到的VRing
2. 实现数据传输的VRing结构
3. 是否开启Event通知机制
4. 当前Descriptor Table中空闲buffer的起始位置
5. 上一次通知Host后,Guest往VRing上添加了多少次buffer,每添加一次buffer,num_added加1,每kick一次Host清空
6. Guest每添加一次buffer,avail_idx_shadow加1,每删除一次buffer,avail_idx_shadow减1
7. virtio队列通知后端的具体实现,初始化队列的时候注册,对于基于pci总线的virtio队列,对应的实现为vp_notify
typedef struct VirtQueueElement
{
unsigned int index; /* 1 */
unsigned int len;
unsigned int ndescs; /* 2 */
unsigned int out_num; /* 3 */
unsigned int in_num; /* 4 */
hwaddr *in_addr; /* 5 */
hwaddr *out_addr; /* 6 */
struct iovec *in_sg; /* 7 */
struct iovec *out_sg; /* 8 */
} VirtQueueElement;
1. 当前元素在描述符表取描述符时的起始索引
2. 当前元素包含的总描述符个数
3. 当前元素中包含的发送描述符个数,即descriptor table entry个数
4. 当前元素中包含的接受描述符个数
5. 接受buffer的起始虚机物理地址GPA
6. 发送buffer的起始虚机物理地址
7. 接受buffer对应的主机虚拟地址HVA,由qemu从GPA转换而来
8. 发送buffer对应的主机虚拟地址
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。