1. 程式人生 > 其它 >高效能網路框架筆記一(網路包接收與傳送流程)

高效能網路框架筆記一(網路包接收與傳送流程)

一、網路收包流程

1、當網路資料幀通過網路傳輸到達網絡卡時,網絡卡會將網路資料幀通過DMA的方式放到緩行緩衝區RingBuffer中。

RingBuffer時網絡卡啟動的時候分配和初始化的環形緩衝佇列。當RingBuffer滿的時候,新來的資料包就會被丟棄。我們可通過ifconfig命令檢視網絡卡收發資料包情況。其中overruns資料項表示當RingBuffer滿時被丟棄的資料包。如果發現出現丟包情況,可以通過ethtool命令來增大RingBuffer長度。

2、當DMA操作完成時,網絡卡向CPU發起一個硬中斷,告訴CPU有網路資料到達。CPU呼叫網絡卡驅動註冊的硬中斷響應程式。網絡卡硬中斷響應程式會為網絡卡資料幀建立核心資料結構sk_buffer,並將網路資料幀拷貝到sk_buffer中。然後發起軟中斷請求,通知核心有新的網路資料幀到達。

sk_buffer緩衝區,是一個維護網路幀結構的雙向連結串列,連結串列中的每一個元素都是一個網路幀。雖然TCP/IP協議棧分了好幾層,但上下不同層之間的傳遞,實際上只需要操作這個資料結構中的指標,而無需進行資料複製。

3、核心執行緒ksoftirqd發現有軟中斷請求到來,隨後呼叫網絡卡驅動註冊的poll函式將sk_buffer中的網路資料包送到核心協議棧中註冊的ip_rcv函式中。

每個CPU會繫結一個ksoftirqd核心執行緒專門來處理軟中斷響應。2個CPU時,就會有ksoftirqd/0和ksoftirqd/1這兩個核心執行緒。

網絡卡接收到資料後,當DMA拷貝完成時,向CPU發出硬中斷,這時哪個CPU上相遇了這個硬中斷,那麼網絡卡硬中斷響應程式中發出的軟中斷請求也會在這個CPU繫結的ksoftirqd執行緒中響應。所以如果發現Linux軟中斷,CPU消耗都集中在一個核上的話,那麼就需要調整硬中斷的CPU親和性,來將硬中斷打散到不同的CPU核上去。

4、在ip_rcv函式中,也就是網路層,取出資料包的IP頭,判斷該資料包下一跳的走向,如果資料包是傳送給本機的,則取出傳輸層的協議型別(TCP或UDP),並去掉資料包的IP頭,將資料包交給傳輸層處理。

傳輸層的處理函式:TCP協議對應核心協議中的註冊函式的tcp_rcv函式,UDP協議對應核心協議棧中註冊的udp_rcv函式。

5、當我們採用的是TCP協議時,資料包到達傳輸層時,會在核心協議棧中的tcp_rcv函式處理,在tcp_rcv函式中去掉TCP頭,根據四元組(源IP、源埠、目的IP、目的埠)查詢隊友的Socket,如果找到對應的Socket則將網路資料包中傳輸資料拷貝到Socket中的接收緩衝區中。如果沒有找到,則傳送一個目標不可達的icmp包。

6、核心在接收網路資料包時所做的工作已經介紹完畢,現在我們把視角放到應用層,當我們程式通過系統呼叫read讀取Socket接收緩衝區中的資料時,如果接收緩衝區中沒有資料,那麼應用程式就會在系統呼叫上阻塞,直到Socket緩衝區有資料,然後CPU將核心空間(Socket接收緩衝區)的資料拷貝到使用者空間,最後系統呼叫read返回,應用程式讀取資料。

7、效能開銷

從核心處理網路資料包接收到整個過程來看,核心幫我們做了非常多的工作,最終我們的應用程式才能讀到網路資料。隨之而來的也帶來了很多效能開銷,結合前面介紹的網路資料包接收過程,我們來看下網路資料包接收的過程有哪些效能開銷:

  • 應用成通過系統呼叫從使用者態轉為核心態的開銷以及系統呼叫返回時從核心態轉為使用者態的開銷。
  • 網路資料從核心空間通過CPU拷貝到使用者空間的開銷。
  • 核心執行緒ksoftirqd響應軟中斷的開銷。
  • DMA拷貝網路資料包到記憶體中的開銷。

二、網路發包流程

