dpdk 1711新特性之vhost iotlb
——lvyilong316
iotlb简介
说的iotlb就不得不先说IOMMU。大家知道,I/O设备可以直接存取内存,称为DMA(Direct Memory Access);DMA要存取的内存地址称为DMA地址(也可称为BUS
address)。在DMA技术刚出现的时候,DMA地址都是物理内存地址,简单直接,但缺点是不灵活,比如要求物理内存必须是连续的一整块而且不能是高位地址等等,也不能充分满足虚拟机的需要。后来dmar就出现了。 dmar意为DMA remapping,是Intel为支持虚拟机而设计的I/O虚拟化技术,I/O设备访问的DMA地址不再是物理内存地址,而要通过DMA remapping硬件进行转译,DMA remapping硬件会把DMA地址翻译成物理内存地址,并检查访问权限等等。负责DMA remapping操作的硬件称为IOMMU。做个类比:大家都知道MMU是支持内存地址虚拟化的硬件,MMU是为CPU服务的;而IOMMU是为I/O设备服务的,是将DMA地址进行虚拟化的硬件。
vhost iova
在dpdk17.11引入了vhost对iotlb的支持。具体体现在将guest物理地址(gpa)转换为host虚拟地址(vva)的过程。具体实现为函数vhost_iova_to_vva。
l vhost_iova_to_vva
-
static __rte_always_inline uint64_t
-
vhost_iova_to_vva(struct virtio_net *dev, struct vhost_virtqueue *vq,
-
uint64_t iova, uint64_t *len, uint8_t perm)
-
{
-
if (!(dev->features & (1ULL << VIRTIO_F_IOMMU_PLATFORM)))
-
return rte_vhost_va_from_guest_pa(dev->mem, iova, len);
-
-
return __vhost_iova_to_vva(dev, vq, iova, len, perm);
-
}
可以看到当后端设备不支持VIRTIO_F_IOMMU_PLATFORM时,将通过rte_vhost_va_from_guest_pa进行转换,这个函数和dpdk 16.11中的rte_vhost_gpa_to_vva函数实现相同,这里不再展开介绍。这里重点关注一下当支持IOMMU_PLATFORM时调用的__vhost_iova_to_vva函数。
-
/* Called with iotlb_lock read-locked */
-
uint64_t
-
__vhost_iova_to_vva(struct virtio_net *dev, struct vhost_virtqueue *vq,
-
uint64_t iova, uint64_t *size, uint8_t perm)
-
{
-
uint64_t vva, tmp_size;
-
-
if (unlikely(!*size))
-
return 0;
-
-
tmp_size = *size;
-
/* 通过查找vq的iotlb_list,找到iova对应的host虚拟地址*/
-
vva = vhost_user_iotlb_cache_find(vq, iova, &tmp_size, perm);
-
if (tmp_size == *size)
-
return vva;
-
-
iova += tmp_size;
-
/* 如果iotlb_list中没有对应iova地址的映射关系,且iotlb_pending_list中也不存在 */
-
if (!vhost_user_iotlb_pending_miss(vq, iova, perm)) {
-
/*
-
* iotlb_lock is read-locked for a full burst,
-
* but it only protects the iotlb cache.
-
* In case of IOTLB miss, we might block on the socket,
-
* which could cause a deadlock with QEMU if an IOTLB update
-
* is being handled. We can safely unlock here to avoid it.
-
*/
-
vhost_user_iotlb_rd_unlock(vq);
-
/* 使用之前miss的iova地址,构建一个vhost_iotlb_entry结构,挂在vq的iotlb_pending_list */
-
vhost_user_iotlb_pending_insert(vq, iova, perm);
-
/* 构造VHOST_IOTLB_MISS消息发给qemu */
-
if (vhost_user_iotlb_miss(dev, iova, perm)) {
-
RTE_LOG(ERR, VHOST_CONFIG,
-
"IOTLB miss req failed for IOVA 0x%" PRIx64 "\n",
-
iova);
-
vhost_user_iotlb_pending_remove(vq, iova, 1, perm);
-
}
-
-
vhost_user_iotlb_rd_lock(vq);
-
}
-
-
return 0;
-
}
理解这段代码之前,首先看下vhost_user为支持iotlb做的数据结构修改,主要在vq结构上,我们看下相关增加的成员结构,如下图。
首先是vhost_iotlb_entry这个结构,这个结构类似于硬件iotlb的表项,其中的iova就是guest的物理地址,而uaddr就是对应的host虚拟地址。然后看到vhost_virtqueue上有两个这个结构的链表,其中一个是iotlb_list,这个就相当于一个设备的iotlb,用于存放guest物理地址和host虚拟地址的映射关系。另一个是iotlb_pending_list,这个是用来存放查找miss的guest物理地址段信息。
然后我们看这个函数的逻辑:
(1) 通过vhost_user_iotlb_cache_find查找vq的iotlb_list,如果找到iova对应的host虚拟地址则直接返回,如果没有查到则向下执行;
(2) 调用vhost_user_iotlb_pending_miss查找当前miss的地址信息是否已经在iotlb_pending_list中,iotlb_pending_list是查找失败等待向qemu发送查找请求的地址段列表;
(3) 如果已经加入到iotlb_pending_list则直接返回0,本次查找失败,否则调用vhost_user_iotlb_miss,该函数通过dev->slave_req_fd向qemu发送VHOST_IOTLB_MISS消息请求对应的地址转换。
那么dev->slave_req_fd这个fd是什么时候被设置的呢?是在前后端协商时通过VHOST_USER_SET_SLAVE_REQ_FD消息设置的。
case
VHOST_USER_SET_SLAVE_REQ_FD:
ret
= vhost_user_set_req_fd(dev, &msg);
break;
l vhost_user_set_req_fd
-
static int
-
vhost_user_set_req_fd(struct virtio_net *dev, struct VhostUserMsg *msg)
-
{
-
int fd = msg->fds[0];
-
-
if (fd < 0) {
-
RTE_LOG(ERR, VHOST_CONFIG,
-
"Invalid file descriptor for slave channel (%d)\n",
-
fd);
-
return -1;
-
}
-
-
dev->slave_req_fd = fd;
-
-
return 0;
-
}
我们再来看什么时候qemu会将查找结果返回给vhost_user,这也是通过VHOST_USER_IOTLB_MSG传递的。在vhost_user_msg_handler中有如下处理逻辑。
case
VHOST_USER_IOTLB_MSG:
ret
= vhost_user_iotlb_msg(&dev, &msg);
break;
l vhost_user_iotlb_msg
-
static int
-
vhost_user_iotlb_msg(struct virtio_net **pdev, struct VhostUserMsg *msg)
-
{
-
struct virtio_net *dev = *pdev;
-
struct vhost_iotlb_msg *imsg = &msg->payload.iotlb;
-
uint16_t i;
-
uint64_t vva, len;
-
-
switch (imsg->type) {
-
case VHOST_IOTLB_UPDATE:
-
len = imsg->size;
-
vva = qva_to_vva(dev, imsg->uaddr, &len);
-
if (!vva)
-
return -1;
-
-
for (i = 0; i < dev->nr_vring; i++) {
-
struct vhost_virtqueue *vq = dev->virtqueue[i];
-
-
vhost_user_iotlb_cache_insert(vq, imsg->iova, vva,
-
len, imsg->perm);
-
-
if (is_vring_iotlb_update(vq, imsg))
-
*pdev = dev = translate_ring_addresses(dev, i);
-
}
-
break;
-
case VHOST_IOTLB_INVALIDATE:
-
for (i = 0; i < dev->nr_vring; i++) {
-
struct vhost_virtqueue *vq = dev->virtqueue[i];
-
-
vhost_user_iotlb_cache_remove(vq, imsg->iova,
-
imsg->size);
-
-
if (is_vring_iotlb_invalidate(vq, imsg))
-
vring_invalidate(dev, vq);
-
}
-
break;
-
default:
-
RTE_LOG(ERR, VHOST_CONFIG, "Invalid IOTLB message type (%d)\n",
-
imsg->type);
-
return -1;
-
}
-
-
return 0;
-
}
如果消息类型是VHOST_IOTLB_UPDATE,则调用vhost_user_iotlb_cache_insert将qemu传递过来的地址转换信息构建成vhost_iotlb_entry 并插入iotlb_list,同时清除之前iotlb_pending_list中的对应条目。如果vq的desc,avail和used地址在更新的地址段内,则需要调用translate_ring_addresses更新这些ring的地址。
如果消息类型是VHOST_IOTLB_INVALIDATE,则调用vhost_user_iotlb_cache_remove将对应的vhost_iotlb_entry从iotlb_list中删除。
为什么要支持vhost iotlb
支持vhost iotlb一个关键作用就是可以提高安全性,由qemu来负责地址转换可以确保guest物理地址的合法性,当然由于收发包可能会有地址miss的情况需要请求qemu,所以会对性能有一定影响。详情参考: 。
阅读(12508) | 评论(0) | 转发(0) |