发包
内核协议栈
dev_hard_start_xmit //net\core\dev.c
xmit_one
netdev_start_xmit //include/linux/netdevice.h
__netdev_start_xmit
ops->ndo_start_xmit(skb, dev); 到virtio_net.c 中
||
\/
virtio_net.c中
static const struct net_device_ops virtnet_netdev = {
.ndo_start_xmit = start_xmit,
start_xmit
xmit_skb // 把skb放到vqueue中
virtqueue_add_outbuf
//把数据写到队列中
virtqueue_add //virtio_ring.c
virtqueue_add_split
virtqueue_kick //virtio_ring.c
||
\/
virtqueue_kick
virtqueue_notify
vq->notify(_vq) // agile_nic.c中notify函数,通知板卡驱动给队列中写数据了,然后板卡收到notify后,读取数据
发包没什么好说的,在经历linux 内核协议栈qos后,dev_hard_start_xmit 传递下来的skb 要映射到scatterlist,才能给到后端。因为skb 是gva 地址,只在虚拟机中用。
核心逻辑只是准备sg,更新avail index。
收包:
数据接收流程:
数据接收流程:
napi_gro_receive(&rq->napi, skb);
netif_receive_skb
__netif_receive_skb // 传输skb给网络层
/\
||
驱动 virtio_net.c 中poll方法 napi_poll(n, &repoll); 即virtio_net.c 中 virtnet_poll()
virtnet_poll
virtnet_receive
receive_buf // 接收到的数据转换成skb
//根据接收类型XDP_PASS、XDP_TX等对 virtqueue 中的数据进行不同的处理
skb = receive_mergeable(dev, vi, rq, buf, ctx, len, xdp_xmit,stats); or
skb = receive_big(dev, vi, rq, buf, len, stats); or
skb = receive_small(dev, vi, rq, buf, ctx, len, xdp_xmit, stats);
napi_gro_receive(&rq->napi, skb); // 把skb上传到上层协议栈
schedule_delayed_work //通过你延迟队列接收数据
refill_work
try_fill_recv(vi, rq, GFP_KERNEL);
如果检测到本次中断 receive 数据完成,则重新开启中断
local_bh_enable //enable 软中断 等待下一次中断接收数据
/\
||
中断下半步
执行软中断回调函数 net_rx_action(), 调用 virtio_net.c 中 virtnet_poll()
/\
||
检查poll队列上是否有设备在等待轮询
napi_schedule ->__napi_schedule -> list_add_tail(&napi->poll_list, &sd->poll_list); //把 NAPI 加入到本地cpu的 softnet_data 的 poll_list链表头
__raise_softirq_irqoff(NET_RX_SOFTIRQ); // 调度收包软中断
/\
||
skb_recv_done //virtio_net.c 中 virtnet_find_vqs() 中,数据接收完成回调函数
virtqueue_napi_schedule
调用 napi_schedule
/\
||
每个vq 对应一个数据接收函数 vring_interrupt()
vring_interrupt() //virtio_ring.c
vq->vq.callback(&vq->vq); 即virtio_net.c 中 skb_recv_done
/\
||
中断上半步
pcie网卡发送数据给host时,会触发pci msix硬中断,然后host driver agile_nic.c 中执行回调函数vring_interrupt
收包会复杂点,涉及中断上半部和下半部。
上半部由硬中断触发,回调是vring_interrupt;上半部回调很简单,什么也没做,就是挂个napi 到本地cpu的softnet_data->poll_list链表,并通过raise_softirq;触发网络收包软中断。其软中断的实际回调是通过open_softirq(NET_RX_SOFTIRQ, net_rx_action);注册的。后续在合适时机,内核会调用本地cpu对应的软中断回调,以定期清理链表;
下半部走软中断,回调是 net_rx_action()->napi_poll()通过napi->poll指针指向驱动特定回调,丢与virtio来说是virtnet_poll,从而获取对应网卡接收队列中的数据包。期间,有两个比较有趣的点:禁用硬中断剪切sd->poll_list的操作,比较有趣;另一个是gro 收到完整报文后,再上送协议栈的hook,等于在驱动层做了个缓冲区。
我做了一张图:
参考文档:
https://blog.csdn.net/wangquan1992/article/details/120649182