中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Linux網絡協議棧收消息過程是怎么樣的

發布時間:2021-10-19 18:17:07 來源:億速云 閱讀:110 作者:柒染 欄目:大數據

本篇文章為大家展示了Linux網絡協議棧收消息過程是怎么樣的,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

想看能不能完整梳理一下收消息過程。從 NIC 收數據開始,到觸發軟中斷,交付數據包到 IP 層再經由路由機制到 TCP 層,最終交付用戶進程。會盡力介紹收消息過程中的各種配置信息,以及各種監控數據。知道了收消息的完整過程,了解了各種配置,明白了各種監控數據后才有可能在今后的工作中做優化配置。

Ring Buffer 相關的收消息過程大致如下:

對 raise softirq 的函數名做了修改,改為了 napi_schedule

NIC (network interface card) 在系統啟動過程中會向系統注冊自己的各種信息,系統會分配 Ring Buffer 隊列也會分配一塊專門的內核內存區域給 NIC 用于存放傳輸上來的數據包。struct sk_buff 是專門存放各種網絡傳輸數據包的內存接口,在收到數據存放到 NIC 專用內核內存區域后,sk_buff 內有個 data 指針會指向這塊內存。Ring Buffer 隊列內存放的是一個個 Packet Descriptor ,其有兩種狀態: ready 和 used 。初始時 Descriptor 是空的,指向一個空的 sk_buff,處在 ready 狀態。當有數據時,DMA 負責從 NIC 取數據,并在 Ring Buffer 上按順序找到下一個 ready 的 Descriptor,將數據存入該 Descriptor 指向的 sk_buff 中,并標記槽為 used。因為是按順序找 ready 的槽,所以 Ring Buffer 是個 FIFO 的隊列。

當 DMA 讀完數據之后,NIC 會觸發一個 IRQ 讓 CPU 去處理收到的數據。因為每次觸發 IRQ 后 CPU 都要花費時間去處理 Interrupt Handler,如果 NIC 每收到一個 Packet 都觸發一個 IRQ 會導致 CPU 花費大量的時間在處理 Interrupt Handler,處理完后又只能從 Ring Buffer 中拿出一個 Packet,雖然 Interrupt Handler 執行時間很短,但這么做也非常低效,并會給 CPU 帶去很多負擔。所以目前都是采用一個叫做 New API(NAPI)的機制,去對 IRQ 做合并以減少 IRQ 次數。

接下來介紹一下 NAPI 是怎么做到 IRQ 合并的。它主要是讓 NIC 的 driver 能注冊一個 poll 函數,之后 NAPI 的 subsystem 能通過 poll 函數去從 Ring Buffer 中批量拉取收到的數據。主要事件及其順序如下:

  1. NIC driver 初始化時向 Kernel 注冊 poll 函數,用于后續從 Ring Buffer 拉取收到的數據

  2. driver 注冊開啟 NAPI,這個機制默認是關閉的,只有支持 NAPI 的 driver 才會去開啟

  3. 收到數據后 NIC 通過 DMA 將數據存到內存

  4. NIC 觸發一個 IRQ,并觸發 CPU 開始執行 driver 注冊的 Interrupt Handler

  5. driver 的 Interrupt Handler 通過 napi_schedule 函數觸發 softirq (NET_RX_SOFTIRQ) 來喚醒 NAPI subsystem,NET_RX_SOFTIRQ 的 handler 是 net_rx_action 會在另一個線程中被執行,在其中會調用 driver 注冊的 poll 函數獲取收到的 Packet

  6. driver 會禁用當前 NIC 的 IRQ,從而能在 poll 完所有數據之前不會再有新的 IRQ

  7. 當所有事情做完之后,NAPI subsystem 會被禁用,并且會重新啟用 NIC 的 IRQ

  8. 回到第三步

從上面的描述可以看出來還缺一些東西,Ring Buffer 上的數據被 poll 走之后是怎么交付上層網絡棧繼續處理的呢?以及被消耗掉的 sk_buff 是怎么被重新分配重新放入 Ring Buffer 的呢?

