在以上文章中,沒有分析過Linux內核網絡關鍵的數據結構-套接字數據緩存struct sk_buff,本文將第一次分享到sk_buff,但鑒于其在內核網絡中一些復雜情況,本次只簡單介紹sk_buff內存空間布局情況與相關操作。
套接字數據緩存(socket buffer)在Linux內核中表示為:struct sk_buff,是Linux內核中數據包管理的基本單元,主要包含兩個部分,其一:管理數據,即數據包的管理信息;其二:報文數據,保存了實際網絡中傳輸的數據,在內核協議棧起承上啟下的作用,也有很多值得關注的sk_buff操作。
1、sk_buff四大指針與相關操作
分配初始化:
struct sk_buff中四個指針都指向數據區,分別是head、data、tail、end,剛剛分配出來的sk_buff會立馬進行四大指針的初始操作。分配sk_buff如下所示:
sk_stream_alloc_skb最終調用__alloc_skb函數進行內存分配,分配skb后,進行四大指針的初始化操作:
其中skb_reset_tail_pointer(skb):
最終四大指針初始化為以下圖所示:
此時head、data、tail三個指針指向一起,end指向數據緩沖區的尾部。預留協議頭空間:在sk_stream_alloc_skb調用__alloc_skb函數進行內存分配后,下一步就會預留協議頭空間,使得head、tail、data指針分離:
skb_reserve如下,
操作后skb_buff的指針如下所示:
skb_reserve作用就是預留空間,而且是盡最大的空間預留,但它并沒有把數據放到該空間,只是簡單更新指針,預留空間!
因為很多頭都會有可選項,那么不知道頭部可選項是多大,所以只能按照最大的分配,同時也要明白一點,預留的空間headroom不一定使用完,可能還有剩余。當我們要增加協議頭信息的時候,data指針向上移動,當增加數據的時候tail指針向下移動,完成數據包的封裝。此時還沒有數據,data和tail指向相同。
操作tailroom中用戶數據塊區域:skb_put用于修改指向數據區末尾的指針tail:
可以看到tail指針的移動是擴大數據區域,即數據區向下擴大len字節,并更新數據區長度len。
增加headroom區域的協議頭:skb_push函數用于移動data指針,增加頭部協議,與skb_reserve()類似,也并沒有真正向數據緩存區中添加數據,而只是移動數據緩存區的頭指針data。數據由其他函數復制到數據緩存區中。函數如下:
如下兩張圖分別是由傳輸層、網絡層,數據包向下傳遞時data指針移動,進行頭部協議的封裝。
TCP層添加TCP首部。
SKB傳遞到IP層,IP層為數據包添加IP首部。
SKB傳遞到鏈路層,鏈路層為數據包添加鏈路層首部。
可以看到在數據包封裝的過程中,每一層移動data指針進行數據報頭的封裝。
數據報文解封裝,解除協議頭:skb_pull通過將data指針向下移動,進行數據報文的解封裝,函數如下所示:
如下圖所示,在收包流程上,向上層協議,如下網絡層向傳輸層傳送的時候,調用skb_pull進行數據包的解封裝。
以上就是struct sk_buff的四大指針的相關操作,通過分析可得:
head指向緩沖區的首地址,作為上邊界
end指向緩沖區的尾地址,作為下邊界
data指針在數據包頭部封裝和解封裝的過程中移動,指向各層的協議頭,skb_push函數將data的指向,向低地址移動(向上),完成協議頭空間的占據,skb_pull函數將data的指向,向高地址移動(向下),完成協議頭的解封裝。
tail指針在增加應用層用戶緩沖數據時移動,skb_put函數將該指針向高地址移動(向上),完成用戶數據空間的占據。
2、非線性區域
在1、中,可以看到每張sk_buff的圖:在end指針緊挨著一個非線性區域;
在struct sk_buff中沒有指向skb_shared_info結構的指針,利用end指針,可以用skb_shinfo宏來訪問:
其中skb_end_pointer函數如下,返回end指針
其中skb_frag_t如下:
nr_frags,frags,frag_list與IP分片存儲有關。
frag_list的用法:
用于在接收分組后鏈接多個分片,組成一個完整的IP數據報
在UDP數據報輸出中,將待分片的SKB鏈接到第一個SKB中,然后在輸出過程中能夠快速的分片
用于存放FRAGLIST類型的聚合分散I/O數據包
判斷是否存在非線性緩沖區:
先說明struct sk_buff中關于長度的兩個字段
len字段:無分片的報文,數據報文的大小
data_len字段:存在分散報文,data_len表示分片的部分大小
如下所示,沒有開啟分片的報文len = x,data_len = 0:
如下所示在Linux內核中,使用skb_is_nonlinear函數判斷是否存在分片,即通過判斷data_len的大小是否為0:
在沒有開啟分片的報文中,數據包長度在struct sk_buff中為len字段的大小,即data到tail的長度,nf_frags為0,frag_list為NULL。
普通聚合分散I/O的報文:
采用聚合分散I/O的報文,
frag_list為 NULL,nf_frags不等于0,說明這不是一個普通的分片,而是聚合分散I/O的報文。如下所示:nr_frags為2,而frag_list為NULL,說明這不是普通的分片,而是聚合分散I/O分片,數量為2,這兩個分片指向同一物理分頁,各自在分頁中的偏移和長度分別是0/S1和S1/S2。
FRAGLIST類型的分散聚合I/O的報文:
采用FRAGLIST類型的分散聚合I/O報文,frag_list不為NULL,nf_frags等于0,數據長度len為x+S1,data_len為S1。
以上從struct sk_buff的四大指針以及操作、非線性區域對套接字緩存(socket buffer)進行分析,更多sk_buff的分析、實操等將在以后的文章中梳理。
評論
查看更多