dpdk vhost_user desc chain的形成

6830阅读 0评论2018-02-04 lvyilong316
分类:LINUX

vhost_user desc chain的形成

——lvyilong316

    在前面分析vhost_user mergeable特性的时候,我们提到了一个”desc chain”的概念。本节重点分析一下这个chain是如何建立的。

所谓desc chain即各个desc通过其next成员相互关联,但前提是desc->falg被设置了VRING_DESC_F_NEXT,只有这种情况next的值才有意义。这个desc chain的建立是由前端驱动建立并维护的,后端不能去更改。我们以kernel 3.10virtio-net为例。当前端要接受数据包时,首先要准备好要接受数据包的内存,也就是avail ring中的desc。这是通过try_fill_recv来完成的。

l  try_fill_recv

点击(此处)折叠或打开

  1. static bool try_fill_recv(struct receive_queue *rq, gfp_t gfp)
  2. {
  3.          struct virtnet_info *vi = rq->vq->vdev->priv;
  4.          int err;
  5.          bool oom;
  6.  
  7.          do {
  8.                    if (vi->mergeable_rx_bufs)
  9.                             err = add_recvbuf_mergeable(rq, gfp); /*后端支持VIRTIO_NET_F_MRG_RXBUF*/
  10.                    else if (vi->big_packets)
  11.                             err = add_recvbuf_big(rq, gfp); /*后端支持GUEST_GSO/GUEST_TSO,相当于LRO*/
  12.                    else
  13.                             err = add_recvbuf_small(rq, gfp);
  14.  
  15.                    oom = err == -ENOMEM;
  16.                    if (err)
  17.                             break;
  18.                    ++rq->num;
  19.          } while (rq->vq->num_free);
  20.          if (unlikely(rq->num > rq->max))
  21.                    rq->max = rq->num;
  22.          virtqueue_kick(rq->vq); /*通知后端avail ring更新*/
  23.          return !oom;
  24. }

     这里会根据是否开启guest tso,是否开启mergeable分为三种情况,分别会调用add_recvbuf_mergeableadd_recvbuf_bigadd_recvbuf_small三个函数。这两个函数最终又都会调用virtqueue_add_inbuf函数。这个函数最终又会调用到virtqueue_add,后者在virtio-net发送逻辑中已经分析过了。其中主要是通过以下逻辑建立desc chain关系的。

点击(此处)折叠或打开

  1. for (sg = sgs[n]; sg; sg = next(sg, &total_out)) {
  2.                             vq->vring.desc[i].flags = VRING_DESC_F_NEXT;
  3.                             vq->vring.desc[i].addr = sg_phys(sg);
  4.                             vq->vring.desc[i].len = sg->length;
  5.                             prev = i;
  6.                             i = vq->vring.desc[i].next;
  7.                    }

这里的sgs指的是接受队列的rq->sg,也就是每个desc chain的长度由rq->sg的长度来决定。所以我们分别看下三种情况是如果初始化rq->sg的。

l  add_recvbuf_mergeable

点击(此处)折叠或打开

  1. static int add_recvbuf_mergeable(struct receive_queue *rq, gfp_t gfp)
  2. {
  3.          struct page *page;
  4.          int err;
  5.  
  6.          page = get_a_page(rq, gfp);
  7.          if (!page)
  8.                    return -ENOMEM;
  9.  
  10.          sg_init_one(rq->sg, page_address(page), PAGE_SIZE);
  11.  
  12.          err = virtqueue_add_inbuf(rq->vq, rq->sg, 1, page, gfp);
  13.          if (err < 0)
  14.                    give_pages(rq, page);
  15.  
  16.          return err;
  17. }

add_recvbuf_mergeable中,首先调用get_a_page分配一个page,然后调用sg_init_one将这个page转换为对应的sg,设置在rq->sg

l  sg_init_one

点击(此处)折叠或打开

  1. void sg_init_one(struct scatterlist *sg, const void *buf, unsigned int buflen)
  2. {
  3.          sg_init_table(sg, 1);
  4.          sg_set_buf(sg, buf, buflen);
  5. }

    首先sg_init_table初始化rq->sg的长度为一个元素,所以从然后调用sg_set_buf将之前的page信息存放在这一个sg中。

点击(此处)折叠或打开

  1. void sg_init_table(struct scatterlist *sgl, unsigned int nents)
  2. {
  3.          memset(sgl, 0, sizeof(*sgl) * nents);
  4.          sg_mark_end(&sgl[nents - 1]);
  5. }

从这个过程可以看出当开启mergeable,时每次rq->sg只有一个元素,所以对应的每个desc chain也只有一个,即每个desc 都是独立的。为什么会这样呢?因为开启mergeable后,后端可以不必只能用一个chain接收数据,既然这样要chain也就没有什么意义了。

下面看普通场景add_recvbuf_small的调用。

l  add_recvbuf_small