這兩個工作都在 poll 中完成,上面說過 poll 是個 driver 實現的函數,所以每個 driver 實現可能都不相同。但 poll 的工作基本是一致的就是:

  1. 從 Ring Buffer 中將收到的 sk_buff 讀取出來

  2. 對 sk_buff 做一些基本檢查,可能會涉及到將幾個 sk_buff 合并因為可能同一個 Frame 被分散放在多個 sk_buff 中

  3. 將 sk_buff 交付上層網絡棧處理

  4. 清理 sk_buff,清理 Ring Buffer 上的 Descriptor 將其指向新分配的 sk_buff 并將狀態設置為 ready

  5. 更新一些統計數據,比如收到了多少 packet,一共多少字節等

如果拿 intel igb 這個網卡的實現來看,其 poll 函數在這里:linux/drivers/net/ethernet/intel/igb/igb_main.c - Elixir - Free Electrons

首先是看到有 tx.ring 和 rx.ring,說明收發消息都會走到這里。發消息先不管,先看收消息,收消息走的是 igb_clean_rx_irq。收完消息后執行 napi_complete_done 退出 polling 模式,并開啟 NIC 的 IRQ。從而我們知道大部分工作是在 igb_clean_rx_irq 中完成的,其實現大致上還是比較清晰的,就是上面描述的幾步。里面有個 while 循環通過 buget 控制,從而在 Packet 特別多的時候不要讓 CPU 在這里無窮循環下去,要讓別的事情也能夠被執行。循環內做的事情如下:

  1. 先批量清理已經讀出來的 sk_buff 并分配新的 buffer 從而避免每次讀一個 sk_buff 就清理一個,很低效

  2. 找到 Ring Buffer 上下一個需要被讀取的 Descriptor ,并檢查描述符狀態是否正常

  3. 根據 Descriptor 找到 sk_buff 讀出來

  4. 檢查是否是 End of packet,是的話說明 sk_buff 內有 Frame 的全部內容,不是的話說明 Frame 數據比 sk_buff 大,需要再讀一個 sk_buff,將兩個 sk_buff 數據合并起來

  5. 通過 Frame 的 Header 檢查 Frame 數據完整性,是否正確之類的

  6. 記錄 sk_buff 的長度,讀了多少數據

  7. 設置 Hash、checksum、timestamp、VLAN id 等信息,這些信息是硬件提供的。

  8. 通過 napi_gro_receive 將 sk_buff 交付上層網絡棧

  9. 更新一堆統計數據

  10. 回到 1,如果沒數據或者 budget 不夠就退出循環

看到 budget 會影響到 CPU 執行 poll 的時間,budget 越大當數據包特別多的時候可以提高 CPU 利用率并減少數據包的延遲。但是 CPU 時間都花在這里會影響別的任務的執行。

budget 默認 300,可以調整
sysctl -w net.core.netdev_budget=600

napi_gro_receive會涉及到 GRO 機制,稍后再說,大致上就是會對多個數據包做聚合,napi_gro_receive 最終是將處理好的 sk_buff 通過調用 netif_receive_skb,將數據包送至上層網絡棧。執行完 GRO 之后,基本可以認為數據包正式離開 Ring Buffer,進入下一個階段了。在記錄下一階段的處理之前,補充一下收消息階段 Ring Buffer 相關的更多細節。

Generic Receive Offloading(GRO)

GRO 是 Large receive offload 的一個實現。網絡上大部分 MTU 都是 1500 字節,開啟 Jumbo Frame 后能到 9000 字節,如果發送的數據超過 MTU 就需要切割成多個數據包。LRO 就是在收到多個數據包的時候將同一個 Flow 的多個數據包按照一定的規則合并起來交給上層處理,這樣就能減少上層需要處理的數據包數量。

