什么是NAPI
NAPI是linux一套最新的處理網(wǎng)口數據的API,linux 2.5引入的,所以很多驅動(dòng)并不支持這種操作方式。簡(jiǎn)單來(lái)說(shuō),NAPI是綜合中斷方式與輪詢(xún)方式的技術(shù)。數據量很低與很高時(shí),NAPI可以發(fā)揮中斷方式與輪詢(xún)方式的優(yōu)點(diǎn),性能較好。如果數據量不穩定,且說(shuō)高不高說(shuō)低不低,則NAPI會(huì )在兩種方式切換上消耗不少時(shí)間,效率反而較低一些。
下面會(huì )用到netdev_priv()這個(gè)函數,這里先講解下,每個(gè)網(wǎng)卡驅動(dòng)都有自己的私有的數據,來(lái)維持網(wǎng)絡(luò )的正常運行,而這部分私有數據放在網(wǎng)絡(luò )設備數據后面(內存概念上),這個(gè)函數就是通過(guò)dev來(lái)取得這部分私有數據,注間這部分私有數據不在dev結構體中,而是緊接在dev內存空間后。
static inline void *netdev_priv(const struct net_device *dev)
{
return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);
}
弄清這個(gè)函數還得先清楚dev這個(gè)結構的分配
alloc_netdev() -> alloc_netdev_mq()
struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
void (*setup)(struct net_device *), unsigned int queue_count)
{
……
alloc_size = sizeof(struct net_device);
if (sizeof_priv) {
/* ensure 32-byte alignment of private area */
alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
alloc_size += sizeof_priv;
}
/* ensure 32-byte alignment of whole construct */
alloc_size += NETDEV_ALIGN - 1;
p = kzalloc(alloc_size, GFP_KERNEL);
if (!p) {
printk(KERN_ERR "alloc_netdev: Unable to allocate device./n");
return NULL;
}
……….
}
可以看到,dev在分配時(shí),即在它的后面分配了private的空間,需要注意的是,這兩部分都是4字節對齊的,如下圖所示,padding是加入的的補齊字節:

舉個(gè)例子,假設sizeof(net_device)大小為31B,private大小45B,則實(shí)際分配空間如圖所示:

b44_interrupt():當有數據包收發(fā)或發(fā)生錯誤時(shí),會(huì )產(chǎn)生硬件中斷,該函數被觸發(fā)
struct b44 *bp = netdev_priv(dev);
取出網(wǎng)卡驅動(dòng)的私有數據private,該部分數據位于dev數據后面
istat = br32(bp, B44_ISTAT);
imask = br32(bp, B44_IMASK);
讀出當前中斷狀態(tài)和中斷屏蔽字
if (istat) {
……
if (napi_schedule_prep(&bp->napi)) {
bp->istat = istat;
__b44_disable_ints(bp);
__napi_schedule(&bp->napi);
}
設置NAPI為SCHED狀態(tài),記錄當前中斷狀態(tài),關(guān)閉中斷,執行調度
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);
}
__get_cpu_var():得到當前CPU的偏移量,與多CPU有關(guān)
將napi的poll_list加入到softnet_data隊列尾部,然后引起軟中斷NET_RX_SOFTIRQ。
似乎還沒(méi)有真正的收發(fā)函數出現,別急;關(guān)于軟中斷的機制請參考資料,在net_dev_init()[dev.c]中,注冊了兩個(gè)軟中斷處理函數,所以引起軟中斷后,最終調用了net_rx_action()。
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
下面來(lái)看下net_rx_action()函數實(shí)現:
static void net_rx_action(struct softirq_action *h)
{
struct list_head *list = &__get_cpu_var(softnet_data).poll_list; // [1]
……
n = list_first_entry(list, struct napi_struct, poll_list); // [2]
……
work = 0;
if (test_bit(NAPI_STATE_SCHED, &n->state)) {
work = n->poll(n, weight); // [3]
trace_napi_poll(n);
}
……
}
__get_cpu_var是不是很熟悉,在b44_interrupt()中才向它的poll_list中加入了一個(gè)napi_struct;代碼[2]很簡(jiǎn)單了,從poll_list的頭中取出一個(gè)napi_struct,然后執行代碼[3],調用poll()函數;注意到這里在interrupt時(shí),會(huì )向poll_list尾部加入一個(gè)napi_struct,并引起軟中斷,在軟中斷處理函數中,會(huì )從poll_list頭部移除一個(gè)napi_struct,進(jìn)行處理,理論上說(shuō),硬件中斷加入的數據在其引起的軟中斷中被處理。
poll函數實(shí)際指向的是b44_poll(),這是顯而易見(jiàn)的,但具體怎樣調用的呢?在網(wǎng)卡驅動(dòng)初始化函數b44_init_one()有這樣一行代碼:
netif_napi_add(dev, &bp->napi, b44_poll, 64);
而netif_napi_add()中初始化napi并將其加入dev的隊列,
napi->poll = poll;
這行代碼就是b44_poll賦給napi_poll,所以在NET_RX_SOFTIRQ軟中斷處理函數net_rx_action()中執行的b44_poll()。
怎么到這里都還沒(méi)有收發(fā)數據包的函數呢!b44_poll()就是輪詢(xún)中斷向量,查找出引起本次操作的中斷;
static int b44_poll(struct napi_struct *napi, int budget)
{
……
if (bp->istat & (ISTAT_TX | ISTAT_TO))
b44_tx(bp);
……
if (bp->istat & ISTAT_RX)
work_done += b44_rx(bp, budget);
if (bp->istat & ISTAT_ERRORS)
……
}
可以看到,查詢(xún)了四種中斷:ISTAT_TX、ISTAT_TO、ISTAT_RX、ISTAT_ERRORS
#define ISTAT_TO 0x00000080 /* General Purpose Timeout */
#define ISTAT_RX 0x00010000 /* RX Interrupt */
#define ISTAT_TX 0x01000000 /* TX Interrupt */
#define ISTAT_ERRORS (ISTAT_DSCE|ISTAT_DATAE|ISTAT_DPE|ISTAT_RDU|ISTAT_RFO|ISTAT_TFU)
如果是TX中斷,則調用b44_tx發(fā)送數據包;如果是RX中斷,則調用b44_rx接收數據包。至此,從網(wǎng)卡驅動(dòng)收發(fā)數據包的調用就是如此了。
最后,給個(gè)總結性的圖:

聯(lián)系客服