一、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_SOFTIRQ
list_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_action
queue = &__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;
Else
dev_put(dev) //释放该设备
......
3、dev->poll,e100驱动在PCI总线探测回调中设置该函数回调为e100_poll
e100_poll
work_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_complete
list_del(&dev->poll_list);
clear_bit(__LINK_STATE_RX_SCHED, &dev->state);
e100_enable_irq
Return 0
*budget -= work_done; //将总的接收配额及设备接收配额递减,并返回还有待
netdev->quota -= work_done; //处理的数据(retur 1)
Return 1
二、非NAPI方式处理数据包接收(始终靠网卡的硬中断来处理收包)
1、de600网卡驱动接收中断触发
de600_rx_intr
netif_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_backlog
process_backlog
quota = 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);
//处理注册到基本包类型的最后一个符合条件的包。
esle
kfree_skb(skb)
ret = NET_RX_DROP;
//不识别的类型包,丢弃处理。
网卡驱动收包
最新推荐文章于 2022-09-22 18:15:00 发布