KVM virtio

    技术2025-10-07  7

    在虚拟化场景,中断的开销更大,所以要尽可能的少用中断,我们从中断处理函数开始看。

    skb_recv_done函数

    static void skb_recv_done(struct virtqueue *rvq) { struct virtnet_info *vi = rvq->vdev->priv; struct receive_queue *rq = &vi->rq[vq2rxq(rvq)]; /* Schedule NAPI, Suppress further interrupts if successful. */ if (napi_schedule_prep(&rq->napi)) { virtqueue_disable_cb(rvq); //关闭中断,vhost不会通知前端 __napi_schedule(&rq->napi); //注册napi,触发软中断 } }

    触发软中断前,关闭vhost中断通知,此时通过poll机制收包。 收包软中断最终会调用napi定义的poll函数,virtio_net定义的是virtnet_poll函数。

    virtnet_poll函数

    static int virtnet_poll(struct napi_struct *napi, int budget) { struct receive_queue *rq = container_of(napi, struct receive_queue, napi); unsigned int r, received; received = virtnet_receive(rq, budget); //接收报文,并上送协议栈 /* Out of packets? */ if (received < budget) { //接收报文小于budget,说明报文已经全部接收完成 r = virtqueue_enable_cb_prepare(rq->vq); //启动vhost触发中断,后端把报文发送到共享环后触发中断 napi_complete(napi); //napi处理完成 if (unlikely(virtqueue_poll(rq->vq, r)) && //判断是否有报文,如果此时有报文则要准备注册napi napi_schedule_prep(napi)) { virtqueue_disable_cb(rq->vq); //关闭vhost触发中断 __napi_schedule(napi); //注册napi,并触发软中断,后续可以继续收包 } } return received; //如果接收报文数超过budget,则主动退出,等到下一次软中处理时再收包 }

    如果报文接收完成,会重新开启中断;否则处理完budget数量的报文后,让出CPU,等待下次处理软中断时再次接收报文。

    关闭和开启中断的函数是通过设置共享环中的flag实现的:

    virtqueue_disable_cb函数

    void virtqueue_disable_cb(struct virtqueue *_vq) { struct vring_virtqueue *vq = to_vvq(_vq); vq->vring.avail->flags |= cpu_to_virtio16(_vq->vdev, VRING_AVAIL_F_NO_INTERRUPT); //关闭中断 }

    virtqueue_enable_cb_prepare函数

    unsigned virtqueue_enable_cb_prepare(struct virtqueue *_vq) { struct vring_virtqueue *vq = to_vvq(_vq); u16 last_used_idx; START_USE(vq); /* We optimistically turn back on interrupts, then check if there was * more to do. */ /* Depending on the VIRTIO_RING_F_EVENT_IDX feature, we need to * either clear the flags bit or point the event index at the next * entry. Always do both to keep code simple. */ vq->vring.avail->flags &= cpu_to_virtio16(_vq->vdev, ~VRING_AVAIL_F_NO_INTERRUPT); //该标记位置0,开启中断 vring_used_event(&vq->vring) = cpu_to_virtio16(_vq->vdev, last_used_idx = vq->last_used_idx); END_USE(vq); return last_used_idx; }

    原文链接:https://blog.csdn.net/one_clouder/article/details/53327362

    Processed: 0.009, SQL: 9