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();
}