转载地址:https://blog.csdn.net/hui6075/article/details/51236203
收包:
NAPI:中断来了,在上半部中把net_device加入poll_list,sk_buff仍然在设备自身队列中,然后下半部软中断过程中轮询所有的设备,用设备的poll函数把自己的sk_buff交给上层。
非NAPI:中断来了,在上半部中把sk_buff放到本CPU的多设备共享队列中,然后下半部软中断过程中用默认poll函数(process_backlog)处理共享队列中的包。除了NAPI和非NAPI,还有NETPOLL,CPU去轮询网卡。可以绕过协议栈收取skb。可以在debug时使用。
===================上半部参考各网卡设备驱动===========================
比如do_IRQ()->e1000_intr(),NAPI把设备挂到本地CPU的softnet_data队列/非NAPI把sk_buff挂到CPU共享队列,这些事情就在这个函数中完成的。
===================NAPI/非NAPI 网卡驱动下半部=========================
net_rx_action(软中断号) //这儿NAPI/非NAPI都适用
{
取本CPU的softnet_data queue;//共享队列
计算budget=300;//一次budget能取的包的数量,NAPI时每个设备有自己的私有队列和budget,16/32/64...
关本地中断;
while(queue->poll_list不为空) //遍历本地CPU上等待轮询的设备
if(budget<=0 || 运行时间超过1个jiffies)
goto softnet_break;
开本地中断;//因为poll_list不空,其他设备只会往list尾加设备,因此可以开了中断再取设备
取设备dev;
if(dev配额足够)
process_backlog(dev) / e1000_clean(dev); //dev->poll()
if dev->poll没用完设备配额: //其实这个只针对NAPI有效,非NAPI时所有设备共享一个budget和queue,所以这么做没有什么意义
将设备dev移到链表尾部;
else
减少设备引用计数;
关本地中断;
out:
开本地中断;
返回;
softnet_break:
netdev_rx_stat->time_squeeze++;//非NAPI时保存接收帧的统计信息的结构体
触发下一次软中断;
goto out;
}
process_backlog(net_device) //非NAPI时所有设备都用这个函数作为默认的dev->poll接口
{
for(;;)
关本地中断;//因为处理的时候有可能有设备在向共享队列中挂包
从本CPU的共享队列struct softnet_data中取sk_buff;
如果取不到sk_buff,goto job_done;
开本地中断;
调用netif_receive_skb()把sk_buff传递给上层协议栈;==================>
如果处理包的数量或者收包时间达到上限,跳出循环;
把设备配额quota减去本次处理包的数量;
job_done:
将net_device从poll_list中删除;
置throttle为0;//cpu没有超载
开本地中断;
}
e1000_clean(net_device) //NAPI时e1000的dev->poll()接口
-|如果设备被关闭或禁止,返回
-|e1000_clean_tx_irq()
-|e1000_unmap_and_free_tx_resource() //此时数据已经发送给网卡发送队列了,所以挨个回收DMA ring中的skb->data资源。在内核准备好sk_buff之后再映射?
-|e1000_clean_rx_irq()
-|pci_unmap_single() //对于已经copy以太包的skb->data,解除其DMA映射
-|skb_put() //删除以太帧尾4字节
-|netif_receive_skb() //把以太包交付给上层协议栈
-|e1000_alloc_rx_buffers() //重新映射流式DMA
发包:
net_tx_action() //网络发包软中断,比如NIC的TX FIFO可用了之后,调用此函数
{
获取本CPU的softnet_data sd;
对于已经发送成功、可以释放的缓冲区,关中断并且把sd->completion_queue给清了(__kfree_skb);
对于每个要发送的设备(sd->output_queue):
自旋成功:
qdisc_run(dev)->qdisc_restart(dev);
自旋失败:
netif_schedule();
}
qdisc_restart()
{
struct Qdisc *q = dev->qdisc;
对于调用q->dequeue(q)获得待发送的skb:
自旋成功:
调用dev->hard_start_xmit(skb, dev)发送一个帧;//e1000_xmit_frame()
}
e1000_xmit_frame()
{
检查len/mss/checksum;
调用e1000_tx_map()申请流式DMA映射,把skb存到网卡DMA中;
调用e1000_tx_queue()往寄存器写命令,网卡应该收到命令之后自己会发送。
}
netif_schedule(dev)
{
把设备挂到调度list上,后面再触发软中断net_tx_action();