很多 LRO 機制是在 NIC 上實現的,沒有實現 LRO 的 NIC 就少了上述合并數據包的能力。而 GRO 是 LRO 在軟件上的實現,從而能讓所有 NIC 都支持這個功能。

napi_gro_receive 就是在收到數據包的時候合并多個數據包用的,如果收到的數據包需要被合并,napi_gro_receive 會很快返回。當合并完成后會調用 napi_skb_finish ,將因為數據包合并而不再用到的數據結構釋放。最終會調用到 netif_receive_skb 將數據包交到上層網絡棧繼續處理。netif_receive_skb 上面說過,就是數據包從 Ring Buffer 出來后到上層網絡棧的入口。

可以通過 ethtool 查看和設置 GRO:

查看 GRO
ethtool -k eth0 | grep generic-receive-offload
generic-receive-offload: on
設置開啟 GRO
ethtool -K eth0 gro on

多 CPU 下的 Ring Buffer 處理 (Receive Side Scaling)

NIC 收到數據的時候產生的 IRQ 只可能被一個 CPU 處理,從而只有一個 CPU 會執行 napi_schedule 來觸發 softirq,觸發的這個 softirq 的 handler 也還是會在這個產生 softIRQ 的 CPU 上執行。所以 driver 的 poll 函數也是在最開始處理 NIC 發出 IRQ 的那個 CPU 上執行。于是一個 Ring Buffer 上同一個時刻只有一個 CPU 在拉取數據。

從上面描述能看出來分配給 Ring Buffer 的空間是有限的,當收到的數據包速率大于單個 CPU 處理速度的時候 Ring Buffer 可能被占滿,占滿之后再來的新數據包會被自動丟棄。而現在機器都是有多個 CPU,同時只有一個 CPU 去處理 Ring Buffer 數據會很低效,這個時候就產生了叫做 Receive Side Scaling(RSS) 或者叫做 multiqueue 的機制來處理這個問題。WIKI 對 RSS 的介紹挺好的,簡潔干練可以看看: Network interface controller - Wikipedia

簡單說就是現在支持 RSS 的網卡內部會有多個 Ring Buffer,NIC 收到 Frame 的時候能通過 Hash Function 來決定 Frame 該放在哪個 Ring Buffer 上,觸發的 IRQ 也可以通過操作系統或者手動配置 IRQ affinity 將 IRQ 分配到多個 CPU 上。這樣 IRQ 能被不同的 CPU 處理,從而做到 Ring Buffer 上的數據也能被不同的 CPU 處理,從而提高數據的并行處理能力。

RSS 除了會影響到 NIC 將 IRQ 發到哪個 CPU 之外,不會影響別的邏輯了。收消息過程跟之前描述的是一樣的。

如果支持 RSS 的話,NIC 會為每個隊列分配一個 IRQ,通過 /proc/interrupts 能進行查看。你可以通過配置 IRQ affinity 指定 IRQ 由哪個 CPU 來處理中斷。先通過 /proc/interrupts 找到 IRQ 號之后,將希望綁定的 CPU 號寫入 /proc/irq/IRQ_NUMBER/smp_affinity,寫入的是 16 進制的 bit mask。比如看到隊列 rx_0 對應的中斷號是 41 那就執行:

echo 6 > /proc/irq/41/smp_affinity
6 表示的是 CPU2 和 CPU1

0 號 CPU 的掩碼是 0x1 (0001),1 號 CPU 掩碼是 0x2 (0010),2 號 CPU 掩碼是 0x4 (0100),3 號 CPU 掩碼是 0x8 (1000) 依此類推。

另外需要注意的是設置 smp_affinity 的話不能開啟 irqbalance 或者需要為 irqbalance 設置 –banirq 列表,將設置了 smp_affinity 的 IRQ 排除。不然 irqbalance 機制運作時會忽略你設置的 IRQ affinity 配置。

