VIRTIO中的前后端配合限速分析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/leoufung/article/details/53584970

VIRTIO中的前后端配合限速分析

 

在VIRTIO中,有个一个设备的特性叫做VIRTIO_RING_F_EVENT_IDX,这个特性是用来对前后端速率进行匹配限速的。

 

一、背景知识

我们这里先看avail ring,这个ring有两个用途,一是发送侧(send queue)前端驱动发送报文的时,将待发送报文加入avail ring等待后端的处理,后端处理完后,会将其放入used ring,并由前端将其释放desc中(free_old_xmit_skbs, detach_buf),最后通过try_fill_recv重新装入availring中; 二是接收侧(receive qeueu),前端将空白物理块加入avail ring中,提供给后端用来接收报文,后端接收完报文会放入used ring。可以看出都是后端用完前端的avail ring的东西放入used ring。本特性也就用了used ring的最后一个元素,用来告诉前端驱动后端处理到哪个avail ring上的元素了。

 

QEMU中的virtqueue_pop

 

void *virtqueue_pop(VirtQueue *vq, size_tsz)

{

……

    i = head = virtqueue_get_head(vq, vq->last_avail_idx++);           

    if (virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) {      

        vring_set_avail_event(vq,vq->last_avail_idx);                                                                                                                                 

    }

//处理报文,进行发送                                                                 

…..

}

 

static inline voidvring_set_avail_event(VirtQueue *vq, uint16_t val)                                                                                                                  

{                                                                      

…….

     pa = vq->vring.used + offsetof(VRingUsed,ring[vq->vring.num]);    

    virtio_stw_phys(vq->vdev, pa, val);                                

}

 

last_avail_idx 表示前端处理到availring的哪个元素了。++之后表示下次待处理的avail ring的哪个元素,并将这个信息放入了used ring的最后一个元素。前端驱动通过读取usedring的最后一个元素就知道后端处理到哪里了。

 

 

 

看一下前端如何构造发送报文

 

static int xmit_skb(struct send_queue *sq,struct sk_buff *skb)

{

……

if (can_push) {

…….

         }else {

//将控制头和SKB放入物理块buf scatter-gatherlist中

                   sg_set_buf(sq->sg,hdr, hdr_len);

                   num_sg= skb_to_sgvec(skb, sq->sg + 1, 0, skb->len) + 1;

         }

         //将sg加入到avail ring中

         returnvirtqueue_add_outbuf(sq->vq, sq->sg,num_sg, skb, GFP_ATOMIC);

}

将guest内核中待发的skb关联到sg中

 

 

int virtqueue_add_outbuf()

{

         returnvirtqueue_add(vq, &sg, sg_next_arr, num, 0, 1, 0, data, gfp);

}

 

static inline int virtqueue_add(……)

{

……

/*取得第一个可用的空闲desc进行逻辑buf的组建*/

         head= i = vq->free_head;

//将sg 的内容关联到desc中,组建逻辑buf

 

/*更新free head,其前面的都在这次构造逻辑buf的过程中被使用了*/

         vq->free_head= i; /*liufeng*/

 

add_head:

……

         //本次应该用avail ring中的哪个元素,avail ring是个环形数组,这里通过&达到取余的目的

         avail = (vq->vring.avail->idx &(vq->vring.num-1));

         //记录本次逻辑buf的起始desc索引号

         vq->vring.avail->ring[avail] = head;

         //++使得avail->idx执行了下一次使用avail ring的哪个元素

         vq->vring.avail->idx++;

         //记录增加了几个可用的avail ring元素

         vq->num_added++;

 

……..

}

将空闲的desc中取得desc,记录好关联好skb的sg。然后将所有的desc组成一个逻辑buf,并将起始的desc索引号记录到一个avail ring中,表示新增了一个最新的可用的逻辑buf到avail ring了。同时更新avail->idx加1,avail->idx表示了avail ring中的最新可用元素。vq->num_added表示比上次,avail ring中加入了几个新的可用逻辑buf(几个新的avail ring的元素)

 

static netdev_tx_t start_xmit(structsk_buff *skb, struct net_device *dev)

{

……

         //组装完逻辑buf并填充到availring中,更新了最新可以用的avail.idx

         err= xmit_skb(sq, skb); /*liufeng*/

….

         //通知后端

virtqueue_kick(sq->vq);

 

…..,.

}

组装完逻辑buf后,开始通知后端QEMU进行发送

 

二、从发送报文看前后端匹配限度

Avail ring的限速是在前端的virtqueue_kick_prepare中限制的

 

void virtqueue_kick(struct virtqueue *vq)

{  

         if(virtqueue_kick_prepare(vq))

                   virtqueue_notify(vq);

}

 

前端通过virtqueue_kick_prepare判断是否通知后端有新的avail ring的逻辑buf可用来达到限速的效果

 

bool virtqueue_kick_prepare(structvirtqueue *_vq)

{

……

         old= vq->vring.avail->idx - vq->num_added;

         new= vq->vring.avail->idx;

……

 

         if(vq->event) {

                   needs_kick =vring_need_event(vring_avail_event(&vq->vring), new, old);

         }

……

         returnneeds_kick;

}

 

old 表示上次添加的avail ring元素的位置

new 表示最新avail ring元素的位置(下一次添加availring的位置)

 

#define vring_avail_event(vr) (*(__u16*)&(vr)->used->ring[(vr)->num])

也就是used ring的最后一个元素,就是后端填入的信息,表示处理到哪个avail ring元素了,即event_idx

 

static inline int vring_need_event(__u16event_idx, __u16 new_idx, __u16 old)

{

         return (__u16)(new_idx - event_idx - 1) < (__u16)(new_idx- old);

}

这个公式决定了是否想后端QEMU发送通知

 

 

当满足公式的时候,后端处理的位置event_idx超过了old,表示后端QEMU处理的速度够快,索引返回true,通知(kick)后端QEMU,通知QEMU有新的avail 逻辑buf,请你继续处理

 

如下下面情况

后端处理的位置event_idx落后于上次添加avail ring的位置,说明后端处理较慢,返回false,那么前端就先不通知(kick),积攒一下,反正QEMU正处理不过来,下次退出的时候,让QEMU一起尽情处理

 

三、其他限速情形

Receive queue的avail ring情况,在前端的try_fill_recv中

new表示新添加的空白逻辑buf的avail ring位置

old表示上一个空白逻辑buf的avail ring位置

event_idx 表示后端接收报文用到哪个availring了

返回true,后端QEMU接收报文速度比较快,加完空白逻辑buf后要赶紧通知后端,不然就不够用了

 

 

返回false,表示QEMU接收的速度比较慢,后端还有足够的空白逻辑buf,暂时不用通知QEMU,下次QEMU收报文直接用就可以了

 

对于used ring的限速在QEMU的virtio_should_notify中进行,前端qemu在avail ring的最后一个元素中记录了前端处理used ring的位置,即event_idx

 

返回true,表示前端处理的速度较快,能力足够,一旦后端接收报文到used ring中就马上notify后端

 

返回false,表示前端处理比较慢,后端QEMU接收完报文到used ring后,也暂时不要notify前端,即不要打断虚拟机,让虚拟机尽情处理。

 

 

 

 

展开阅读全文

没有更多推荐了,返回首页