NAPI 方式的實現 (linux網絡子系統學習 第三節 )

Linux 內核協議棧中報文接收的設計思路:




NAPI接口和舊接口兩者有一下相同點:


1)、對報文的處理都應該放在軟中斷中處理。


2)、兩者都有存儲報文的隊列,NAPI的隊列是由網卡來管理的,舊接口的隊列是由內核管理的。





每個NAPI設備都有一個輪詢函數來由軟中斷調用,來進行輪詢處理報文。我們可以建立一個虛擬的NAPI設備,讓她的輪詢函數來輪詢的處理舊接口的報文隊列,這樣NAPI和舊接口的軟中斷處理函數就可以共用一個。收包軟中斷處理函數只是進行調用相應NAPI的輪詢函數進行處理,並不關心是虛擬NAPI還是非虛擬NAPI。具體輪詢細節由相關NAPI的輪詢函數自己實現。




如上所述,我們現在可以知道每個CPU上需要有如下幾個元素:


1、一個報文的接收隊列,由舊接口來使用。


2、一個虛擬的NAPI設備。


3、一個NAPI的鏈表,上面掛着有報文需要處理的NAPI設備。




Linux內核具體實現:



結構體定義如下



struct softnet_data
{
  struct sk_buff_head   input_pkt_queue; //舊接口的輸入隊列
  struct list_head  poll_list; //有需要處理報文的NAPI設備
  struct napi_struct    backlog;//虛擬的NAPI設備 backlog
};


定義一個per_cpu變量 softnet_data

DECLARE_PER_CPU(struct softnet_data,softnet_data);



softnet_data 的初始化如下:


static int __init net_dev_init(void)
{
    int i, rc = -ENOMEM;
    /*
     *  Initialise the packet receive queues.
     */
    for_each_possible_cpu(i)
    {
        struct softnet_data *queue;
        queue = &per_cpu(softnet_data, i);
        /*初始化輸入隊列*/
        skb_queue_head_init(&queue->input_pkt_queue);
        queue->completion_queue = NULL;
        /*初始化pool 鏈表頭*/
        INIT_LIST_HEAD(&queue->poll_list);
        /*把backlog的輪詢函數初始化爲 process_backlog
         該函數來處理傳統接口使用的輸入隊列的報文*/
        queue->backlog.poll = process_backlog;
        /*backlog 輪詢函數一次可以處理的報文上限個數*/
        queue->backlog.weight = weight_p;
        queue->backlog.gro_list = NULL;
        queue->backlog.gro_count = 0;
    }
}



NAPI的數據結構:


每個NAPI需要如下幾個元素:

1、一個輪詢函數,來輪詢處理報文。

2、需要有一個狀態,來標識NAPI的調度狀態,是正在被調度還是沒有被調度。

3、每個NAPI的一次輪詢能處理的報文個數應該有一個上限,不能無限制的處理下去,防止獨佔CPU資源導致其他進程被餓死。

4、每個NAPI應該和一個網絡設備進行關聯。

5、NAPI設計時考慮了多隊列的情況。一個網絡設備可以有多個報文隊列,這樣一個網絡設備可以關聯多個NAPI,每個NAPI只處理特定的隊列的報文。這樣在多核情況下,多個CPU核可以並行的進行報文的處理。一般專用的網絡多核處理器存在這種情況。

6、每個NAPI在有報文的情況下應該掛到softnet_data的pool_list上去。


如上所述,NAPI的結構體定義如下


struct napi_struct
{
    struct list_head  poll_list;//掛到softnet_data的pool_list上
    unsigned long  state;//NAPI的調度狀態
    int  weight;//一次輪詢的最大處理報文數
    int  (*poll)(struct napi_struct *, int);//輪詢函數
    struct net_device   *dev;//指向關聯的網絡設備
    struct list_head  dev_list;//對應的網絡設備上關聯的NAPI鏈表節點
        /*其他字段是gso功能用,這裏先不討論*/
};


NAPI的調度狀態:

NAPI_STATE_SCHED設置時表示該NAPI有報文 需要接收。即把NAPI掛到softnet_data 時要設置該狀態,處理完softnet_data 上摘除該NAPI時要清除該狀態。



一些NAPI的函數詳解:


1、netif_napi_add(),把網絡設備net_device 和NAPI結構相綁定。



void netif_napi_add(struct net_device *dev,
      struct napi_struct *napi,
      int (*poll)(struct napi_struct *, int), int weight)
{
    INIT_LIST_HEAD(&napi->poll_list);
    napi->poll = poll;
    napi->weight = weight;
    /*把NAPI加入到網絡設備相關聯的NAPI鏈表上去。*/
    list_add(&napi->dev_list, &dev->napi_list);
    napi->dev = dev;
    /*綁定時設置NAPI是已調度狀態,禁用該NAPI,以後手動的來清除該標識來使
    能NAPI.*/
    set_bit(NAPI_STATE_SCHED, &napi->state);
}


2、使能和禁用NAPI:


static inline void napi_disable(struct napi_struct *n)
{
    /*先設置NAPI狀態爲DISABLE*/
    set_bit(NAPI_STATE_DISABLE, &n->state);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
    /*循環的等待NAPI被調度完成,變成可用的,設置成SCHED狀態*/
    while (test_and_set_bit(NAPI_STATE_SCHED, &n->state))
        msleep(1);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
    /*清除DISABLE狀態*/
    clear_bit(NAPI_STATE_DISABLE, &n->state);
}


3、調度NAPI


void __napi_schedule(struct napi_struct *n)
{
    unsigned long flags;
    /*鏈表操作必須在關閉本地中斷的情況下操作,防止硬中斷搶佔*/
    local_irq_save(flags);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
    /*把NAPI加入到本地CPU的softnet_data 的pool_list 鏈表上*/
    list_add_tail(&n->poll_list,
                  &__get_cpu_var(softnet_data).poll_list);
    /*調度收包軟中斷*/
    __raise_softirq_irqoff(NET_RX_SOFTIRQ);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
    local_irq_restore(flags);
}


4、NAPI執行完畢,如果NAPI一次輪詢處理完隊列的所以報文,調用該函數。


void __napi_complete(struct napi_struct *n)
{
    BUG_ON(!test_bit(NAPI_STATE_SCHED, &n->state));
    BUG_ON(n->gro_list);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
    /*把NAPI從softnet_data的pool_list上摘除下來*/
    list_del(&n->poll_list);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
    /*使能NAPI,允許它下次可以被再次調度*/
    smp_mb__before_clear_bit();
    clear_bit(NAPI_STATE_SCHED, &n->state);
}


一般網卡驅動的NAPI的步驟:

1、不同的網卡驅動都會爲網絡設備定義自己的結構體。有的是自己的結構體中即包括net_device和napi,有的是把自己的結構體放到net_device的priv部分。


2、實現NAPI的輪詢函數 rx_pool(struct napi_struct *napi,int weigh);


int rx_pool(struct napi_struct *napi,int weigh)

{

int rx_cnt = 0;

struct sk_buff *skb;


while(rx_cnt < weigh)

{

 skb = netdev_alloc_skb();

 copy_skb_form_hw(skb);//把報文從硬件緩存中讀到內存skb中*/

 rx_cnt ++;

 netif_receive_skb(skb);//送協議棧直接處理

}

if(rx_cnt < weigh)

{

 /*處理報文小與允許處理最大個數,表示一次輪詢處理完全部的報文。

 使能收包中斷*/

 napi_complete(napi)

 enable_rx_irq();

}

return rx_cnt;

}


3、把網絡設備跟NAPI相關聯,netif_napi_add(netdev,napi,rx_pool,weigh);


4、註冊網卡的收包中斷:例:

request_irq(irq,&rx_irq_handle, 0, netdev->name, netdev);


在中斷處理函數中

static irqreturn_t rx_irq_handle(int irq, void *data)

{

struct net_device *netdev = data

struct napi_struct *napi = netdev_priv(netdev)->napi;

disable_rx_irq();

__napi_schedule(napi);

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章