qemu和vhost-user前后端协商过程

14770阅读 0评论2018-06-24 lvyilong316
分类:LINUX

qemuvhost-user前后端协商过程

这篇文章主要从qemu的角度分析虚拟机启动前后端的协商过程。虚拟机当后端使用dpdk vhost-user时整个前后端过程可以分为三个阶段:qemu启动阶段,前端驱动加载写VIRTIO_PCI_GUEST_FEATURES寄存器,前端驱动加载完成写VIRTIO_PCI_STATUS寄存器。我们

     我们这里主要分析qemudpdk vhost_user的交互逻辑(代码:qemu2.10

qemu启动阶段

     qemu启动后,dpdk vhost_user会和qemu建立vhost socket链接,连接建立成功后qemu会调用net_vhost_user_event函数。

l  net_vhost_user_event


点击(此处)折叠或打开

  1. static void net_vhost_user_event(void *opaque, int event)
  2. {
  3.     const char *name = opaque;
  4.                    /* 定义多个NetClientState结构,每个队列一个 */
  5.     NetClientState *ncs[MAX_QUEUE_NUM];
  6.     VhostUserState *s;
  7.     Chardev *chr;
  8.     Error *err = NULL;
  9.     int queues;
  10.     /* 从qemu参数中获取后端设备定义的队列个数 */
  11.     queues = qemu_find_net_clients_except(name, ncs,
  12.                                           NET_CLIENT_DRIVER_NIC,
  13.                                           MAX_QUEUE_NUM);
  14.     assert(queues < MAX_QUEUE_NUM);
  15.  
  16.     s = DO_UPCAST(VhostUserState, nc, ncs[0]);
  17.     chr = qemu_chr_fe_get_driver(&s->chr);
  18.     trace_vhost_user_event(chr->label, event);
  19.     switch (event) {
  20.     case CHR_EVENT_OPENED: /* 链接建立的逻辑 */
  21.         if (vhost_user_start(queues, ncs, &s->chr) < 0) {
  22.             qemu_chr_fe_disconnect(&s->chr);
  23.             return;
  24.         }
  25.         s->watch = qemu_chr_fe_add_watch(&s->chr, G_IO_HUP,
  26.                                          net_vhost_user_watch, s);
  27.         qmp_set_link(name, true, &err);
  28.         s->started = true;
  29.         break;
  30.     case CHR_EVENT_CLOSED: /* 链接断开的逻辑 */
  31.         /* a close event may happen during a read/write, but vhost
  32.          * code assumes the vhost_dev remains setup, so delay the
  33.          * stop & clear to idle.
  34.          * FIXME: better handle failure in vhost code, remove bh
  35.          */
  36.         if (s->watch) {
  37.             AioContext *ctx = qemu_get_current_aio_context();
  38.  
  39.             g_source_remove(s->watch);
  40.             s->watch = 0;
  41.             qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, NULL, NULL,
  42.                                      NULL, NULL, false);
  43.  
  44.             aio_bh_schedule_oneshot(ctx, chr_closed_bh, opaque);
  45.         }
  46.         break;
  47.     }
  48.  
  49.     if (err) {
  50.         error_report_err(err);
  51.     }
  52. }


   其中分别有链接建立的逻辑,和链接断开的逻辑。我们主要关注链接建立的逻辑,也就是vhost_user_start。下面是vhost_user_start涉及和vhost_user交互的注意流程,具体代码不再展开。

其中需要说明的一点是qemuvhost_user发送消息是通过vhost_user_write函数进行的,而其中又会调用vhost_user_one_time_request对所发消息进行判断,如果当前消息属于只发一次的消息,且之前已经发送过了,就不在发送了。那么那些消息是只需要发送一次的呢?我们看下vhost_user_one_time_request的实现就清楚了。

l  vhost_user_one_time_request


点击(此处)折叠或打开

  1. static bool vhost_user_one_time_request(VhostUserRequest request)
  2. {
  3.     switch (request) {
  4.     case VHOST_USER_SET_OWNER:
  5.     case VHOST_USER_RESET_OWNER:
  6.     case VHOST_USER_SET_MEM_TABLE:
  7.     case VHOST_USER_GET_QUEUE_NUM:
  8.     case VHOST_USER_NET_SET_MTU:
  9.         return true;
  10.     default:
  11.         return false;
  12.     }
  13. }


