1. 程式人生 > 實用技巧 >linux核心協議棧 IPv4之傳送介面: ip_append_data() Ⅰ 【UDP使用】

linux核心協議棧 IPv4之傳送介面: ip_append_data() Ⅰ 【UDP使用】

目錄

1 ip_append_data 函式功能概述

2 skb資料組織策略

2.1 情形1

2.2 情形2

2.3 情形3


1 ip_append_data 函式功能概述

ip_append_data() 主要是由 udp 使用。因為 ip_append_data() 的實現非常的複雜,涉及 skb 對資料的組織方式,以及各種出於效能考慮的分配策略,所以在分析其程式碼實現之前,有必要先討論下核心實際採取的資料組織方式。

首先需要明確 ip_append_data() 要實現的功能:

  1. ip_append_data() 的任務就是將多個片段合併成一個大的IP報文,該函式僅僅做合併工作,並不實際傳送報文,如果要傳送,呼叫者後面應該主動呼叫 ip_push_pending_frames();
  2. 這裡要注意區分IP報文和 IP片段的不同,傳送端可以組織一個很大的IP報文,但是在實際傳送時,為了適配裝置的MTU,需要將這一個IP報文拆封成多個IP片段,這些拆分的IP片段的ipid相同,片內偏移不同;
  3. 該函式將引數指定的資料(from,length)按照MTU大小組織成一個個的方便後面IP協議處理的skb,並且將這些skb放入傳輸控制塊的傳送緩衝區中,這些skb在程式碼中被稱之為 pending資料;

2 skb資料組織策略

  1. 一個IP報文可能超過了MTU大小,這時一個IP報文就需要拆分成多個IP片段分開傳輸以適配裝置的MTU;
  2. 為了方便IP協議後續處理,ip_append_data()呼叫之後,IP片段和skb是一一對應的,不管skb內部是如何儲存資料的,這點都是確定的。

下面我們來看一些可能的情況。

2.1 情形1

在這裡插入圖片描述

如上圖所示,這是一種不需要分段的情形,即傳輸的資料長度小於PTMU,只需要分配一個skb就可以容納下要傳送的資料了。這裡有些關鍵點需要說明:

  1. 實際分配skb時,會將L2所需頭部、IP頭部、L4的頭部和資料部分都考慮進來,這樣後續協議在處理該資料包是就無需再次分配緩衝區了;
  2. 示例中還有IPsec header和IPsec tailer兩部分,這屬於擴充套件首部和擴充套件末尾,當啟用IPsec時,需要在IP報文的首尾追加IPsec特有的資訊,所以如果是這種情況,在分配skb時也會將這種開銷考慮進來;
  3. 實際分配時,如果一個IP資料包總長度小於PMTU,分配的skb長度是按照需要的大小來分配的,不會造成浪費,這點同樣適用於需要分段時的最後一個片段(可能小於PMTU)的情況。

額外需要說明的是:ip_append_data()在分配了skb後,會拷貝L4 payload部分,還會初始化skb中的某些欄位,如上圖緩衝區左側的圖示;其餘的如L4 header由呼叫ip_push_pending_frames()的函式負責填充、IP header由ip_push_pending_frames()填充。

2.2 情形2

在這裡插入圖片描述

這個例子比上一個稍微複雜一點,這個例子我們不考慮IPsec的情形。左下角為L4協議想要傳輸的報文,長度為length並且大於PMTU,一次呼叫ip_append_data()需要將這整個報文拆分成skb放入到套接字的傳送緩衝區中,這裡假設分配兩個skb可以容納length長度的資料,這時第一個skb大小為PTMU,它拷貝了x位元組的資料;第二個skb拷貝了剩餘的y位元組(x+y=length),並且第二個skb的長度小於PMTU。

由於呼叫者可能會連續呼叫ip_append_data()多次,然後再呼叫ip_push_pending_frames()啟動傳送,所以如果按照上圖的方式分配最後一個skb,再來資料時就需要重新分配skb,然後將最後一個skb的資料拷貝到新的緩衝區中,然後刪除最後一個緩衝區,執行這樣的動作是耗時的,會帶來效能的損耗。為了處理這種問題,引入了MSG_MORE標記,如果在前一次呼叫ip_append_data()時指定了MSG_MORE標記,那麼表示後面馬上還會新增新的資料,這時就可以在處理最後一部分資料時預先分配一個容納PMTU大小的skb,如下圖所示:
在這裡插入圖片描述
雖然當前無法填滿最後一個skb,但是因為很快有資料到達,所以這麼分配沒有問題。

2.3 情形3

case1和case2所舉的例子都是裝置不支援S/G IO時能夠採取的解決方案,如果裝置支援S/G IO,那麼即使L4是多次傳入小片段呼叫ip_append_data(),ip_append_data()也可以按照下圖所示方式組織資料:

在這裡插入圖片描述

上圖a表示支援S/G IO的情形下第一次呼叫ip_append_data()後的緩衝區結構,注意skb的分配是按需分配的,其大小並不是PMTU。圖b是第二次呼叫ip_append_data()後,新增的資料儲存在了前一個skb的frags陣列中,這兩塊區域並不連續,但是由於裝置支援S/G IO,所以這麼分配是沒有問題的。

要特別注意的是,上圖中的x+S1的大小依然要小於PMTU,如果超過,那麼還是需要分配一個新的skb,這是因為一個skb對應一個IP片段的原則不能打破。