Receive Packet Steering(RPS) 是在 NIC 不支持 RSS 時候在軟件中實現 RSS 類似功能的機制。其好處就是對 NIC 沒有要求,任何 NIC 都能支持 RPS,但缺點是 NIC 收到數據后 DMA 將數據存入的還是一個 Ring Buffer,NIC 觸發 IRQ 還是發到一個 CPU,還是由這一個 CPU 調用 driver 的 poll 來將 Ring Buffer 的數據取出來。RPS 是在單個 CPU 將數據從 Ring Buffer 取出來之后才開始起作用,它會為每個 Packet 計算 Hash 之后將 Packet 發到對應 CPU 的 backlog 中,并通過 Inter-processor Interrupt(IPI) 告知目標 CPU 來處理 backlog。后續 Packet 的處理流程就由這個目標 CPU 來完成。從而實現將負載分到多個 CPU 的目的。

RPS 默認是關閉的,當機器有多個 CPU 并且通過 softirqs 的統計 /proc/softirqs 發現 NET_RX 在 CPU 上分布不均勻或者發現網卡不支持 mutiqueue 時,就可以考慮開啟 RPS。開啟 RPS 需要調整 /sys/class/net/DEVICE_NAME/queues/QUEUE/rps_cpus 的值。比如執行:

echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus

表示的含義是處理網卡 eth0 的 rx-0 隊列的 CPU 數設置為 f 。即設置有 15 個 CPU 來處理 rx-0 這個隊列的數據,如果你的 CPU 數沒有這么多就會默認使用所有 CPU 。甚至有人為了方便都是直接將 echo fff > /sys/class/net/eth0/queues/rx-0/rps_cpus 寫到腳本里,這樣基本能覆蓋所有類型的機器,不管機器 CPU 數有多少,都能覆蓋到。從而就能讓這個腳本在任意機器都能執行。

注意:如果 NIC 不支持 mutiqueue,RPS 不是完全不用思考就能打開的,因為其開啟之后會加重所有 CPU 的負擔,在一些場景下比如 CPU 密集型應用上并不一定能帶來好處。所以得測試一下。

Receive Flow Steering(RFS) 一般和 RPS 配合一起工作。RPS 是將收到的 packet 發配到不同的 CPU 以實現負載均衡,但是可能同一個 Flow 的數據包正在被 CPU1 處理,但下一個數據包被發到 CPU2,會降低 CPU cache hit 比率并且會讓數據包要從 CPU1 發到 CPU2 上。RFS 就是保證同一個 flow 的 packet 都會被路由到正在處理當前 Flow 數據的 CPU,從而提高 CPU cache 比率。這篇文章 把 RFS 機制介紹的挺好的。基本上就是收到數據后根據數據的一些信息做個 Hash 在這個 table 的 entry 中找到當前正在處理這個 flow 的 CPU 信息,從而將數據發給這個正在處理該 Flow 數據的 CPU 上,從而做到提高 CPU cache hit 率,避免數據在不同 CPU 之間拷貝。當然還有很多細節,請看上面鏈接。

RFS 默認是關閉的,必須主動配置才能生效。正常來說開啟了 RPS 都要再開啟 RFS,以獲取更好的性能。這篇文章也有說該怎么去開啟 RFS 以及推薦的配置值。一個是要配置 rps_sock_flow_entries

sysctl -w net.core.rps_sock_flow_entries=32768

這個值依賴于系統期望的活躍連接數,注意是同一時間活躍的連接數,這個連接數正常來說會大大小于系統能承載的最大連接數,因為大部分連接不會同時活躍。該值建議是 32768,能覆蓋大多數情況,每個活躍連接會分配一個 entry。除了這個之外還要配置 rps_flow_cnt,這個值是每個隊列負責的 flow 最大數量,如果只有一個隊列,則 rps_flow_cnt 一般是跟 rps_sock_flow_entries 的值一致,但是有多個隊列的時候 rps_flow_cnt 值就是 rps_sock_flow_entries / N, N 是隊列數量。

echo 2048 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

