DPDK系列之十五:Virtio技术分析之一,virtio基础架构

一、前言

KVM可以使用Intel VT-x或者AMD-V虚拟化辅助技术,提高CPU虚拟化的效率;使用支持Intel EPT的平台上,可以提高内存虚拟化的效率;但是对于IO虚拟化,传统的方式是KVM使用qemu来模拟IO设备(网卡、磁盘、显卡等),其效率比较低下。而通过paravirtualization技术,例如virtio,可以进一步提升IO虚拟化的性能。

转载自https://blog.csdn.net/cloudvtech

二、qemu全虚拟化工作模式

在qemu全虚拟化的模式下,当客户机的设备驱动发起IO请求时,KVM会捕获这次IO请求,进行初步处理之后放入将IO请求放入KVM和qemu的共享内存页,然后通知用户空间的qemu进程;用户空间qemu进程会从内核中读取这个IO请求,由硬件模拟模块模拟这个IO操作;qemu的硬件模拟模块会根据IO请求的不同,跟不同的真实物理设备驱动进行交互,完成真正的IO操作(比如通过物理网卡访问外部网络),并将结果放回共享内存页,通知KVM的IO处理模块;KVMIO处理模块读取处理结果并返回给客户机设备驱动。

qemu全虚拟化方式处理IO,每次IO操作都要发送多次VMExit/VMEnrty、上下文切换、数据复制,性能较差。

DPDK系列之十五:Virtio技术分析之一,virtio基础架构_第1张图片

转载自https://blog.csdn.net/cloudvtech

三、virtio工作模式

virio的基本概念是让客户机感知自己正处于虚拟化环境中,这时候虚拟机可以基于virio标准与宿主机进行协作以提高IO虚拟化的效率。

DPDK系列之十五:Virtio技术分析之一,virtio基础架构_第2张图片


virtio在部署的时候由前端、后端组成和virt-queue组成。

3.1 前端初始化

前端包括virio为了满足Linux设备模型(总线、设备、驱动)而模拟的virtio pci bus、virtio pci driver和virtio pci device(包括virtio-net、virtio-blk等如上图所示)。当使用qemu启动VM并在命令行传入使用virtio-net类型设备的时候,qemu会对virtio-net设备进行初始化并在qemu虚拟PCI中加入这个virtio设备的初始化信息。在虚拟机内操作系统启动的时候,会枚举到该virtio设备并分配相应资源。在/sys/bus/目录下创建了一个新的目录virtio,在该目录下同时创建了两个文件夹为devices和drivers。表示创建virtio总线,总线支持设备与驱动,在/sys/devices/virtio-pci/创建相应子设备{virtioxxx},同时在/sys/bus/virtio/devices下面创建符号连接文件{virtioxxx}。这样在qemu和虚拟机操作系统内核对virtio设备的支持下,完成了virtio net设备的初始化,其实该过程与真实物理网卡的初始化过程类似。

3.2 virt-queue的使用

Virtio使用virtqueue接口来进行IO,每个virtqueue就是一个存储和传输IO数据的queue,virio中virtqueue由vring来实现。通过对virtqueue的buffer的读写,可以实现IO数据的读写,在读写virtqueue数据完毕后,借助virtqueue_kick()函数可以向queue notify寄存器写入队列index来通知宿主机。每个virio设备可以关联一个或者多个读写队列。

DPDK系列之十五:Virtio技术分析之一,virtio基础架构_第3张图片

3.3 前端驱动的运作方式

虚拟机中virtio驱动通过iowrite/ioread等操作与virtio设备进行交互,而该io操作会被KVM截获,从而转移至后端进行处理。在QEMU中为支持virtio设备的模拟提供了多个函数注册结构来响应虚拟机的不同读写请求。

在虚拟机将数据包从内核协议栈向下发送的时候,虚拟网卡设备的virtio-net驱动中注册的处理函数会被调用:

static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev)
{
	......
    err = xmit_skb(sq, skb);  
	......
    virtqueue_kick(sq->vq);
	......
}

xmit_skb主要是向vring写入数据:

static int xmit_skb(struct send_queue *sq, struct sk_buff *skb)
{
    struct skb_vnet_hdr *hdr;
    hdr_len = sizeof hdr->hdr;
    hdr = skb_vnet_hdr(skb);
    ......
    sg_set_buf(sq->sg, hdr, hdr_len);
  	num_sg = skb_to_sgvec(skb, sq->sg + 1, 0, skb->len) + 1;
    return virtqueue_add_outbuf(sq->vq, sq->sg, num_sg, skb, GFP_ATOMIC);
}

在将数据写入之后,还要将需要发送buffer信息填充到vring的desc描述符,包括buffer的长度、buffer占用的page的基址,相对基址的偏移量等。

信息写入完毕之后,可以将IO请求事件通过virtqueue_kick发送到后端:

bool virtqueue_kick(struct virtqueue *vq)
{
  if (virtqueue_kick_prepare(vq))
      return virtqueue_notify(vq);
  return true;
}

bool virtqueue_notify(struct virtqueue *_vq)
{
  struct vring_virtqueue *vq = to_vvq(_vq);
  if (unlikely(vq->broken))
      return false;
  /* Prod other side to tell it about changes. */
  if (!vq->notify(_vq)) {
      vq->broken = true;
      return false;
  }
  return true;
}

static bool vp_notify(struct virtqueue *vq)
{
  struct virtio_pci_device *vp_dev = to_vp_device(vq->vdev);
  /* we write the queue's selector into the notification register to
    * signal the other end */
  iowrite16(vq->index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NOTIFY);
  return true;
}

由于前端已经将数据的位置和长度信息发送给virtio驱动并且发送至后端,而且前端和后端是共享内存的,所以后端可以直接开始处理IO请求,并且不存在数据拷贝。

3.4 后端处理

虚拟机使用vp_notify模拟一次IO操作,之后虚拟机会退出guest模式,host内核会执行vmx_handle_eixt,最后运行到ioeventfd_write的时候,就会产生kick信号,如果该eventfd是由qemu侧来监听的,则会执行对应的qemu函数kvm_handle_io();如果是vhost来监听的,则直接在vhost内核模块执行vhost->handle_kick()。

eventfd是只存在于内存中的文件,通过系统调用sys_eventfd可以创建新的文件,它可以用于线程间、进程间的通信,无论是内核态或用户态。一般来说,称guest侧通知host侧的行为为kick,host侧通知guset侧的行为为call。 

根据不同的前端,后端会有不同的机制去handle,比如如果是网络数据包,则由后端处理程序负责发送到宿主机为这个虚拟机建立的tap接口:

DPDK系列之十五:Virtio技术分析之一,virtio基础架构_第4张图片

转载自https://blog.csdn.net/cloudvtech


















你可能感兴趣的:(DPDK,virtio,vhost,dpdkvhostuser,vhost-user,DPDK系列)