NAPI之(一)——原理和實現

概述

 

NAPI是Linux新的網卡數據處理API,據說是由於找不到更好的名字,所以就叫NAPI(New API),在2.5之後引入。

簡單來說,NAPI是綜合中斷方式與輪詢方式的技術。

中斷的好處是響應及時,如果數據量較小,則不會佔用太多的CPU事件;缺點是數據量大時,會產生過多中斷,

而每個中斷都要消耗不少的CPU時間,從而導致效率反而不如輪詢高。輪詢方式與中斷方式相反,它更適合處理

大量數據,因爲每次輪詢不需要消耗過多的CPU時間;缺點是即使只接收很少數據或不接收數據時,也要佔用CPU

時間。

NAPI是兩者的結合,數據量低時採用中斷,數據量高時採用輪詢。平時是中斷方式,當有數據到達時,會觸發中斷

處理函數執行,中斷處理函數關閉中斷開始處理。如果此時有數據到達,則沒必要再觸發中斷了,因爲中斷處理函

數中會輪詢處理數據,直到沒有新數據時纔打開中斷。

很明顯,數據量很低與很高時,NAPI可以發揮中斷與輪詢方式的優點,性能較好。如果數據量不穩定,且說高不高

說低不低,則NAPI則會在兩種方式切換上消耗不少時間,效率反而較低一些。

 

實現

 

來看下NAPI和非NAPI的區別:

(1) 支持NAPI的網卡驅動必須提供輪詢方法poll()。

(2) 非NAPI的內核接口爲netif_rx(),NAPI的內核接口爲napi_schedule()。

(3) 非NAPI使用共享的CPU隊列softnet_data->input_pkt_queue,NAPI使用設備內存(或者

設備驅動程序的接收環)。

 

(1) NAPI設備結構


  1. /* Structure for NAPI scheduling similar to tasklet but with weighting */  
  2.   
  3. struct napi_struct {  
  4.     /* The poll_list must only be managed by the entity which changes the 
  5.      * state of the NAPI_STATE_SCHED bit. This means whoever atomically 
  6.      * sets that bit can add this napi_struct to the per-cpu poll_list, and 
  7.      * whoever clears that bit can remove from the list right before clearing the bit. 
  8.      */  
  9.     struct list_head poll_list; /* 用於加入處於輪詢狀態的設備隊列 */  
  10.     unsigned long state; /* 設備的狀態 */  
  11.     int weight; /* 每次處理的最大數量,非NAPI默認爲64 */  
  12.     int (*poll) (struct napi_struct *, int); /* 此設備的輪詢方法,非NAPI爲process_backlog() */  
  13.   
  14. #ifdef CONFIG_NETPOLL  
  15.     ...  
  16. #endif  
  17.   
  18.     unsigned int gro_count;  
  19.     struct net_device *dev;  
  20.     struct list_head dev_list;  
  21.     struct sk_buff *gro_list;  
  22.     struct sk_buff *skb;  
  23. };  

(2) 初始化

初始napi_struct實例。


  1. void netif_napi_add(struct net_device *dev, struct napi_struct *napi,  
  2.         int (*poll) (struct napi_struct *, int), int weight)  
  3. {  
  4.     INIT_LIST_HEAD(&napi->poll_list);  
  5.     napi->gro_count = 0;  
  6.     napi->gro_list = NULL;  
  7.     napi->skb = NULL;  
  8.     napi->poll = poll; /* 設備的poll函數 */  
  9.     napi->weight = weight; /* 設備每次poll能處理的數據包個數上限 */  
  10.   
  11.     list_add(&napi->dev_list, &dev->napi_list); /* 加入設備的napi_list */  
  12.     napi->dev = dev; /* 所屬設備 */  
  13.   
  14. #ifdef CONFIG_NETPOLL  
  15.     spin_lock_init(&napi->poll_lock);  
  16.     napi->poll_owner = -1;  
  17. #endif  
  18.     set_bit(NAPI_STATE_SCHED, &napi->state); /* 設置NAPI標誌位 */  
  19. }  

 

(3) 調度

在網卡驅動的中斷處理函數中調用napi_schedule()來使用NAPI。


  1. /** 
  2.  * napi_schedule - schedule NAPI poll 
  3.  * @n: napi context 
  4.  * Schedule NAPI poll routine to be called if it is not already running. 
  5.  */  
  6.   
  7. static inline void napi_schedule(struct napi_struct *n)  
  8. {  
  9.     /* 判斷是否可以調度NAPI */  
  10.     if (napi_schedule_prep(n))  
  11.         __napi_schedule(n);  
  12. }  

判斷NAPI是否可以調度。如果NAPI沒有被禁止,且不存在已被調度的NAPI,

則允許調度NAPI,因爲同一時刻只允許有一個NAPI poll instance。


  1. /** 
  2.  * napi_schedule_prep - check if napi can be scheduled 
  3.  * @n: napi context 
  4.  * Test if NAPI routine is already running, and if not mark it as running. 
  5.  * This is used as a condition variable insure only one NAPI poll instance runs. 
  6.  * We also make sure there is no pending NAPI disable. 
  7.  */  
  8.   
  9. static inline int napi_schedule_prep(struct napi_struct *n)  
  10. {  
  11.     return !napi_disable_pending(n) && !test_and_set_bit(NAPI_STATE_SCHED, &n->state);  
  12. }  
  13.    
  14. static inline int napi_disable_pending(struct napi_struct *n)  
  15. {  
  16.     return test_bit(NAPI_STATE_DISABLE, &n->state);  
  17. }   
  18.   
  19. enum {  
  20.     NAPI_STATE_SCHED, /* Poll is scheduled */  
  21.     NAPI_STATE_DISABLE, /* Disable pending */  
  22.     NAPI_STATE_NPSVC, /* Netpoll - don't dequeue from poll_list */  
  23. };  

NAPI的調度函數。把設備的napi_struct實例添加到當前CPU的softnet_data的poll_list中,

以便於接下來進行輪詢。然後設置NET_RX_SOFTIRQ標誌位來觸發軟中斷。


  1. void __napi_schedule(struct napi_struct *n)  
  2. {  
  3.     unsigned long flags;  
  4.     local_irq_save(flags);  
  5.     ____napi_schedule(&__get_cpu_var(softnet_data), n);  
  6.     local_irq_restore(flags);  
  7. }  
  8.   
  9. static inline void ____napi_schedule(struct softnet_data *sd, struct napi_struct *napi)  
  10. {  
  11.     /* 把napi_struct添加到softnet_data的poll_list中 */  
  12.     list_add_tail(&napi->poll_list, &sd->poll_list);  
  13.     __raise_softirq_irqoff(NET_RX_SOFTIRQ); /* 設置軟中斷標誌位 */  
  14. }  

 

(4) 輪詢方法

NAPI方式中的POLL方法由驅動程序提供,在通過netif_napi_add()加入napi_struct時指定。

在驅動的poll()中,從自身的隊列中獲取sk_buff後,如果網卡開啓了GRO,則會調用

napi_gro_receive()處理skb,否則直接調用netif_receive_skb()。

POLL方法應該和process_backlog()大體一致,多了一些具體設備相關的部分。

 

(5) 非NAPI和NAPI處理流程對比

以下是非NAPI設備和NAPI設備的數據包接收流程對比圖:

NAPI方式在上半部中sk_buff是存儲在驅動自身的隊列中的,軟中斷處理過程中驅動POLL方法調用

netif_receive_skb()直接處理skb並提交給上層。


  1. /** 
  2.  * netif_receive_skb - process receive buffer from network 
  3.  * @skb: buffer to process 
  4.  * netif_receive_skb() is the main receive data processing function. 
  5.  * It always succeeds. The buffer may be dropped during processing 
  6.  * for congestion control or by the protocol layers. 
  7.  * This function may only be called from softirq context and interrupts 
  8.  * should be enabled. 
  9.  * Return values (usually ignored): 
  10.  * NET_RX_SUCCESS: no congestion 
  11.  * NET_RX_DROP: packet was dropped 
  12.  */  
  13.   
  14. int netif_receive_skb(struct sk_buff *skb)  
  15. {  
  16.     /* 記錄接收時間到skb->tstamp */  
  17.     if (netdev_tstamp_prequeue)  
  18.         net_timestamp_check(skb);  
  19.    
  20.     if (skb_defer_rx_timestamp(skb))  
  21.         return NET_RX_SUCCESS;  
  22.   
  23. #ifdef CONFIG_RPS  
  24.     ...  
  25. #else  
  26.     return __netif_receive_skb(skb);  
  27. #endif  
  28. }  

__netif_receive_skb()在上篇blog中已分析過了,接下來就是網絡層來處理接收到的數據包了。

發佈了4 篇原創文章 · 獲贊 38 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章