轉自:http://book.51cto.com/art/201206/345043.htm
《Linux內核源碼剖析:TCP/IP實現》本書詳細論述了Linux內核2.6.20版本中TCP/IP的實現。書中給出了大量的源代碼,通過對源代碼的詳細注釋,幫助讀者掌握TCP/IP的實現。本節為大家介紹數據預留和對齊。
3.4.4 數據預留和對齊
數據預留和對齊主要由skb_reserve()、skb_put()、skb_push()以及skb_pull()這幾個函數來完成。
1.skb_reserve()
skb_reserve()在數據緩存區頭部預留一定的空間,通常被用來在數據緩存區中插入協議首部或者在某個邊界上對齊。它並沒有把數據移出或移入數據緩存區,而只是簡單地更新了數據緩存區的兩個指針-分別指向負載起始和結尾的data和tail指針,圖3-15 展示了調用skb_reserve()前后這兩個指針的變化。
請注意:skb_reserve()只能用於空的SKB,通常會在分配SKB之后就調用該函數,此時data和tail指針還一同指向數據區的起始位置,如圖3-15a所示。例如,某個以太網設備驅動的接收函數,在分配SKB之后,向數據緩存區填充數據之前,會有這樣的一條語句skb_reserve(skb, 2),這是因為以太網頭長度為14B,再加上2B就正好16字節邊界對齊,所以大多數以太網設備都會在數據包之前保留2B。
當SKB在協議棧中向下傳遞時,每一層協議都把skb->data指針向上移動,然后復制本層首部,同時更新skb->len。這些操作都使用圖3-15 中所示的函數完成。
![]() |
圖3-15 在接收過程中使用skb_reserve() a) 空的SKB b) 頭部預留2個字節 c) 復制以太網幀到SKB |
2.skb_push()
skb_push()在數據緩存區的前頭加入一塊數據,與skb_reserve()類似,也並沒有真正向數據緩存區中添加數據,而只是移動數據緩存區的頭指針data和尾指針tail。數據由其他函數復制到數據緩存區中。
函數執行步驟如下:
1)當TCP發送數據時,會根據一些條件,如TCP最大分段長度MSS、是否支持聚合分散I/O等,分配一個SKB。
2)TCP需在數據緩存區的頭部預留足夠的空間,用來填充各層首部。MAX_TCP_HEADER是各層首部長度的總和,它考慮了最壞的情況:由於TCP層不知道將要用哪個接口發送包,它為每一層預留了最大的首部長度,甚至還考慮了出現多個IP首部的可能性,因為在內核編譯支持IP over IP的情況下,會遇到多個IP首部。
3)把TCP負載復制到數據緩存區。需要注意的是,圖3-16 只是一個例子,TCP負載可能會被組織成其他形式,例如分片,在后續章節中將會看到一個分片的數據緩存區是什么樣的。
![]() |
圖3-16 TCP層向鏈路層傳遞時數據的填充過程 a) 空的SKB b) 在SKB的頭部預留足夠的空間 c) 復制TCP數據 d) 添加TCP首部 e) 添加IP首部 f) 添加以太網幀首部 |
4)TCP層添加TCP首部。
5)SKB傳遞到IP層,IP層為數據包添加IP首部。
6)SKB傳遞到鏈路層,鏈路層為數據包添加鏈路層首部。
3.skb_put()
skb_put()修改指向數據區末尾的指針tail,使之往下移len字節,即使數據區向下擴大len字節,並更新數據區長度len。調用skb_put()前后,SKB結構變化如圖3-17所示。
![]() |
圖3-17 skb_put()示意 a) 調用前 b) 調用后 |
4.skb_pull()
skb_pull()通過將data指針往下移動,在數據區首部忽略len字節長度的數據,通常用於接收到數據包后在各層間由下往上傳遞時,上層忽略下層的首部。調用skb_pull()前后,SKB結構變化如圖3-18所示。
![]() |
圖3-18 skb_pull()示意 a) 調用前 b) 調用后 |