当使用vhost-user时,需要在系统中创建一个unix domain socket server,用来处理qemu发送给host的消息。
如果有新的socket连接,说明guest创建了新的virtio-net设备,vhost驱动会为之创建一个vhost设备,之后qemu就可以通过socket和vhost进行通信了;当socket关闭,vhost就会销毁对应的设备。
常用的消息包括:
//driver\net\virtio\virtio_user\vhost_kernel.c
/* vhost kernel ioctls */
#define VHOST_VIRTIO
/*返回vhost支持的virtio-net功能子集*/
#define VHOST_GET_FEATURES
/*检查功能掩码,设置vhost和virtio前端共同支持的特性,需要两者同时支持才能生效*/
#define VHOST_SET_FEATURES
/*将设备设置为当前进程所有*/
#define VHOST_SET_OWNER
/*当前进程释放对设备的所有权*/
#define VHOST_RESET_OWNER
/*设置内存空间布局信息,用于报文收发时的地址转换*/
#define VHOST_SET_MEM_TABLE
/*下面两个宏,用于guest在线迁移*/
#define VHOST_SET_LOG_BASE
#define VHOST_SET_LOG_FD
/*vhost记录每个虚拟队列的大小*/
#define VHOST_SET_VRING_NUM //设置vring size
/*由qemu发送virtqueue结构的虚拟地址。vhost将该地址转换成vhost的虚拟地址。*/
#define VHOST_SET_VRING_ADDR
/*传递初始索引值,vhost通过该索引值找到初始描述符*/
#define VHOST_SET_VRING_BASE //VirtQueue.last_avail_idx
/*将虚拟队列的当前可用索引值发送给qemu*/
#define VHOST_GET_VRING_BASE
/*传递eventfd文件描述符。当guest有新的数据要发送时,通过该文件描述符通知vhost接收数据并发送到目的地;vhost使用eventfd代理模块把这个文件描述符从qemu上下文切换到自己的进程上下文*/
#define VHOST_SET_VRING_KICK //设置kick fd (guest -> vhost)
/*也是用来传递eventfd文件描述符。使vhost能够在完成对新的数据包接收时,通过中断方式通知guest准备接收数据包。使用eventfd代理模块把这个文件描述符从qemu上下文切换到自己的进程上下文*/
#define VHOST_SET_VRING_CALL //设置call fd (vhost -> guest)
/*代码中仅有定义,未使用*/
#define VHOST_SET_VRING_ERR
/*用来支持virtio-user*/
#define VHOST_NET_SET_BACKEND
virtqueue和vring进行数据交换的核心是使用一种机制将数据缓冲区实现对guest和host同时可见,从而通过避免数据的拷贝来消耗性能。dpdk vhost在这里使用的是大页内存、内存映射以及相应的地址转换来完成这个功能的。
因此,host端必须由足够的大页空间,同时需要指定内存预分配。为了vhost能访问virtqueue和数据包缓冲区,所有的描述符表、环表地址,其所在页面必须被映射到vhost的进程空间中。
vhost在收到VHOST_SET_MEM_TABLE消息后,会使用消息中的内存分布表来完成内存映射工作:
/*下面的两个数据结构记录guest的物理地址及偏移量*/
/**
* Information relating to memory regions including offsets to
* addresses in QEMUs memory file.
*/
struct rte_vhost_mem_region {
uint64_t guest_phys_addr;
uint64_t guest_user_addr;
uint64_t host_user_addr;
uint64_t size;
void *mmap_addr;
uint64_t mmap_size;
int fd;
};
/**
* Memory structure includes region and mapping information.
*/
struct rte_vhost_memory {
uint32_t nregions;
struct rte_vhost_mem_region regions[];
};
/*
*将 QEMU virtual address 转化成 Vhost virtual address. 该函数用来将ring address
* 转换成host端的virtual address
*/
static uint64_t
qva_to_vva(struct virtio_net *dev, uint64_t qva)
{
struct rte_vhost_mem_region *reg;
uint32_t i;
/* Find the region where the address lives. */
for (i = 0; i < dev->mem->nregions; i++) {
reg = &dev->mem->regions[i];
if (qva >= reg->guest_user_addr &&
qva < reg->guest_user_addr + reg->size) {
return qva - reg->guest_user_addr +
reg->host_user_addr;
}
}
return 0;
}
一个virtio-net设备的生命周期包括设备创建、配置、服务启动和设备销毁几个阶段。
vhost-user通过socket连接来创建。当创建一个virtio-net设备是,需要
分配新的virtio-net设备结构,并添加到设备链表中
为该设备分配一个处理处理核并添加设备到数据面的链表中
在vhost上分配一个为virtio-net设备服务的RX\TX队列
利用VHOST_SET_VRING_*消息通知vhost虚拟队列的大小、基本索引和位置,vhost将虚拟队列映射到自己的虚拟地址空间
vhost利用VHOST_SET_VRING_KICK消息来启动虚拟队列服务。之后,vhost便可以轮询接收队列,并将数据放到virtio-net设备的接收队列上。同时,也可以轮询发送虚拟队列,查看是否有待发送的数据包,如果有,则将其复制到发送队列中。
vhost利用VHOST_GET_VRING_BASE消息来通知停止提供对接收队列和发送虚拟队列的服务。同时,分配给virtio-net设备的处理和和物理网卡上的RX和TX队列也将被释放。
/*显式设置支持新特性*/
int rte_vhost_driver_set_features(const char *path, uint64_t features)
/*使能相关特性*/
int rte_vhost_driver_enable_features(const char *path, uint64_t features)
/*去使能相关特性*/
int rte_vhost_driver_disable_features(const char *path, uint64_t features)
以上的操作都是针对socket->features做软件特性的设置,原理大同小异;这些接口可以用来在driver注册后,对该driver的特性进行微调。
比如当支持mergeable特性时,可以调用rte_vhost_driver_enable_features(file,1ULL << VIRTIO_NET_F_MRG_RXBUF)来进行设置。 当前支持的特性包括:
/* The feature bitmap for virtio net */
#define VIRTIO_NET_F_CSUM 0 /* Host handles pkts w/ partial csum */
#define VIRTIO_NET_F_GUEST_CSUM 1 /* Guest handles pkts w/ partial csum */
#define VIRTIO_NET_F_MTU 3 /* Initial MTU advice. */
#define VIRTIO_NET_F_MAC 5 /* Host has given MAC address. */
#define VIRTIO_NET_F_GUEST_TSO4 7 /* Guest can handle TSOv4 in. */
#define VIRTIO_NET_F_GUEST_TSO6 8 /* Guest can handle TSOv6 in. */
#define VIRTIO_NET_F_GUEST_ECN 9 /* Guest can handle TSO[6] w/ ECN in. */
#define VIRTIO_NET_F_GUEST_UFO 10 /* Guest can handle UFO in. */
#define VIRTIO_NET_F_HOST_TSO4 11 /* Host can handle TSOv4 in. */
#define VIRTIO_NET_F_HOST_TSO6 12 /* Host can handle TSOv6 in. */
#define VIRTIO_NET_F_HOST_ECN 13 /* Host can handle TSO[6] w/ ECN in. */
#define VIRTIO_NET_F_HOST_UFO 14 /* Host can handle UFO in. */
#define VIRTIO_NET_F_MRG_RXBUF 15 /* Host can merge receive buffers. */
#define VIRTIO_NET_F_STATUS 16 /* virtio_net_config.status available */
#define VIRTIO_NET_F_CTRL_VQ 17 /* Control channel available */
#define VIRTIO_NET_F_CTRL_RX 18 /* Control channel RX mode support */
#define VIRTIO_NET_F_CTRL_VLAN 19 /* Control channel VLAN filtering */
#define VIRTIO_NET_F_CTRL_RX_EXTRA 20 /* Extra RX mode control support */
#define VIRTIO_NET_F_GUEST_ANNOUNCE 21 /* Guest can announce device on the
* network */
#define VIRTIO_NET_F_MQ 22 /* Device supports Receive Flow
* Steering */
#define VIRTIO_NET_F_CTRL_MAC_ADDR 23 /* Set MAC address */
/* Do we get callbacks when the ring is completely used, even if we've
* suppressed them? */
#define VIRTIO_F_NOTIFY_ON_EMPTY 24
/* Can the device handle any descriptor layout? */
#define VIRTIO_F_ANY_LAYOUT 27
/* We support indirect buffer descriptors */
#define VIRTIO_RING_F_INDIRECT_DESC 28
#define VIRTIO_F_VERSION_1 32
#define VIRTIO_F_IOMMU_PLATFORM 33