虽然以前做网卡和swi芯片驱动,但是也仅限于注册中断而已,大部分还是参考别人的框架,那时候是tasklet机制,开始接触和了解NAPI要从调试网络性能说起,现在项目用的内核比较新是2.6.32的,而我这里要分析是3.1.1的大同小异.下面说一下NAPI需要的条件:
驱动可以继续使用老的 2.4 内核的网络驱动程序接口,NAPI 的加入并不会导致向前兼容性的丧失,但是 NAPI 的使用至少要得到下面的保证:
A. 要使用 DMA 的环形输入队列(也就是 ring_dma,这个在 2.4 驱动中关于 Ethernet 的部分有详细的介绍),或者是有足够的内存空间缓存驱动获得的包。
B. 在发送/接收数据包产生中断的时候有能力关断 NIC 中断的事件处理,并且在关断 NIC 以后,并不影响数据包接收到网络设备的环形缓冲区(以下简称 rx-ring)处理队列中。
这里本人比较懒,没有打字,可以参考《深入理解linux网络内幕》章节. 而网上搜集的资料大部分也都来自一篇ibm工作人员的文档,但是我觉得写的还行,我这里主要说流程和框架,而不在于某个细节的具体优化工作. 下面就贴出来基本框架图:
这里首先看中断例程里主要做了什么工作,这里我随意在源码里找了一个例子:
/*
* rx/tx dma interrupt handler
*/
static irqreturn_t XXX_isr(int irq, void *dev_id)
{
struct net_device *dev;
struct bcm_enet_priv *priv;
dev = dev_id;
priv = netdev_priv(dev);
/***/
.......
napi_schedule(&priv->napi);
return IRQ_HANDLED;
}
注册中断例程:
ret = request_irq(priv->irq_rx, bcm_enet_isr_dma,XXX, dev->name, dev);
这里我们看napi_schedule()具体做了什么工作:
/**
* napi_schedule - schedule NAPI poll
* @n: napi context
*
* Schedule NAPI poll routine to be called if it is not already
* running.
*/
static inline void napi_schedule(struct napi_struct *n)
{
if (napi_schedule_prep(n))
__napi_schedule(n);
}
最后展开是调用了它:
/* Called with irq disabled */
static inline void ____napi_schedule(struct softnet_data *sd,
struct napi_struct *napi)
{
list_add_tail(&napi->poll_list, &sd->poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}
它的作用就是网卡的数据链表添加到poll_list里,然后开启软中断. 这里比较有意思的是:
raise_softirq_irqoff(NET_RX_SOFTIRQ);我们具体看一下:
/*
* This function must run with irqs disabled!
*/
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
/*
* If we're in an interrupt or softirq, we're done
* (this also catches softirq-disabled code). We will
* actually run the softirq once we return from
* the irq or softirq.
*
* Otherwise we wake up ksoftirqd to make sure we
* schedule the softirq soon.
*/
if (!in_interrupt())
wakeup_softirqd();
}
/*
* we cannot loop indefinitely here to avoid userspace starvation,
* but we also don't want to introduce a worst case 1/HZ latency
* to the pending events, so lets the scheduler to balance
* the softirq load for us.
*/
static void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __this_cpu_read(ksoftirqd);
if (tsk && tsk->state != TASK_RUNNING)
wake_up_process(tsk);
}
它的工作就是唤醒ksoftirqd内核线程触发do_softirq,然后调用net_rx_action.
这里还需要特别说明的是关于XXX_poll这个函数,作为驱动或内核开发者,可以自己写一个,也可以用系统默认的poll函数,那它就是process_backlog.
看net/core/dev.c中我们知道它的初始化时在static int __init net_dev_init(void)中,
sd->backlog.poll = process_backlog;
sd->backlog.weight = weight_p;
sd->backlog.gro_list = NULL;
sd->backlog.gro_count = 0;
而sd->backlog的调用是在netif_rx中调用enqueue_to_backlog而间接调用. 也就是原来的netif_rx的处理方法和现在NAPI达到了一个和谐的统一,它也会走软中断处理的流程. 如果有自己写的poll函数,一般都会在具体驱动初始化的时候已经对poll赋值,看net_rx_action中对poll的调用:
work = n->poll(n, weight);
接下来的工作就是把skb里数据包传递给上层那么这里需要一个接口函数,那么它就是netif_receive_skb.
阅读(1299) | 评论(0) | 转发(0) |