Qemu学习-I/O虚拟化

I/O虚拟化

Qemu/kvm中,Guest OS可以使用的设备大致可分为三类:

  1. 模拟设备:完全由Qemu纯软件模拟的设备,也就是全虚拟化I/O设备。

  2. Virtio设备:实现Virtio API的半虚拟化设备,也就是半虚拟化I/O设备。

  3. PCI设备直接分配(PCI device assignment)

全虚拟化I/O

kvm在虚拟机中只负责有vCPU和内存管理,I/O设备则默认全部交由Qemu用纯软件的方式模拟完成,包括鼠标,键盘,显卡,硬盘,网卡等。

具体原理如下:

img

  1. Guest OSdevice drivers programe发起I/O请求操作请求。
  2. KVM模块中的I/O操作捕获代码拦截这次I/O请求。
  3. 经过处理后将本次I/O请求的信息放到I/O共享页(sharing page,并通知用户空间的QEMU程序。
  4. QEMU程序获得I/O操作的具体信息之后,交由硬件模拟代码来模拟出本次I/O操作。
  5. 完成之后,QEMU将结果放回I/O共享页,并通知KMV模块中的I/O操作捕获代码。
  6. KVM模块的捕获代码读取I/O共享页中的操作结果,并把结果放回客户机。

半虚拟化I/O(Virtio框架)

简述

先说结论,通过之前的学习,我们知道kvm作为User modeGuest mode之前的桥梁,关系如下图:

img

这就导致I/O操作比较繁琐,而Virtio框架使Guest OS可以直接与Host/User mode(Qemu)I/O模块通信,无须kvm中转,从而提高I/O性能,使virtio设备性能趋近于native设备。

其需要在Guest OS中提前安装Front-endQemu中实现Back-endFront-end作为一个内核模块/设备驱动位于Guest OS中,其负责接收客户机用户态程序的I/O请求,并将I/O请求传输给Back-endBack-end作为一个模拟设备位于Qemu中,其负责接收Front-endI/O请求,并通过真实的物理设备执行I/O操作。

img

image.png

virtio_ring

Front-endBack-end是通过vring进行通信的,Guest OS前端驱动程序通过virtqueuehypervisor交互,实现数据的共享。对于I/OGuest OS提供一个或多个表示请求的缓冲池。

vringvirtqueue 的具体实现方式,在hostguest操作系统之间作内存映射,针对vring会有相应的描述符表格进行描述。
virtio_ring(实现虚拟队列的环形缓冲区)virtio传输机制的实现,vring 引入ring buffers(环形缓冲区)来作为数据传输的载体。

virtqueue实现

从结构上看,virtio_ring包含3部分:

  • Descriptor Table
  • Available Ring
  • Used Ring

Descriptor Table(描述符数组)

描述符数组(descriptor table)用于存储真正的buffer,每个描述符都是一个对buffer的描述,包含一个address/length的配对、下个buffer的指针、两个标志位(下个buffer是否有效和当前buffer是可读/写)。
每个buffer在内部被表示为一个散集列表(scatter-gather),列表中的每个条目表示一个地址和一个长度。
每个条目的结构体为struct vring_descdesc table数组大小为Queue Size

1
2
3
4
5
6
7
8
9
10
11
12
/* include/standard-headers/linux/virtio_ring.h */
/* 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;
};

Available ring

Available ring用于Guest端表示哪些描述符链(descriptor chain)当前是可用的。
Available ringdriver写,device读取。

1
2
3
4
5
6
/* include/standard-headers/linux/virtio_ring.h */
struct vring_avail {
__virtio16 flags;
__virtio16 idx;
__virtio16 ring[];
};

flags:用于指示Host当它处理完buffer,将Descriptor index写入Used Ring之后,是否通过注入中断通知Guest

  • 如果flags设置为0Host每处理完一次buffer就会中断通知Guest

  • 如果flags为1,不通知Guest。 如果VIRTIO_F_EVENT_IDX该特性开启,那么flags的意义将会改变,Guest必须把flags设置为0,然后通过used_event机制实现通知。

idx : 指示Guest下一次添加buffer时,在ring[]中取的位置,从0开始。
ring[]:是一个大小为Queue Size的数组,entry是存放Descriptor Table Chainhead
used_event用作Event方式通知机制,此值用于控制Guestvirtqueue数据发送速度,是Qemu处理avail ring后的last_avail_idx

Used ring

used ring表示device记录哪些描述符链已经使用。
used ringdevice写,driver读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* include/standard-headers/linux/virtio_ring.h */
struct vring_used {
__virtio16 flags;
__virtio16 idx;
struct vring_used_elem ring[];
};

/* uint32_t 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;
};

每个used ring中条目struct virtq_used_elemid指定了在descriptor chain中的head条目,而len指定了要写入buffer的字节数。
struct virtq_used 中的flags用于指示Guest当它添加完buffer,将Descriptor index写入Avail Ring之后,是否发送notification通知Host

  • 如果flags设置为0,Guest每增加一次buffer就会通知Host

  • 如果flags为1,不通知Host

VIRTIO_F_EVENT_IDX 特性开启时,flags必须被设置成0,Guest使用avail_event方式通知Host
idx 用于指示Host下一次处理的bufferUsed Ring所的位置。
ring[]:是一个大小为Queue Size的数组,entry是存放Descriptor Table Chainhead
avail_event用作Event方式通知机制。

1
2
3
4
5
/* include/standard-headers/linux/virtio_ring.h */
/* We publish the used event index at the end of the available ring, and vice
* versa. They are at the end for backwards compatibility. */
#define vring_used_event(vr) ((vr)->avail->ring[(vr)->num])
#define vring_avail_event(vr) (*(__virtio16 *)&(vr)->used->ring[(vr)->num])

vring的实现

virtqueue_pop()virtqueue_push()virtqueue_fill()virtqueue_flush()函数代码分析

to do…

virtio数据传输

https://blog.csdn.net/huang987246510/article/details/103708461#_2

驱动程序代码flow

virtio有分为guest中的前端程序和Qemu中的后端程序。
virtio中有五种前端程序:

  • virtio-blk(块设备驱动程序):/hw/block/virtio-blk.c
  • virtio-net(网络设备驱动程序):/hw/net/virtio-net.c
  • virtio-pci(PCI设备驱动程序):/hw/virtio/virtio-pci.c
  • virtio-balloon(气球驱动程序,动态管理客户机内存使用情况):/hw/virtio/virtio-balloon.c
  • virtio-console(控制台驱动程序):/hw/char/virtio-console.c

其继续往下调用为Transport过程/hw/virtio/virtio.c,然后进入Back-end

Guest OS中,在不使用virtio设备的时候,这些驱动不会被加载。只有在使用某个virtio设备的时候,对应的驱动才会被加载。每个前端驱动器具有在管理程序中的相应的后端的驱动程序。

virtioflowguest => qemu => host kernel => hw

参考

https://www.cnblogs.com/sammyliu/p/4543657.html

https://juniorprincewang.github.io/2018/03/01/virtio%E5%AD%A6%E4%B9%A0/

打赏还是打残,这是个问题