分类: LINUX
2022-03-22 12:38:17
原文地址:qemu和vhost-user前后端协商过程 作者:lvyilong316
这篇文章主要从qemu的角度分析虚拟机启动前后端的协商过程。虚拟机当后端使用dpdk vhost-user时整个前后端过程可以分为三个阶段:qemu启动阶段,前端驱动加载写VIRTIO_PCI_GUEST_FEATURES寄存器,前端驱动加载完成写VIRTIO_PCI_STATUS寄存器。我们
我们这里主要分析qemu和dpdk vhost_user的交互逻辑(代码:qemu2.10)。
qemu启动后,dpdk vhost_user会和qemu建立vhost socket链接,连接建立成功后qemu会调用net_vhost_user_event函数。
l net_vhost_user_event
点击(此处)折叠或打开
其中分别有链接建立的逻辑,和链接断开的逻辑。我们主要关注链接建立的逻辑,也就是vhost_user_start。下面是vhost_user_start涉及和vhost_user交互的注意流程,具体代码不再展开。
其中需要说明的一点是qemu向vhost_user发送消息是通过vhost_user_write函数进行的,而其中又会调用vhost_user_one_time_request对所发消息进行判断,如果当前消息属于只发一次的消息,且之前已经发送过了,就不在发送了。那么那些消息是只需要发送一次的呢?我们看下vhost_user_one_time_request的实现就清楚了。
l vhost_user_one_time_request
点击(此处)折叠或打开
这些消息反应在图中使用红色表示。另外需要注意的就是图中的两个循环,一个是vhost_net_init的调用。这个函数会初始化一个vhost_net结构,每个queue都会调用一次(每个queue都对应一个vhost_net结构),例如qemu启动参数定义设备有20个queue,则这个函数就会调用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启动后,加载virtio-net驱动,会写寄存器VIRTIO_PCI_GUEST_FEATURES,这个写操作会被kvm捕获传递给qemu。qemu会做如下处理。
其中有两个变量比较关键,一个是max_queues,这个就是qemu启动时后端指定的队列个数,另一个是curr_queues,这个是当前前端enable的queue。例如启动时指定20个queue,但一般guest启动默认只会enable一个queue,所以max_queues为20,curr_queues为1。另外注意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时,也就是只有enable的queue才会发送state为1的消息(共两个),否则state为0。以20个queue为例,会发生20个VHOST_USER_SET_VRING_ENABLE消息,但只有第一个state为enable。
当guest中virtio-net加载完成后会写VIRTIO_PCI_STATUS寄存器,这个操作同样会被kvm捕获传递给qemu。qemu的相应处理逻辑如下。
其中比较关键的又是两个循环,一个是vhost_net_start_one的调用。这里的循环控制变量total_queues当guest驱动支持多队列时即为qemu启动的指定的后端队列个数,当guest不支持多队列特性的时候即为1。另一处循环就是vhost_virtqueue_start的调用,这个循环和第一阶段的内部循环类似,nvqs为2,即每个queue拥有的ring的个数。hdev即为vhost_dev结构。
最后要注意的就是只有guest中enable的queue才会调用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
点击(此处)折叠或打开
可以看到vhost_user_set_vring_enable内部是多当前queue的每个ring调用一次VHOST_USER_SET_VRING_ENABLE,所以对于一个队列enable的情况这里会发送两个VHOST_USER_SET_VRING_ENABLE。
到此为止,guest启动前后端的协商过程就完成了。如果是后端dpdk重启,vhost_user重连过程和以上启动过程类似,区别是没有第二个阶段,因为这个时候guest内部驱动已经加载完成。