lvyilong316 阅读(18) 评论(0)

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引入了vhostiotlb的支持。具体体现在将guest物理地址(gpa)转换为host虚拟地址(vva)的过程。具体实现为函数vhost_iova_to_vva

l  vhost_iova_to_vva

点击(此处)折叠或打开

  1. static __rte_always_inline uint64_t
  2. vhost_iova_to_vva(struct virtio_net *dev, struct vhost_virtqueue *vq,
  3.                             uint64_t iova, uint64_t *len, uint8_t perm)
  4. {
  5.          if (!(dev->features & (1ULL << VIRTIO_F_IOMMU_PLATFORM)))
  6.                    return rte_vhost_va_from_guest_pa(dev->mem, iova, len);
  7.  
  8.          return __vhost_iova_to_vva(dev, vq, iova, len, perm);
  9. }

可以看到当后端设备不支持VIRTIO_F_IOMMU_PLATFORM时,将通过rte_vhost_va_from_guest_pa进行转换,这个函数和dpdk 16.11中的rte_vhost_gpa_to_vva函数实现相同,这里不再展开介绍。这里重点关注一下当支持IOMMU_PLATFORM时调用的__vhost_iova_to_vva函数。

点击(此处)折叠或打开

  1. /* Called with iotlb_lock read-locked */
  2. uint64_t
  3. __vhost_iova_to_vva(struct virtio_net *dev, struct vhost_virtqueue *vq,
  4.                        uint64_t iova, uint64_t *size, uint8_t perm)
  5. {
  6.          uint64_t vva, tmp_size;
  7.  
  8.          if (unlikely(!*size))
  9.                    return 0;
  10.  
  11.          tmp_size = *size;
  12.     /* 通过查找vq的iotlb_list,找到iova对应的host虚拟地址*/
  13.          vva = vhost_user_iotlb_cache_find(vq, iova, &tmp_size, perm);
  14.          if (tmp_size == *size)
  15.                    return vva;
  16.  
  17.          iova += tmp_size;
  18.     /* 如果iotlb_list中没有对应iova地址的映射关系,且iotlb_pending_list中也不存在 */
  19.          if (!vhost_user_iotlb_pending_miss(vq, iova, perm)) {
  20.                    /*
  21.                     * iotlb_lock is read-locked for a full burst,
  22.                     * but it only protects the iotlb cache.
  23.                     * In case of IOTLB miss, we might block on the socket,
  24.                     * which could cause a deadlock with QEMU if an IOTLB update
  25.                     * is being handled. We can safely unlock here to avoid it.
  26.                     */
  27.                    vhost_user_iotlb_rd_unlock(vq);
  28.         /* 使用之前miss的iova地址,构建一个vhost_iotlb_entry结构,挂在vq的iotlb_pending_list */
  29.                    vhost_user_iotlb_pending_insert(vq, iova, perm);
  30.              /* 构造VHOST_IOTLB_MISS消息发给qemu */
  31.                    if (vhost_user_iotlb_miss(dev, iova, perm)) {
  32.                             RTE_LOG(ERR, VHOST_CONFIG,
  33.                                      "IOTLB miss req failed for IOVA 0x%" PRIx64 "\n",
  34.                                      iova);
  35.                             vhost_user_iotlb_pending_remove(vq, iova, 1, perm);
  36.                    }
  37.  
  38.                    vhost_user_iotlb_rd_lock(vq);
  39.          }
  40.  
  41.          return 0;
  42. }

理解这段代码之前,首先看下vhost_user为支持iotlb做的数据结构修改,主要在vq结构上,我们看下相关增加的成员结构,如下图。

首先是vhost_iotlb_entry这个结构,这个结构类似于硬件iotlb的表项,其中的iova就是guest的物理地址,而uaddr就是对应的host虚拟地址。然后看到vhost_virtqueue上有两个这个结构的链表,其中一个是iotlb_list,这个就相当于一个设备的iotlb,用于存放guest物理地址和host虚拟地址的映射关系。另一个是iotlb_pending_list,这个是用来存放查找missguest物理地址段信息。

然后我们看这个函数的逻辑:

(1)     通过vhost_user_iotlb_cache_find查找vqiotlb_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_fdqemu发送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

点击(此处)折叠或打开

  1. static int
  2. vhost_user_set_req_fd(struct virtio_net *dev, struct VhostUserMsg *msg)
  3. {
  4.          int fd = msg->fds[0];
  5.  
  6.          if (fd < 0) {
  7.                    RTE_LOG(ERR, VHOST_CONFIG,
  8.                                      "Invalid file descriptor for slave channel (%d)\n",
  9.                                      fd);
  10.                    return -1;
  11.          }
  12.  
  13.          dev->slave_req_fd = fd;
  14.  
  15.          return 0;
  16. }

我们再来看什么时候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

点击(此处)折叠或打开

  1. static int
  2. vhost_user_iotlb_msg(struct virtio_net **pdev, struct VhostUserMsg *msg)
  3. {
  4.          struct virtio_net *dev = *pdev;
  5.          struct vhost_iotlb_msg *imsg = &msg->payload.iotlb;
  6.          uint16_t i;
  7.          uint64_t vva, len;
  8.  
  9.          switch (imsg->type) {
  10.          case VHOST_IOTLB_UPDATE:
  11.                    len = imsg->size;
  12.                    vva = qva_to_vva(dev, imsg->uaddr, &len);
  13.                    if (!vva)
  14.                             return -1;
  15.  
  16.                    for (i = 0; i < dev->nr_vring; i++) {
  17.                             struct vhost_virtqueue *vq = dev->virtqueue[i];
  18.           
  19.                             vhost_user_iotlb_cache_insert(vq, imsg->iova, vva,
  20.                                                len, imsg->perm);
  21.  
  22.                             if (is_vring_iotlb_update(vq, imsg))
  23.                                      *pdev = dev = translate_ring_addresses(dev, i);
  24.                    }
  25.                    break;
  26.          case VHOST_IOTLB_INVALIDATE:
  27.                    for (i = 0; i < dev->nr_vring; i++) {
  28.                             struct vhost_virtqueue *vq = dev->virtqueue[i];
  29.  
  30.                             vhost_user_iotlb_cache_remove(vq, imsg->iova,
  31.                                                imsg->size);
  32.  
  33.                             if (is_vring_iotlb_invalidate(vq, imsg))
  34.                                      vring_invalidate(dev, vq);
  35.                    }
  36.                    break;
  37.          default:
  38.                    RTE_LOG(ERR, VHOST_CONFIG, "Invalid IOTLB message type (%d)\n",
  39.                                      imsg->type);
  40.                    return -1;
  41.          }
  42.  
  43.          return 0;
  44. }

如果消息类型是VHOST_IOTLB_UPDATE,则调用vhost_user_iotlb_cache_insertqemu传递过来的地址转换信息构建成vhost_iotlb_entry 并插入iotlb_list,同时清除之前iotlb_pending_list中的对应条目。如果vqdescavailused地址在更新的地址段内,则需要调用translate_ring_addresses更新这些ring的地址。

如果消息类型是VHOST_IOTLB_INVALIDATE,则调用vhost_user_iotlb_cache_remove将对应的vhost_iotlb_entryiotlb_list中删除。

为什么要支持vhost iotlb

    支持vhost iotlb一个关键作用就是可以提高安全性,由qemu来负责地址转换可以确保guest物理地址的合法性,当然由于收发包可能会有地址miss的情况需要请求qemu,所以会对性能有一定影响。详情参考:https://www.linux-kvm.org/page/Device_iotlb