点击(此处)折叠或打开

  1. static int add_recvbuf_small(struct receive_queue *rq, gfp_t gfp)
  2. {
  3.          struct virtnet_info *vi = rq->vq->vdev->priv;
  4.          struct sk_buff *skb;
  5.          struct skb_vnet_hdr *hdr;
  6.          int err;
  7.     /*这里MAX_PACKET_LEN的值为1500+ETH_HLEN(14) + VLAN_HLEN(4)*/
  8.          skb = __netdev_alloc_skb_ip_align(vi->dev, MAX_PACKET_LEN, gfp);
  9.          if (unlikely(!skb))
  10.                    return -ENOMEM;
  11.  
  12.          skb_put(skb, MAX_PACKET_LEN);
  13.  
  14.          hdr = skb_vnet_hdr(skb);
  15.          sg_set_buf(rq->sg, &hdr->hdr, sizeof hdr->hdr);
  16.  
  17.          skb_to_sgvec(skb, rq->sg + 1, 0, skb->len);
  18.  
  19.          err = virtqueue_add_inbuf(rq->vq, rq->sg, 2, skb, gfp);
  20.          if (err < 0)
  21.                    dev_kfree_skb(skb);
  22.  
  23.          return err;
  24. }

这个函数首先会按照1500mtu分配一个skb,然后调用sg_set_bufskb_vnet_hdr转换为一个sg放入rq->sg中,紧接着又调用skb_to_sgvecskb的数据放入rq->sg的第二个sg中,所以这种情况每次rq->sg的长度就是2,即第一个用来存放skb_vnet_hdr,第二个用来存放数据。所以这种情况下每个desc chain的长度也是2

下面看如果开启GUEST_GSO/GUEST_TSO的情况。

l  add_recvbuf_big

点击(此处)折叠或打开

  1. static int add_recvbuf_big(struct receive_queue *rq, gfp_t gfp)
  2. {
  3.          struct page *first, *list = NULL;
  4.          char *p;
  5.          int i, err, offset;
  6.  
  7.          /* page in rq->sg[MAX_SKB_FRAGS + 1] is list tail */
  8.          /* MAX_SKB_FRAGS为16,即(65536/PAGE_SIZE + 1), 因为当开启GUEST_GSO/GUEST_TSO
  9.           * 时会使用一个长的chain接收大包, 这里使用17个page分别初始化rq->sg[0]~rq->[sg][17],
  10.           * 其中rq->sg[0]和rq->sg[1]共享一个page*/
  11.          for (i = MAX_SKB_FRAGS + 1; i > 1; --i) {
  12.                    first = get_a_page(rq, gfp);
  13.                    if (!first) {
  14.                             if (list)
  15.                                      give_pages(rq, list);
  16.                             return -ENOMEM;
  17.                    }
  18.                    sg_set_buf(&rq->sg[i], page_address(first), PAGE_SIZE);
  19.  
  20.                    /* chain new page in list head to match sg */
  21.                    first->private = (unsigned long)list;
  22.                    list = first;
  23.          }
  24.  
  25.          first = get_a_page(rq, gfp);
  26.          if (!first) {
  27.                    give_pages(rq, list);
  28.                    return -ENOMEM;
  29.          }
  30.          p = page_address(first);
  31.  
  32.          /* rq->sg[0], rq->sg[1] share the same page */
  33.          /* a separated rq->sg[0] for virtio_net_hdr only due to QEMU bug */
  34.          /* rq->sg[0]用来存放virtio_net_hdr*/
  35.          sg_set_buf(&rq->sg[0], p, sizeof(struct virtio_net_hdr));
  36.  
  37.          /* rq->sg[1] for data packet, from offset */
  38.          /*rq->sg[1]~rq->sg[17]用来存放真正的数据*/
  39.          offset = sizeof(struct padded_vnet_hdr);
  40.          sg_set_buf(&rq->sg[1], p + offset, PAGE_SIZE - offset);
  41.  
  42.          /* chain first in list head */
  43.          first->private = (unsigned long)list;
  44.          err = virtqueue_add_inbuf(rq->vq, rq->sg, MAX_SKB_FRAGS + 2,
  45.                                        first, gfp);
  46.          if (err < 0)
  47.                    give_pages(rq, first);
  48.  
  49.          return err;
  50. }

    当开启GUEST_GSO/GUEST_TSO时,每个desc chain的长度为17,其中第一个用来触发virtio header,其他用来存放数据。也就是说如果希望guest能接受大包,可以有两种方式,一种是开启GUEST_GSO/GUEST_TSO,另一种是开启mergeable。对比之下,前者无论接受大包还是小包都会使用一个长chain来接收,对于小包会造成浪费,后者会使用多个chain来接受;此外,前者的chain长度为17,所以最大收包为65535,而后者没有这个限制

上一篇:vhost_user mergeable 特性
下一篇:vhost-user mergeable对接收端(guest)的影响