这些消息反应在图中使用红色表示。另外需要注意的就是图中的两个循环,一个是vhost_net_init的调用。这个函数会初始化一个vhost_net结构,每个queue都会调用一次(每个queue都对应一个vhost_net结构),例如qemu启动参数定义设备有20queue,则这个函数就会调用20次。下面是vhost_net相关数据结构关系。


另一个循环是vhost_virtqueue_init的调用,这个调用是当前queue (对应结构体vhost_net)的每个virtqueue(也就是ring)调用一次,其中nvqs是固定的2(每个queue有两个ring)。

所以这一步的协商过程就是有一个个以VHOST_USER_GET_FEATURES开始的循环构成,其中VHOST_USER_SET_VRING_CALL又会被内部循环调用两次。

guest驱动加载

guest启动后,加载virtio-net驱动,会写寄存器VIRTIO_PCI_GUEST_FEATURES,这个写操作会被kvm捕获传递给qemuqemu会做如下处理。

其中有两个变量比较关键,一个是max_queues,这个就是qemu启动时后端指定的队列个数,另一个是curr_queues,这个是当前前端enablequeue。例如启动时指定20queue,但一般guest启动默认只会enable一个queue,所以max_queues20curr_queues1。另外注意vhost_set_vring_enable最终调用的是vhost_user_set_vring_enable,这个函数会为当前queue的每个ring发送一次VHOST_USER_SET_VRING_ENABLE消息,具体在下个阶段分析。所以20个队列会为每个queue都发送两个VHOST_USER_SET_VRING_ENABLE消息,共40个,但只有小于curr_queues时,也就是只有enablequeue才会发送state1的消息(共两个),否则state0。以20queue为例,会发生20VHOST_USER_SET_VRING_ENABLE消息,但只有第一个stateenable

guest驱动加载完成

    guestvirtio-net加载完成后会写VIRTIO_PCI_STATUS寄存器,这个操作同样会被kvm捕获传递给qemuqemu的相应处理逻辑如下。

其中比较关键的又是两个循环,一个是vhost_net_start_one的调用。这里的循环控制变量total_queuesguest驱动支持多队列时即为qemu启动的指定的后端队列个数,当guest不支持多队列特性的时候即为1。另一处循环就是vhost_virtqueue_start的调用,这个循环和第一阶段的内部循环类似,nvqs2,即每个queue拥有的ring的个数。hdev即为vhost_dev结构。

  最后要注意的就是只有guestenablequeue才会调用vhost_ops->vhost_set_vring_enable,也就是对于开机默认只enable 1个对列的情况只会调用一次。而vhost_ops->vhost_set_vring_enable实际上就是vhost_user_set_vring_enable,我们看下其实现。

l  vhost_user_set_vring_enable


点击(此处)折叠或打开

  1. static int vhost_user_set_vring_enable(struct vhost_dev *dev, int enable)
  2. {
  3.     int i;
  4.  
  5.     if (!virtio_has_feature(dev->features, VHOST_USER_F_PROTOCOL_FEATURES)) {
  6.         return -1;
  7.     }
  8.  
  9.     for (i = 0; i < dev->nvqs; ++i) {
  10.         struct vhost_vring_state state = {
  11.             .index = dev->vq_index + i,
  12.             .num = enable,
  13.         };
  14.  
  15.         vhost_set_vring(dev, VHOST_USER_SET_VRING_ENABLE, &state);
  16.     }
  17.  
  18.     return 0;
  19. }


可以看到vhost_user_set_vring_enable内部是多当前queue的每个ring调用一次VHOST_USER_SET_VRING_ENABLE,所以对于一个队列enable的情况这里会发送两个VHOST_USER_SET_VRING_ENABLE

到此为止,guest启动前后端的协商过程就完成了。如果是后端dpdk重启,vhost_user重连过程和以上启动过程类似,区别是没有第二个阶段,因为这个时候guest内部驱动已经加载完成

上一篇:基于dpdk的用户态协议栈f-stack实现分析
下一篇:Intel RDT特性详解