天天看點

【Linux4.1.12源碼分析】virtio_net之NAPI機制

在虛拟化場景,中斷的開銷更大,是以要盡可能的少用中斷,我們從中斷處理函數開始看。

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;
}
           

繼續閱讀