1、當我們在應用程式中呼叫send系統呼叫傳送資料時,由於是系統呼叫所以執行緒會發生一次使用者態到核心態到轉換,在核心中首先根據fd將真正的Socket找出,這個Socket物件中記著各種協議棧的函式地址,然後構造struct msghdr物件,將使用者需要傳送的資料全部封裝在這個struct msghdr結構體中。

2、呼叫核心協議函式inet_sendmsg,傳送流程進入核心協議棧處理。在進入到核心協議棧之後,核心會找到Socket上的具體協議傳送函式。

比如:我們使用的是TCP協議,對應的TCP協議傳送函式是tcp_sendmsg,如果是UDP協議的話,對應的傳送函式為udp_sendmsg。

3、在TCP協議的傳送函式tcp_sendmsg中,建立核心資料結構sk_buffer,將struct msghdr結構體中的傳送資料拷貝到sk_buffer中。呼叫tcp_write_queue_tail函式獲取Socket傳送佇列中對尾元素,將新建立的sk_buffer新增到Socket傳送佇列的尾部。

Socket的傳送佇列是由sk_buffer組成的一個雙向連結串列。

傳送流程走到這裡,使用者要傳送的資料總算是從使用者空間拷貝到核心中,這時雖然傳送資料已經拷貝到了核心Socket中的傳送佇列中,但並不代表核心會開始傳送,因為TCP協議的流量控制和擁塞控制,使用者要傳送的資料包並不一定會立馬傳送出去,而是需要符合TCP協議的傳送條件。如果沒有達到傳送條件,那麼本次send系統呼叫就會直接返回。

4、如果符合傳送條件,則開始呼叫tcp_write_xmit核心函式。在這個函式中,會迴圈獲取Socket傳送佇列中待發送的sk_buffer,然後進行擁塞控制以及滑動視窗的管理。

5、將從Socket傳送佇列中獲取到的sk_buffer重新拷貝一份,設定sk_buffer副本中TCP HEADER。

sk_buffer內部其實包含了網路協議中所有的header。在設定TCP HEADER的時候,只是把指標指向sk_buffer的合適位置。後面再設定IP HEADER的時候,再把指標移動一下就行,避免頻繁的記憶體申請和拷貝,效率很高。

為什麼不直接使用Socket傳送佇列中的sk_buffer而是需要拷貝一份呢?因為TCP協議是支援丟包重傳的,在沒有收到對端ACK之前,這個sk_buffer是不能刪除的。核心每次呼叫網絡卡傳送資料的時候,實際上傳遞的是sk_buffer的拷貝副本,當網絡卡把資料傳送出去後,sk_buffer拷貝副本就會被釋放。當收到對端的ACK之後,Socket傳送佇列中的sk_buffer才會被真正刪除。

6、當設定完TCP頭後,核心協議棧傳輸層的事情就做完了,下面通過呼叫ip_queue_xmit核心函式,正式來到核心協議棧網路層的處理。

通過route命令可以檢視本機路由配置。

如果使用iptables配置了一些規則,那麼這將檢測是否命中規則。如果你設定了非常複雜的netfilter規則,在這個函式裡講導致你的執行緒CPU開銷會極大增加。

將sk_buffer中的指標移動到IP頭位置上,設定IP頭。

執行netfilters過濾。過濾通過之後,如果資料對於MTU的話,則執行分片。

檢查Socket中是否有快取路由表,如果沒有的話,則查詢路由項,並快取到Socket中。接著再把路由表設定到sk_buffer中。

7、核心協議棧網路層的事情處理完後,現在傳送流進入到了鄰居子系統,鄰居子系統位於核心協議棧中的網路和網路介面層之間,用於傳送ARP請求獲取MAC地址,然後將sk_buffer中的指標移動到MAC頭位置,填充MAC頭。

8、經過鄰居子系統的處理,現在sk_buffer中已經封裝了一個完整的資料幀,隨後核心將sk_buffer交給網路裝置子系統進行處理。網路裝置子系統主要做以下幾項事情:

  • 選擇傳送佇列(RingBuffer)。因為網絡卡擁有多個傳送佇列,所以在傳送之前需要選擇一個傳送佇列。
  • 將sk_buffer新增中傳送佇列中。
  • 迴圈從傳送佇列(RingBuffer)取出sk_buffer,呼叫核心函式sch_direct_xmil傳送資料,其中會呼叫網絡卡驅動程式來發送資料。

以上過程全部是使用者執行緒的核心態在執行,佔用的CPU時間是系統態時間(sy),當分配給使用者執行緒的CPU quota用完的時候,會觸發NET_TX_SOFTIRQ型別的軟中斷,核心執行緒ksoftirqd會響應這個軟中斷,並執行NET_TX_SOFTIRQ型別的軟中斷註冊的回撥函式net_tx_action,在毀掉函式中會執行到驅動程式函式de_hard_stat_xmit來發送資料。

