linux網絡收包過程


記錄一下linux數據包從網卡進入協議棧的過程,不涉及驅動,不涉及其他層的協議處理。

內核是如何知道網卡收到數據的,這就涉及到網卡和內核的交互方式:

輪詢(poll):內核周期性的檢查網卡,查看是否收到數據。優點:數據包非常多的時候,這種處理方法會非常快速有效。缺點:數據包少的時候會CPU總是輪詢卻沒有收到數據包,造成CPU資源的浪費。這種方法很少使用。

中斷(interrupt):網卡收到數據就給內核發送硬件中斷打斷內核的正常運行,讓內核來處理數據包。優點:在數據包少的時候CPU能及時中斷其他任務來處理數據包,比較高效。缺點:數據包多的時候每個數據包都引發一次中斷,造成CPU頻繁地在收包過程和其他過程之間切換,浪費時間。在極端情況下收包中斷可能會一直搶占CPU造成軟中斷無法運行,收包隊列得不到處理,進而造成大量丟包。這就是所謂的receive-livelock。

Llinux早期是采用中斷的方式處理數據包的,之后引入了另一種方式NAPI,NAPI結合了輪詢和中斷的優點,在數據包少的時候采用中斷方式,數據包多的時候采用輪詢的方式,從而在兩種極端情況下也會有比較好的表現。

在NAPI下收包的過程

先看一個比較關鍵的結構softnet_data,每個邏輯CPU都有一個softnet_data結構,這個結構的poll_list是非常重要的。

struct softnet_data
{
        struct net_device       *output_queue;
        //收報隊列,這個隊列是給傳統收報方法兼容新收報架構用的(backlog_dev),用來模擬NAPI的
        struct sk_buff_head     input_pkt_queue;
        //用於收包的net_device
        struct list_head        poll_list;
        struct sk_buff          *completion_queue;

        //backlog_dev是一個偽造的net_device用來來處理input_pkt_queue里的數據
        struct net_device       backlog_dev;    /* Sorry. 8) */
};

收包過程可以分成兩步:

  1. 當網卡收到數據包中斷發生,中斷處理程序就會把當前網卡的net_device插入當前CPU的softnet_data的poll_list鏈表,調度軟中斷。
  2.  軟中斷處理鏈表poll_list,讀出數據包,放入協議棧。

第一步的中斷的代碼可以參考drivers/net/tg3.c文件的tg3_interrupt函數,中斷發生的時候它會調用netif_rx_schedule把當前網卡的net_device插入當前CPU的softnet_data的poll_list鏈表。netif_rx_schedule函數又調度了軟中斷NET_RX_SOFTIRQ。大致結構就是下圖這樣子

第二步軟中斷在合適的時機得以執行,看一下他的執行過程:

static void net_rx_action(struct softirq_action *h)
{
        struct softnet_data *queue = &__get_cpu_var(softnet_data);
        unsigned long start_time = jiffies;
        //預算,所有網卡的總配額
        int budget = netdev_budget;
        void *have;

        local_irq_disable();

        while (!list_empty(&queue->poll_list)) {
                struct net_device *dev;

                //預算用完了,或者時間太長了,跳出等下一輪處理
                if (budget <= 0 || jiffies - start_time > 1)
                        goto softnet_break;

                local_irq_enable();

                dev = list_entry(queue->poll_list.next,
                                 struct net_device, poll_list);
                have = netpoll_poll_lock(dev);

                if (dev->quota <= 0 || dev->poll(dev, &budget)) {
                        netpoll_poll_unlock(have);
                        local_irq_disable();
                        //沒處理完,放到隊尾准備下次處理,注意是list_move_tai,不是
                        //list_insert_tail
                        list_move_tail(&dev->poll_list, &queue->poll_list);
                        if (dev->quota < 0)
                                dev->quota += dev->weight;
                        else
                                dev->quota = dev->weight;
                } else {
                        netpoll_poll_unlock(have);
                        dev_put(dev);
                        local_irq_disable();
                }
        }
        //省略部分代碼
}

軟中斷不能長時間占用CPU,否則會造成用戶態進程長時間得不到調度,net_rx_action也一樣。所以net_rx_action函數每次執行最多會處理budget個數據包(所有網卡都算),同時這budget個數據包也需要平均分配,不能只處理一個網卡造成其他網卡得不到處理,net_device的weight和quota是用來處理這個問題的。這個代碼的大概意思是每次從poll_list里取出一個網卡,調用該網卡的poll函數盡可能多的收包(但是不會超過weight),poll函數收包后調用netif_receive_skb把數據包放入協議棧。如果網卡里的數據包沒處理完就會把net_device繼續放到poll_list鏈表等待下一次軟中斷繼續處理,如果網卡里的數據包處理完了就把該net_device從poll_list摘除。

傳統中斷收包方式

linux網卡驅動還有部分是用的傳統中斷收包方式,為了兼容也都挪到了NAPI架構上。用softnet_data結構的backlog_dev偽造了一個net_device。中斷發生的時候把數據包放到了softnet_data結構的input_pkt_queue鏈表里。

static int __init net_dev_init(void)
{
//省略部分代碼
        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;
                INIT_LIST_HEAD(&queue->poll_list);
                set_bit(__LINK_STATE_START, &queue->backlog_dev.state);
                queue->backlog_dev.weight = weight_p;
                queue->backlog_dev.poll = process_backlog;
                atomic_set(&queue->backlog_dev.refcnt, 1);
        }
}

軟中斷的處理過程和NAPI類似。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM