歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

Linux內核分析 - 網絡[一]:收發數據包的調用

內核版本:Linux-2.6.34

網卡驅動:B4401

什麼是NAPI

NAPI是linux一套最新的處理網口數據的API,linux 2.5引入的,所以很多驅動並不支持這種操作方式。簡單來說,NAPI是綜合中斷方式與輪詢方式的技術。數據量很低與很高時,NAPI可以發揮中斷方式與輪詢方式的優點,性能較好。如果數據量不穩定,且說高不高說低不低,則NAPI會在兩種方式切換上消耗不少時間,效率反而較低一些。

下面會用到netdev_priv()這個函數,這裡先講解下,每個網卡驅動都有自己的私有的數據,來維持網絡的正常運行,而這部分私有數據放在網絡設備數據後面(內存概念上),這個函數就是通過dev來取得這部分私有數據,注間這部分私有數據不在dev結構體中,而是緊接在dev內存空間後。

static inline void *netdev_priv(const struct net_device *dev)   

{   

  return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);   
       
}

弄清這個函數還得先清楚dev這個結構的分配

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在分配時,即在它的後面分配了private的空間,需要注意的是,這兩部分都是4字節對齊的,如下圖所示,padding是加入的的補齊字節:

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

b44_interrupt():當有數據包收發或發生錯誤時,會產生硬件中斷,該函數被觸發

struct b44 *bp = netdev_priv(dev);

取出網卡驅動的私有數據private,該部分數據位於dev數據後面

istat = br32(bp, B44_ISTAT);   
       
imask = br32(bp, B44_IMASK);

讀出當前中斷狀態和中斷屏蔽字

if (istat) {   
       
         ……   
       
         if (napi_schedule_prep(&bp->napi)) {   
       
                  bp->istat = istat;   
       
                  __b44_disable_ints(bp);   
       
                  __napi_schedule(&bp->napi);   
       
         }

設置NAPI為SCHED狀態,記錄當前中斷狀態,關閉中斷,執行調度

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有關

將napi的poll_list加入到softnet_data隊列尾部,然後引起軟中斷NET_RX_SOFTIRQ。

似乎還沒有真正的收發函數出現,別急;關於軟中斷的機制請參考資料,在net_dev_init()[dev.c]中,注冊了兩個軟中斷處理函數,所以引起軟中斷後,最終調用了net_rx_action()。

open_softirq(NET_TX_SOFTIRQ, net_tx_action);   
       
open_softirq(NET_RX_SOFTIRQ, net_rx_action);

下面來看下net_rx_action()函數實現:

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

     }   

……   

}

api_struct;代碼[2]很簡單了,從poll_list的頭中取出一個napi_struct,然後執行代碼[3],調用poll()函數;注意到這裡在interrupt時,會向poll_list尾部加入一個napi_struct,並引起軟中斷,在軟中斷處理函數中,會從poll_list頭部移除一個napi_struct,進行處理,理論上說,硬件中斷加入的數據在其引起的軟中斷中被處理。

poll函數實際指向的是b44_poll(),這是顯而易見的,但具體怎樣調用的呢?在網卡驅動初始化函數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()。

怎麼到這裡都還沒有收發數據包的函數呢!b44_poll()就是輪詢中斷向量,查找出引起本次操作的中斷;

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)   

              ……   

}

可以看到,查詢了四種中斷: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發送數據包;如果是RX中斷,則調用b44_rx接收數據包。至此,從網卡驅動收發數據包的調用就是如此了。

最後,給個總結性的圖:

Copyright © Linux教程網 All Rights Reserved