alloc_skb申請函數分析

alloc_skb()用於分配緩衝區的函數。由於"數據緩衝區"和"緩衝區的描述結構"(sk_buff結構)是兩種不同的實體,這就意味着,在分配一個緩衝區時,需要分配兩塊內存(一個是緩衝區,一個是緩衝區的描述結構sk_buff)。

首先看alloc_skb
  1. static inline struct sk_buff *alloc_skb(unsigned int size,
  2.                     gfp_t priority)
  3. {
  4.     return __alloc_skb(size, priority, 0, -1);
  5. }
這個函數比較簡單,參數中的size不用解釋,爲skb數據段的大小,但是第二個參數priority名字比較奇怪。叫優先級,實際上則是GFP MASK宏,如GFP_KERNEL,GFP_ATOMIC等。
 __alloc_skb()調用kmem_cache_alloc()從緩存中獲取一個sk_buff結構,並調用kmalloc_track_caller分配緩衝區

接下來看__alloc_skb
  1. /*
  2. 參數:
  3. size:skb的數據大小
  4. gfp_mask:不用解釋
  5. fclone:表示從哪個cache中分配
  6.        當fclone爲1時,從skbuff_fclone_cache上分配
  7.        當fclone爲0時,從skbuff_head_cache上分配
  8. node: NUMA節點
  9. */
  10. struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
  11.              int fclone, int node)
  12. {
  13.     struct kmem_cache *cache;
  14.     struct skb_shared_info *shinfo;
  15.     struct sk_buff *skb;
  16.     u8 *data;
     /* 獲得指定的cache */
  1.     cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;
     /* 從cache上分配, 如果cache上無法分配,則從內存中申請 */
  1.     /* Get the HEAD */
  2.     skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
  3.     if (!skb)
  4.         goto out;
  5.     prefetchw(skb);

  6.     size = SKB_DATA_ALIGN(size);
  7.     data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info),
  8.             gfp_mask, node);
  9.     if (!data)
  10.         goto nodata;
  11.     prefetchw(data + size);

  12.     /*
  13.      * Only clear those fields we need to clear, not those that we will
  14.      * actually initialise below. Hence, don't put any more fields after
  15.      * the tail pointer in struct 
  16.      */
  17.     memset(skb, 0, offsetof(struct sk_buff, tail));
  18.     skb->truesize = size + sizeof(struct sk_buff);
  19.     atomic_set(&skb->users, 1);
  20.     skb->head = data;
  21.     skb->data = data;
  22.     skb_reset_tail_pointer(skb);
  23.     skb->end = skb->tail + size;
  24.     kmemcheck_annotate_bitfield(skb, flags1);
  25.     kmemcheck_annotate_bitfield(skb, flags2);
  26. #ifdef NET_SKBUFF_DATA_USES_OFFSET
  27.     skb->mac_header = ~0U;
  28. #endif

  29.     /* make sure we initialize shinfo sequentially */
  30.     shinfo = skb_shinfo(skb);
  31.     memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
  32.     atomic_set(&shinfo->dataref, 1);

  33.     if (fclone) {
  34.         /* 如果是fclone cache的話,那麼skb的下一個buf,也將被分配*/
  35.         struct sk_buff *child = skb + 1;
  36.         atomic_t *fclone_ref = (atomic_t *) (child + 1);

  37.         kmemcheck_annotate_bitfield(child, flags1);
  38.         kmemcheck_annotate_bitfield(child, flags2);
  39.         skb->fclone = SKB_FCLONE_ORIG;
  40.         atomic_set(fclone_ref, 1);

  41.         child->fclone = SKB_FCLONE_UNAVAILABLE;
  42.     }
  43. out:
  44.     return skb;
  45. nodata:
  46.     kmem_cache_free(cache, skb);
  47.     skb = NULL;
  48.     goto out;
  49. }
這裏有兩個cache,skbuff_fclone_cache和skbuff_head_cache。它們兩個的區別是前者是每兩個skb爲一組。當從skbuff_fclone_cache分配skb時,會兩個連續的skb一起分配,但是釋放的時候可以分別釋放。也就是說當調用者知道需要兩個skb時,如後面的操作很可能使用skb_clone時,那麼從skbuff_fclone_cache上分配skb會更高效一些。

