23 September 2014

Vhost 概述

Linux kernel 中的vhost driver提供了KVM在kernel环境中的virtio设备的模拟。vhost把QEMU模拟设备的代码放在了linux kernel里面,所以设备模拟代码可以直接进入kernel子系统,从而不需要从用户空间通过系统调用陷入内核,减少了由于模拟IO导致的性能下降。

vhost-net是在宿主机上对vhost 网卡的模拟,同样,也有vhost-blk,对block设备的模拟,以及vhost-scsi,对scsi设备的模拟。

vhost 在kernel中的代码位于 drivers/vhost/vhost.c

Vhost 驱动模型

vhost driver创建了一个字符设备 /dev/vhost-net,这个设备可以被用户空间打开,并可以被ioctl命令操作。当给一个Qemu进程传递了参数-netdev tap,vhost=on 的时候,QEMU会通过调用几个ioctl命令对这个文件描述符进行一些初始化的工作,然后进行特性的协商,从而宿主机跟客户机的vhost-net driver建立关系。 QEMU代码调用如下:

vhost_net_init -> vhost_dev_init -> vhost_net_ack_features

在vhost_net_init中调用了 vhost_dev_init ,打开/dev/vhost-net这个设备,然后返回一个文件描述符作为vhost-net的后端, vhost_dev_init 调用的ioctl命令有

r = ioctl(hdev->control, VHOST_SET_OWNER, NULL);

Kernel 中的定义为:

1.  /* Set current process as the (exclusive) owner of this file descriptor.  This  
2.   * must be called before any other vhost command.  Further calls to  
3.   * VHOST_OWNER_SET fail until VHOST_OWNER_RESET is called. */  
4.  #define VHOST_SET_OWNER _IO(VHOST_VIRTIO, 0x01)

然后获取VHOST支持的特性

r = ioctl(hdev->control, VHOST_GET_FEATURES, &features);

同样,kernel中的定义为:

1.  /* Features bitmask for forward compatibility.  Transport bits are used for  
2.   * vhost specific features. */  
3.  #define VHOST_GET_FEATURES      _IOR(VHOST_VIRTIO, 0x00, __u64)  

QEMU中用vhost_net 这个数据结构代表打开的vhost_net 实例:

1.  struct vhost_net {  
2.      struct vhost_dev dev;  
3.      struct vhost_virtqueue vqs[2];  
4.      int backend;  
5.      NetClientState *nc;  
6.  };  

使用ioctl设置完后,QEMU注册memory_listener 回调函数:

1.  hdev->memory_listener = (MemoryListener) {  
2.      .begin = vhost_begin,  
3.      .commit = vhost_commit,  
4.      .region_add = vhost_region_add,  
5.      .region_del = vhost_region_del,  
6.      .region_nop = vhost_region_nop,  
7.      .log_start = vhost_log_start,  
8.      .log_stop = vhost_log_stop,  
9.      .log_sync = vhost_log_sync,  
10.     .log_global_start = vhost_log_global_start,  
11.     .log_global_stop = vhost_log_global_stop,  
12.     .eventfd_add = vhost_eventfd_add,  
13.     .eventfd_del = vhost_eventfd_del,  
14.     .priority = 10  
15. };  

vhost_region_add 是为了将QEMU guest的地址空间映射到vhost driver

最后进行特性的协商:

1.  /* Set sane init value. Override when guest acks. */  
2.  vhost_net_ack_features(net, 0); 

与此同时,kernel中要创建一个kernel thread 用于处理I/O事件和设备的模拟。 kernel代码 drivers/vhost/vhost.c:

在vhost_dev_set_owner中,调用了这个函数用于创建worker线程(线程名字为vhost-qemu+进程pid)

arc of vhost

Kernel 中的virtio 模拟

vhost并没有完全模拟一个pci设备,相反,它只把自己限制在对virtqueue的操作上。

worker thread 一直等待virtqueue的数据,对于vhost-net来说,当virtqueue的tx队列中有数据了,它会把数据传送到与其相关联的tap设备文件描述符上。

相反,worker thread 也要进行tap文件描述符的轮询,对于vhost-net,当tap文件描述符有数据到来时候,worker thread会被唤醒,然后将数据传送到rx队列中。

vhost的在用户空间的接口

数据已经准备好了,如何通知客户机呢?

从vhost模块的依赖性可以得知,vhost这个模块并没有依赖于kvm 模块,也就是说,理论上其他应用只要实现了和vhost接口,也可以调用vhost来进行数据传输的加速。

但也正是因为vhost跟kvm模块没什么关系,当QEMU(KVM)guest把数据放到tx队列(virtqueue)上之后,它是没有办法直接通知vhost数据准备了的。

不过,vhost 设置了一个eventfd文件描述符,这个文件描述符被前面我们提到的worker thread 监控,所以QEMU可以通过向eventfd发送消息告诉vhost数据准备好了。

QEMU的做法是这样的,在QEMU中注册一个ioeventfd,当guest 发生I/O退出了,会被KVM捕捉到,KVM向vhost发送eventfd从而告知vhost KVM guest已经准备好数据了。由于 worker thread监控这个eventfd,在收到消息后,知道guest已经把数据放到了tx队列,可以进行对戏

vhost通过发出一个guest中断,通过KVM提供的irqevent,告诉guest需要传送的buffer已经放到了rx virtqueue了,QEMU(KVM)注册这个irq PCI事件,得知内核空间的数据准备好了,调用guest驱动进行数据的读取。

所以,总的来说 vhost 实例需要知道的有三样

  • guest的内存映射,也就是virtqueue,用于数据的传输
  • qemu kick eventfd,vhost接收guest发送的消息,该消息被worker thread捕获
  • call event(irqfd)用于通知guest

以上三点在QEMU初始化的时候准备好,数据的传输只在内核空间就完成了,不需要QEMU进行干预,所以这也是为什么使用vhost进行传输数据高效的原因。



blog comments powered by Disqus