(转)Linux NAPI/非NAPI 网卡驱动部分



转载地址: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();

猜你喜欢

转载自blog.csdn.net/m0_37570820/article/details/80249353