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_devicestruct list_head poll_list;struct sk_buff *completion_queue;//backlog_dev是一個偽造的net_device用來來處理input_pkt_queue里的數據struct net_device backlog_dev; /* Sorry. 8) */ };收包過程可以分成兩步:
第一步的中斷的代碼可以參考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_taillist_move_tail(&dev->poll_list, &queue->poll_list);if (dev->quota < 0)dev->quota += dev->weight;elsedev->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類似。
轉載于:https://www.cnblogs.com/4a8a08f09d37b73795649038408b5f33/p/11475249.html
總結
以上是生活随笔為你收集整理的linux网络收包过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 多线程-生产者、消费者
- 下一篇: 第十届机器学习及其应用研讨会 MLA’2