一、NAPI方式处理数据包接收(中断与轮询配合,由硬件中断触发接收处理,之后关闭网卡硬中断,当处理完成后,再将网卡硬中断开启,在高负荷工作情况下,可以减少因频繁切换中断上下文造成的CPU开销)
1、e100网卡驱动接收中断触发
e100_intr
//e100网卡驱动中,在如下流程中设置中断回调
//netdev->open (当应用层执行ifup时,调用了设备回调netdev->open)
//e100_open
// e100_up
// request_irq(nic->pdev->irq, e100_intr, IRQF_SHARED,...)
// e100_enable_irq(nic)u8 stat_ack = readb(&nic->csr->scb.stat_ack)if(stat_ack == stat_ack_not_ours ||stat_ack == stat_ack_not_present)return IRQ_NONE;writeb(stat_ack, &nic->csr->scb.stat_ack)if(stat_ack & stat_ack_rnr)nic->ru_running = RU_SUSPENDED;if(likely(netif_rx_schedule_prep(netdev)))//如果设置为打开状态(__LINK_STATE_START)并且先前还未进行接收调度//(__LINK_STATE_RX_SCHED),则条件满足e100_disable_irq(nic); //关闭硬件中断__netif_rx_schedule(netdev)//将当前设备加入到每CPU的轮询设备列表中//分配当前设备的接收报文配额//触发软中断NET_RX_SOFTIRQlist_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);dev->quota = dev->weight;__raise_softirq_irqoff(NET_RX_SOFTIRQ);2、软中断NET_RX_SOFTIRQ中断函数,之前在net_dev_init中初始化
net_rx_actionqueue = &__get_cpu_var(softnet_data)//获取每CPU软中断处理队列budget = netdev_budget;//获取总的执行配额while (!list_empty(&queue->poll_list))//遍历轮询设备列表中每个需要接收的设备if (budget <= 0 || jiffies - start_time > 1)//当总的收包配额使用完,或者已经执行了1秒钟则暂时退出轮询处理goto softnet_break;dev = list_entry(queue->poll_list.next,....)//从轮询设备列表中取出第一个待处理的设备if (dev->quota <= 0 || dev->poll(dev, &budget))//如果在处理的设备自己的收包配额使用完,还有未接收的数据,则当前设备//轮询列表首部移动轮询列表尾部,同时更新此设备的接收配额。list_move_tail(&dev->poll_list, &queue->poll_list);dev->quota = dev->weight;Elsedev_put(dev) //释放该设备......3、dev->poll,e100驱动在PCI总线探测回调中设置该函数回调为e100_poll
e100_pollwork_to_do = min(netdev->quota, *budget)//budget是调用dev->poll时传入的参数,budget是设备核心模块总的一次轮询配额,这//里用当前设备的收包配额和总的收包配额进行对比,取出最小接收包配额值。e100_rx_clean(nic, &work_done, work_to_do);//这里从网卡中获取数据包,并调用netif_receive_skb函数进行上层协议处理,这里暂//时不进行分析。if((!tx_cleaned && (work_done == 0)) || !netif_running(netdev))//如果发送和接收都处理完成,或者设备未开启,则将设备从轮询列表删除,清除正在//接收的调度标记,同时开启硬中断netif_rx_completelist_del(&dev->poll_list);clear_bit(__LINK_STATE_RX_SCHED, &dev->state);e100_enable_irqReturn 0*budget -= work_done; //将总的接收配额及设备接收配额递减,并返回还有待netdev->quota -= work_done; //处理的数据(retur 1)Return 1二、非NAPI方式处理数据包接收(始终靠网卡的硬中断来处理收包)
1、de600网卡驱动接收中断触发
de600_rx_intrnetif_rx(skb)//接收中断触发后执行netif_rx进行接收包处理if (netpoll_rx(skb))return NET_RX_DROP;if (!skb->tstamp.off_sec)net_timestamp(skb);//skb->tstamp,更新收包的时间戳queue = &__get_cpu_var(softnet_data);//获取每CPU软中断处理队列if (queue->input_pkt_queue.qlen <= netdev_max_backlog)//如果接收包的队列没满则继续处理,否则丢弃。if (queue->input_pkt_queue.qlen)//如果队列中已经有值,表明软中断正在处理,直接将包放入队列尾。enqueue:__skb_queue_tail(&queue->input_pkt_queue, skb);return NET_RX_SUCCESS;netif_rx_schedule(&queue->backlog_dev);if (netif_rx_schedule_prep(dev))__netif_rx_schedule(dev)//接口为开启,先没有进行接收调度,则触发接收调度,将设备//加入到轮询设备列表中,同时触发接收软中断。注意这里的向//轮询列表加入的设备是虚拟的backlog_dev,同时接收软中断在//调用设备接收回调函数也是使用的这个虚拟设备的接收函数。goto enqueue;//如果队列是空的,则激活软中断,同时将包放入队列中。kfree_skb(skb)return NET_RX_DROP;//在上面判断队列已经满了,则新来的包丢弃处理。2、接收软中断任务在上面已经描述过了,接收软中断任务主要从设备轮询列表调用对应设备的poll回调函数,非NAPI方式的驱动在上面使用的是虚拟设备backlog_dev,该设备的回调函数在net_dev_init初始化时已经设置为process_backlogprocess_backlogquota = min(backlog_dev->quota, *budget)//从总的收包配额,与设备的收包配额中选取一个较小的值for (;;)skb = __skb_dequeue(&queue->input_pkt_queue);//从接收队列中获取一个包,如果队列已空,则返回接收完成。if (!skb)goto job_done;dev = skb->dev;netif_receive_skb(skb);//调用上层接口进行包处理work++;if (work >= quota || jiffies - start_time > 1)//如果已经达到了收包限额,或者执行了1秒中,则退出循环break;backlog_dev->quota -= work;
*budget -= work;
return -1;
//将设备限额和总收包限额递减,返回-1表明还有包需要处理job_done:
backlog_dev->quota -= work;
*budget -= work;
list_del(&backlog_dev->poll_list);
//接收队列中已经没有包需要处理,则递减包限额后,从轮询列表中删除虚拟设备。netif_poll_enable(backlog_dev);
//将虚拟设备状态去除__LINK_STATE_RX_SCHED,表明已经接收调度已经没有运行。三、netif_receive_skb
if (skb->dev->poll && netpoll_rx(skb))
//如果netpoll处理,则这里直接丢弃包,不再向上层传递。return NET_RX_DROP;if (!skb->tstamp.off_sec)
//更新时间戳net_timestamp(skb);if (!skb->input_dev)skb->input_dev = skb->dev;orig_dev = skb_bond(skb);
//如果有绑定功能,则获取绑定主设备,否则不支持绑定功能,则取当前获取包的设备//初始化skb一些参数值
skb->h.raw = skb->nh.raw = skb->data;
skb->mac_len = skb->nh.raw - skb->mac.raw;pt_prev = NULL;list_for_each_entry_rcu(ptype, &ptype_all, list)
//将包传给所有嗅探器,这里ptype_all是指可以处理任何二层以上的包类型列表。通常应用
//层实现嗅探器创建的套接口类型为socket(AF_PACKET,SOCK_RAW,htons(ETH_P_ALL)),
//则系统就会调用dev_add_pack向ptype_all包类型列表中添加。这里嗅探器得到接收的所有
//报文,同样也会得到所有发送的报文。在dev_add_pack函数执行时,如果包类型为
//ETH_P_ALL,则递增全局变量netdev_nit,之后dev_hard_start_xmit发包函数中判断如果
//netdev_nit大于0,则会把发出的包放到当前嗅探器的套接收接收队列中。if (!ptype->dev || ptype->dev == skb->dev)if (pt_prev)deliver_skb(skb, pt_prev, orig_dev);pt_prev->func(skb, skb->dev, pt_prev, orig_dev);pt_prev = ptype;if (handle_bridge(&skb, &pt_prev, &ret, orig_dev))
//如果二层桥已经处理,则返回goto out;type = skb->protocol;
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list)
//处理所有注册到基本包类型的包。(最后一个不做处理)if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev))if (pt_prev)ret = deliver_skb(skb, pt_prev, orig_dev);pt_prev->func(skb, skb->dev, pt_prev, orig_dev);pt_prev = ptype;if (pt_prev)ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);//处理注册到基本包类型的最后一个符合条件的包。
eslekfree_skb(skb)ret = NET_RX_DROP;//不识别的类型包,丢弃处理。