您的当前位置:首页正文

vhost-user前后端通知交互机制

2024-11-25 来源:个人技术集锦

概述

vhost-user是将内核的vhost模块移植到了用户态执行,实现效果基本和内核vhost一致。本文主要描述的vhost-user是基于dpdk的实现;后端使用内核vhost还是vhost-user对前端(虚拟机)是透明的,虚拟机内部都是virtio-net标准的驱动。(当然使用vhost-user时guest也可以使用virtio-net-pmd驱动作为前端)。
本文主要讲述vhost-user作为后端时前后端的交互通知机制,前端与后端通过vring 交换报文,发送队列、接收队列是前后端共享内存实现。

前端:

指虚拟机(guest)一端,本文中指virtio-net驱动(实际也可以使用dpdk-pmd驱动)

后端:

指宿主机(host)的vhost-user模块,本文指dpdk的vhost-user模块

通知交互4种场景:

发送队列(tx queue):
1、virtio-net驱动发送报文给vhost-user时,将报文放入tx队列后需要通知vhost-user模块tx队列有数据可以读取了;
2、vhost-user处理完tx队列的数据后(读取后做转发处理,发送到物理网卡或者ovs内部转发)需要通知virtio-net,报文已经读取完毕,你可以回收旧的desc了;

接收队列(rx queue)
3、vhost-user将需要发送到虚拟机的报文放到rx队列以后,需要通知virtio-net有新报文可以收取;
4、virtio-net收取完所有报文后,满足一定条件时会将描述符回填到avail ring中,然后通知vhost-user: avail ring有了新的描述符了,你可以用来放置新的报文了;

交互过程关键知识点//只涉及split ring, 不包含virtio1.1的packed ring

1、rx queue、 tx queue是两个单独的vring,(按virtio协议标准也可以用一个ring,但那样太复杂了,所以virtio-net基于独立的两个vring实现)
2、split ring把descriptor环形缓冲区(descriptor ring),可用环形缓冲区(available ring)和已用环形缓冲区(used ring) 分别使用3个数据结构来表示,这也是他被叫做split ring的原因。前端作为生产者将新生产可给后端使用的descriptor在available ring中更新,然后由后端从available ring中读取到自己可用的descriptor进行使用,使用完以后将用完的descriptor写到used ring中,供前端处理,循环使用这些内存。这里所谓的使用,可以从别的地方把数据拷贝到这些descriptor所描述的内存区域中(rx queue),也可以是从这些descriptor所描述的内存区域中把数据拷贝到别的地方(tx queue)。//三个ring只是三个数组用来访问队列的描述符,队列只有一个,通过三个ring实现数据共享内存交换。
3、队列中的vring结构体是共享内存,前后端共享访问,其中内部avail ring由前端设置,后端读取;used ring由后端设置,前端读取。
4、VRING_AVAIL_F_NO_INTERRUPT 由前端设置,后端用此flag判断是否需要给前端发送中断通知(发送时机见后面描述)
5、VRING_USED_F_NOTIFY 由后端设置,前端判断此标记决定是否给后端发送kick通知。
6、vring_virtqueue结构用于描述前端驱动(Guest)中的一条虚拟队列
7、前后端的通知不一定是必须的,如果后端使用poll机制,则不需要前端给后端发送通知;同样如果前端不使用标准的virtio-net中断模式,采用dpdk-pmd或者其他实现的轮询机制,则不需要后端给前端发送中断通知。//不发送前端通知可以提升较大性能,但由于是无限轮询,对cpu等硬件资源消耗比较大,具体使用什么方式看具体需求。

通知时机

1、发送队列前端通知后端:guest发送数据后调用virtqueue_kick(最终为vp_notify)发送通知。(有event_idx时通过eventidx判断,否则判断VRING_USED_F_NOTIFY是否发送通知)
2、发送队列后端通知前端:后端读完数据时发送中断给guest,触发skb_xmit_done (free_old_xmit_skb流程也会主动释放旧skb)
对于发送队列,即使不开启event_idx,也不是每次uesd 变化都会kick前端的,而是只有queue stop后才会kick,正常状态前端是会设置VRING_AVAIL_F_NO_INTERRUPT不让后端kick的,
因为回收desc是在start_xmit函数开始位置执行,不需要后端发送通知。

3、接收队列后端通知前端: 后端放入数据后(数据是放到了used ring中),发送中断,guest内部最终的中断处理函数为skb_recv_done;然后skb_recv_done内部调用napi收包流程(或者自己实现处理流程)virtqueue_get_buf收取报文数据,收取skb后函数中调用detach_buf将desc归还到free_list中(desc_ring里)
4、接收队列前端通知后端: guest接收完数据后会判断free_desc是否小于1/2的队列大小,满足则调用try_fill_recv填充avail ring。然后kick。

遗留问题:

1、packed ring通知机制、event_idx通知机制
2、packed ring相比split ring性能提升多少?(packed ring 在内核4.19中支持)

参考资料:


显示全文