skb的分配細節
1. 關於 SKB 的分配細節.

LINUX 中 SKB 的分配最終是由函數 : struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,int fclone) 來完成.
SKB 可以分爲 SKB 描述符與 SKB 數據區兩個部分,其中描述符必須從 CACHE 中來分配 : 或者從skbuff_fclone_cache 中分配,或者從 skbuff_head_cache 中來分配.
如果從分配描述符失敗,則直接反回 NULL,表示 SKB 分配失敗.

SKB 描述符分配成功後,即可分配數據區.
在具體分配數據區之前首先要對數據區的長度進行 ALIGN 操作, 通過宏 SKB_DATA_ALIGN 來重新確定 size 大小. 然後調用 kmalloc 函數分配數據區 :
data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
需要注意的是數據區的大小是 SIZE 的大小加上 skb_shared_info 結構的大小.

數據區分配成功後,便對 SKB 描述符進行與此數據區相關的賦值操作 :
memset(skb, 0, offsetof(struct sk_buff, truesize));
skb->truesize = size + sizeof(struct sk_buff);
atomic_set(&skb->users, 1);
skb->head = data;
skb->data = data;
skb->tail = data;
skb->end = data + size;
需要主意的是, SKB 的 truesize 的大小並不包含 skb_shared_info 結構的大小. 另外,skb 的 end 成員指針也就事 skb_shared_info 結構的起始指針,系統用
一個宏 : skb_shinfo 來完成尋找 skb_shared_info 結構指針的操作.

最後,系統初始化 skb_shared_info 結構的成員變量 :
atomic_set(&(skb_shinfo(skb)->dataref), 1);
skb_shinfo(skb)->nr_frags = 0;
skb_shinfo(skb)->tso_size = 0;
skb_shinfo(skb)->tso_segs = 0;
skb_shinfo(skb)->frag_list = NULL;
skb_shinfo(skb)->ufo_size = 0;
skb_shinfo(skb)->ip6_frag_id = 0;

最後,返回 SKB 的指針.

2. SKB 的分配時機
SKB 的分配時機主要有兩種,最常見的一種是在網卡的中斷中,有數據包到達的時,系統分配 SKB 包進行包處理; 第二種情況是主動分配 SKB 包用於各種調試或者其他處理環境.

3. SKB 的 reserve 操作
SKB 在分配的過程中使用了一個小技巧 : 即在數據區中預留了 128 個字節大小的空間作爲協議頭使用, 通過移動 SKB 的 data 與 tail 指針的位置來實現這個功能.

4. SKB 的 put 操作
put 操作是 SKB 中一個非常頻繁也是非常重要的操作, 膽識, skb_put()函數其實什麼也沒做!
它只是根據數據的長度移動了 tail 指針並改寫了 skb->len 的值,其他的什麼都沒做,然後就返回了 skb->data 指針(就是 tail 指針在移動之前的位置). 看上去此函數彷彿要拷貝數據到 skb 的數據區中,其實這事兒是 insl 這個函數乾的,跟 skb_put() 函數毫不相關,不過它仍然很重要.

5. 中斷環境下 SKB 的分配流程
當數據到達網卡後,會觸發網卡的中斷,從而進入 ISR 中,系統會在 ISR 中計算出此次接收到的數據的字節數 : pkt_len, 然後調用 SKB 分配函數來分配 SKB :
skb = dev_alloc_skb(pkt_len+5);
我們可以看到, 實際上傳入的數據區的長度還要比實際接收到的字節數多,這實際上是一種保護機制. 實際上,在 dev_alloc_skb 函數調用 __dev_alloc_skb 函數,而 __dev_alloc_skb 函數又調用 alloc_skb 函數 時,其數據區的大小又增加了 128 字節, 這 128 字節就事前面我們所說的 reserve 機制預留的 header 空間.


本文參考資料:感謝作者的分享

發佈了28 篇原創文章 · 獲贊 40 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章