注意:當觸發NET_TX_SOFTIRQ軟中斷來發送資料時,後面消耗的CPU就顯示在si這裡來,不會消耗使用者程序的系統態時間(sy)了。

從這裡可以看到網路包的傳送過程和接收過程是不同的,在介紹網路包的接收過程時,我們提到是通過出發NET_RX_SOFTIRQ型別的軟中斷在核心執行緒ksoftirqd中執行核心網路協議棧接受資料。而在網路資料包的傳送過程中是使用者執行緒的核心態在執行核心網路協議棧,只有當執行緒的CPU quota用盡時,才觸發NET_TX_SOFTIRQ軟中斷來發送資料。

在整個網路包的傳送和接收過程中,NET_TX_SOFTIRQ型別的軟中斷只會在傳送網路包時且當用戶執行緒的CPU quota用盡時,才會觸發。剩下的接收過程中觸發的軟中斷型別以及傳送完整資料觸發的軟中斷型別均為NET_RX_SOFTIRQ。所以這就是你在伺服器上檢視/proc/softirqs,一般NET_RX都要比NET_TX大很多的原因。

9、現在傳送流程終於到了網絡卡真是傳送資料的階段,前面我們講到無論時使用者執行緒的核心態還是觸發NET_TX_SOFTIRQ型別的軟中斷在傳送資料的時候最終會呼叫到網絡卡的驅動程式函式dev_hard_start_xmit來發送資料。在網絡卡驅動程式函式dev_hard_xmit中會將sk_buffer對映到網絡卡可以訪問的記憶體DMA區域,最終網絡卡驅動程式通過DMA的方式將資料幀通過物理網絡卡傳送出去。

10、當資料傳送完畢後,還以最後一項重要的工作,就是清理工作。資料傳送完畢後,網絡卡裝置會向CPU傳送一個硬中斷,CPU呼叫網絡卡驅動程式註冊的硬中斷響應程式,在硬中斷響應中觸發NET_RX_SOFTIRQ型別的軟中斷,在軟中斷的回撥函式igb_poll中清理是否sk_buffer,清理網絡卡傳送佇列(RingBuffer),解除DMA對映。

無論硬中斷是因為有資料要接收,還是說傳送完成通知,從硬中斷觸發的軟中斷都是NET_RX_SOFTIRQ。

這裡釋放清理的只是sk_buffer的副本,真正的sk_buffer現在還是存放在Socket的傳送佇列中。前面在傳輸層處理的時候我們提到過,因為傳輸層需要保證可靠性,所以sk_buffer其實還沒有刪除。它得等到收到對方的ACK之後才會真正刪除。

11、效能開銷:

前面我們提到了網路包接收過程中涉及的效能開銷,現在介紹完了網路包的傳送過程,我們來看下在資料包傳送過程中的效能開銷:

  • 和接收資料一樣,應用程式在呼叫系統呼叫send的時候會從使用者態轉為核心態以及傳送完資料後,系統呼叫返回時從核心態轉為使用者態的開銷。
  • 使用者執行緒核心態CPU quota用盡時觸發NET_TX_SOFTIRQ型別軟中斷,核心響應軟中斷的開銷。
  • 網絡卡傳送完資料,向CPU傳送硬中斷,CPU響應硬中斷的開銷。以及在硬中斷中傳送NET_RX_SOFTIRQ軟中斷執行具體的記憶體清理工作。核心響應軟中斷的開銷。
  • 記憶體拷貝的開銷。我們來回顧下在資料包傳送的過程中都發生了哪些記憶體拷貝:
    • 在核心協議棧的傳輸層中,TCP協議對應的傳送函式tcp_sendmsg會申請sk_buffer將使用者要傳送的資料拷貝到sk_buffer中。
    • 在傳送流程從傳輸層到網路層的時候,會拷貝一個sk_buffer副本出來,將這個sk_buffer副本向下傳遞。原始sk_buffer保留在Socket傳送佇列中,等待網路對端ACK,對端ACK後刪除Socket傳送佇列中的sk_buffer。對端沒有傳送ACK,則重新從Socket傳送佇列中傳送,實現TCP協議的可靠傳輸。
    • 在網路層,如果發現要傳送的資料大於MTU,則會進行分片操作,申請額外的sk_buffer,並將原來的sk_buffer拷貝到多個小的sk_buffer中。