NAPI 的核心在于:在一个繁忙网络,每次有网络数据包到达时,不需要都引发中断,因为高频率的中断可能会影响系统的整体效率,在高流量下,网卡产生的中断可能达到每秒几千次,而如果每次中断都需要系统来处理,是一个很大的压力,而NAPI 使用轮询时是禁止了网卡的接收中断的,这样会减小系统处理中断的压力,NAPI 是Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后POLL 的方法来轮询数据,(类似于底半(bottom-half)处理模式);
但是NAPI 存在一些比较严重的缺陷:而对于上层的应用程序而言,系统不能在每个数据包接收到的时候都可以及时地去处理它,而且随着传输速度增加,累计的数据包将会耗费大量的内存,经过实验表明在Linux 平台上这个问题会比在FreeBSD 上要严重一些;另外采用NAPI 所造成的另外一个问题是对于大的数据包处理比较困难,原因是大的数据包传送到网络层上的时候耗费的时间比短数据包长很多(即使是采用DMA 方式),所以正如前面所说的那样,NAPI 技术适用于对高速率的短长度数据包的处理,
核心API:
1. __netif_rx_schedule(dev)
这个函数被中断服务程序调用,将设备的POLL 方法添加到网络层次的POLL 处理队列中去,
排队并且准备接收数据包,在使用之前需要调用netif_rx_reschedule_prep,并且返回的数为1,
并且触发一个NET_RX_SOFTIRQ 的软中断通知网络层接收数据包。
2. netif_rx_schedule_prep(dev)
确定设备处于运行,而且设备还没有被添加到网络层的POLL 处理队列中,
在调用netif_rx_schedule之前会调用这个函数。
3. netif_rx_complete(dev)
把当前指定的设备从POLL 队列中清除,通常被设备的POLL 方法调用,
注意如果在POLL 队列处于工作状态的时候是不能把指定设备清除的,否则将会出错。
static inline void __netif_rx_schedule(struct net_device *dev)
{
unsigned long flags;
local_irq_save(flags);
dev_hold(dev);
list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
if (dev->quota < 0)
dev->quota += dev->weight;
else
dev->quota = dev->weight;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
local_irq_restore(flags);
}
static inline void netif_rx_schedule(struct net_device *dev)
{
if (netif_rx_schedule_prep(dev))
__netif_rx_schedule(dev);
}
#define athr_mac_rx_sched_prep(m, d) napi_schedule_prep(&m->napi)
#define __athr_mac_rx_sched(m, d) __napi_schedule(&m->napi)
#define athr_mac_rx_sched(m) napi_schedule(&m->napi)
二:NET_RX_SOFTIRQ软终端的注册:
OS默认定义的软终端向量如下:
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
在内核的linux\kernels\mips-linux-2.6.15\net\core\Dev.c 文件中有如下代码:
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);//注册NET_RX_SOFTIRQ。内核软中断函数net_rx_action();
三:NET_RX_SOFTIRQ软中断的设置:
我们不能直接对软终端处理函数直接进行调用,在调用之前必须对其进行中断向量位设置。
/**
* __napi_schedule - schedule for receive
* @n: entry to schedule
*
* The entry's receive function will be scheduled to run
*/
void __napi_schedule(struct napi_struct *n)
{
unsigned long flags;
local_irq_save(flags);
list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
local_irq_restore(flags);
}
四:NET_RX_SOFTIRQ软件中断的执行
之前看到过如下函数:
do_IRQ(ATH_CPU_IRQ_GE0);其定义如下:
unsigned int do_IRQ(int irq, struct uml_pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs((struct pt_regs *)regs);
irq_enter();
__do_IRQ(irq);
irq_exit();
set_irq_regs(old_regs);
return 1;
}
由do_IRQ()函数可以知道,当执行完硬中断__do_IRQ(irq)后,会执行irq_exit()这个函数,
* Exit an interrupt context. Process softirqs if needed and possible:
*/
void irq_exit(void)
{
account_system_vtime(current);
trace_hardirq_exit();
sub_preempt_count(IRQ_EXIT_OFFSET);
if (!in_interrupt() && local_softirq_pending()) //判断时候处于硬件中断嵌套,同时是否已经注册了软中断。
{
invoke_softirq(); //这个函数是一个宏定义,定义如下:
}
#ifdef CONFIG_NO_HZ
/* Make sure that timer wheel updates are propagated */
rcu_irq_exit();
if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
tick_nohz_stop_sched_tick(0);
#endif
preempt_enable_no_resched();
}
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq() __do_softirq()
#else
# define invoke_softirq() do_softirq()
#endif
do_softirq()定义如下:
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
if (in_interrupt()) //如果有硬终端存在,则返回。
return;
local_irq_save(flags);
pending = local_softirq_pending(); //当有软中断注册时,对其进行调用。
if (pending)
__do_softirq();
local_irq_restore(flags);
}
__do_softirq()函数定义如下:
#define MAX_SOFTIRQ_RESTART 10
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();
account_system_vtime(current);
__local_bh_disable((unsigned long)__builtin_return_address(0));
lockdep_softirq_enter();
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
local_irq_enable();
h = softirq_vec;
do {
if (pending & 1) {
int prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(h - softirq_vec);
trace_softirq_entry(h, softirq_vec);
/*
//对注册的软终端进行函数调用处理。最终会调用我们前面通过open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
注册的函数static void net_rx_action(struct softirq_action *h),
在函数net_rx_action()中就可以调用我们网络驱动的poll()函数。
实现数据的查询接受。
*/
h->action(h);
trace_softirq_exit(h, softirq_vec);
if (unlikely(prev_count != preempt_count())) {
printk(KERN_ERR "huh, entered softirq %td %s %p"
"with preempt_count %08x,"
" exited with %08x?\n", h - softirq_vec,
softirq_to_name[h - softirq_vec],
h->action, prev_count, preempt_count());
preempt_count() = prev_count;
}
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
local_irq_disable();
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
if (pending)
wakeup_softirqd();
lockdep_softirq_exit();
account_system_vtime(current);
_local_bh_enable();
}