Accelerated Receive Flow Steering (aRFS) 類似 RFS 只是由硬件協助完成這個工作。aRFS 對于 RFS 就和 RSS 對于 RPS 一樣,就是把 CPU 的工作挪到了硬件來做,從而不用浪費 CPU 時間,直接由 NIC 完成 Hash 值計算并將數據發到目標 CPU,所以快一點。NIC 必須暴露出來一個 ndo_rx_flow_steer 的函數用來實現 aRFS。

adaptive RX/TX IRQ coalescing

有的 NIC 支持這個功能,用來動態的將 IRQ 進行合并,以做到在數據包少的時候減少數據包的延遲,在數據包多的時候提高吞吐量。查看方法:

ethtool -c eth2
Coalesce parameters for eth2:
Adaptive RX: off  TX: off
stats-block-usecs: 0
.....

開啟 RX 隊列的 adaptive coalescing 執行:

ethtool -C eth0 adaptive-rx on

并且有四個值需要設置:rx-usecs、rx-frames、rx-usecs-irq、rx-frames-irq,具體含義等需要用到的時候查吧。

Ring Buffer 相關監控及配置

收到數據包統計

ethtool -S eh0
NIC statistics:
     rx_packets: 792819304215
     tx_packets: 778772164692
     rx_bytes: 172322607593396
     tx_bytes: 201132602650411
     rx_broadcast: 15118616
     tx_broadcast: 2755615
     rx_multicast: 0
     tx_multicast: 10

RX 就是收到數據,TX 是發出數據。還會展示 NIC 每個隊列收發消息情況。其中比較關鍵的是帶有 drop 字樣的統計和 fifo_errors 的統計 :

tx_dropped: 0
rx_queue_0_drops: 93
rx_queue_1_drops: 874
....
rx_fifo_errors: 2142
tx_fifo_errors: 0

看到發送隊列和接收隊列 drop 的數據包數量顯示在這里。并且所有 queue_drops 加起來等于 rx_fifo_errors。所以總體上能通過 rx_fifo_errors 看到 Ring Buffer 上是否有丟包。如果有的話一方面是看是否需要調整一下每個隊列數據的分配,或者是否要加大 Ring Buffer 的大小。

/proc/net/dev是另一個數據包相關統計,不過這個統計比較難看:

cat /proc/net/dev
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
    lo: 14472296365706 10519818839    0    0    0     0          0         0 14472296365706 10519818839    0    0    0     0       0          0
  eth2: 164650683906345 785024598362    0    0 2142     0          0         0 183711288087530 704887351967    0    0    0     0       0          0
調整 Ring Buffer 隊列數量
ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX:             0
TX:             0
Other:          1
Combined:       8
Current hardware settings:
RX:             0
TX:             0
Other:          1
Combined:       8

看的是 Combined 這一欄是隊列數量。Combined 按說明寫的是多功能隊列,猜想是能用作 RX 隊列也能當做 TX 隊列,但數量一共是 8 個?

如果不支持 mutiqueue 的話上面執行下來會是:

Channel parameters for eth0:
Cannot get device channel parameters
: Operation not supported

看到上面 Ring Buffer 數量有 maximums 和 current settings,所以能自己設置 Ring Buffer 數量,但最大不能超過 maximus 值:

sudo ethtool -L eth0 combined 8

如果支持對特定類型 RX 或 TX 設置隊列數量的話可以執行:

sudo ethtool -L eth0 rx 8

需要注意的是,ethtool 的設置操作可能都要重啟一下才能生效。

調整 Ring Buffer 隊列大小

先查看當前 Ring Buffer 大小:

ethtool -g eth0
Ring parameters for eth0:
Pre-set maximums:
RX:   4096
RX Mini:  0
RX Jumbo: 0
TX:   4096
Current hardware settings:
RX:   512
RX Mini:  0
RX Jumbo: 0
TX:   512

看到 RX 和 TX 最大是 4096,當前值為 512。隊列越大丟包的可能越小,但數據延遲會增加

設置 RX 隊列大小:

ethtool -G eth0 rx 4096
調整 Ring Buffer 隊列的權重

NIC 如果支持 mutiqueue 的話 NIC 會根據一個 Hash 函數對收到的數據包進行分發。能調整不同隊列的權重,用于分配數據。

ethtool -x eth0
RX flow hash indirection table for eth0 with 8 RX ring(s):
    0:      0     0     0     0     0     0     0     0
    8:      0     0     0     0     0     0     0     0
   16:      1     1     1     1     1     1     1     1
   ......
   64:      4     4     4     4     4     4     4     4
   72:      4     4     4     4     4     4     4     4
   80:      5     5     5     5     5     5     5     5
   ......
  120:      7     7     7     7     7     7     7     7

我的 NIC 一共有 8 個隊列,一個有 128 個不同的 Hash 值,上面就是列出了每個 Hash 值對應的隊列是什么。最左側 0 8 16 是為了能讓你快速的找到某個具體的 Hash 值。比如 Hash 值是 76 的話我們能立即找到 72 那一行:”72: 4 4 4 4 4 4 4 4”,從左到右第一個是 72 數第 5 個就是 76 這個 Hash 值對應的隊列是 4 。

ethtool -X eth0 weight 6 2 8 5 10 7 1 5

設置 8 個隊列的權重。加起來不能超過 128 。128 是 indirection table 大小,每個 NIC 可能不一樣。

更改 Ring Buffer Hash Field

分配數據包的時候是按照數據包內的某個字段來進行的,這個字段能進行調整。

ethtool -n eth0 rx-flow-hash tcp4
TCP over IPV4 flows use these fields for computing Hash flow key:
IP SA
IP DA
L4 bytes 0 & 1 [TCP/UDP src port]
L4 bytes 2 & 3 [TCP/UDP dst port]

查看 tcp4 的 Hash 字段。

也可以設置 Hash 字段:

ethtool -N eth0 rx-flow-hash udp4 sdfn

sdfn 需要查看 ethtool 看其含義,還有很多別的配置值。

softirq 數統計

通過 /proc/softirqs 能看到每個 CPU 上 softirq 數量統計:

cat /proc/softirqs
                    CPU0       CPU1       
          HI:          1          0
       TIMER: 1650579324 3521734270
      NET_TX:   10282064   10655064
      NET_RX: 3618725935       2446
       BLOCK:          0          0
BLOCK_IOPOLL:          0          0
     TASKLET:      47013      41496
       SCHED: 1706483540 1003457088
     HRTIMER:    1698047   11604871
         RCU: 4218377992 3049934909

看到 NET_RX 就是收消息時候觸發的 softirq,一般看這個統計是為了看看 softirq 在每個 CPU 上分布是否均勻,不均勻的話可能就需要做一些調整。比如上面看到 CPU0 和 CPU1 兩個差距很大,原因是這個機器的 NIC 不支持 RSS,沒有多個 Ring Buffer。開啟 RPS 后就均勻多了。

IRQ 統計

/proc/interrupts 能看到每個 CPU 的 IRQ 統計。一般就是看看 NIC 有沒有支持 multiqueue 以及 NAPI 的 IRQ 合并機制是否生效。看看 IRQ 是不是增長的很快。

上述內容就是Linux網絡協議棧收消息過程是怎么樣的,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

惠来县| 汾西县| 卓资县| 雷山县| 六枝特区| 五莲县| 潞城市| 崇明县| 安仁县| 马山县| 措勤县| 江阴市| 汉阴县| 论坛| 丹寨县| 广安市| 门源| 淅川县| 张家口市| 黑水县| 昭通市| 泊头市| 平邑县| 万山特区| 哈密市| 商洛市| 类乌齐县| 西和县| 岑巩县| 剑河县| 永福县| 漳平市| 棋牌| 张北县| 平安县| 台南市| 合川市| 九龙县| 清水河县| 凯里市| 锡林郭勒盟|