alloc_skb()用於分配緩衝區的函數。由於"數據緩衝區"和"緩衝區的描述結構"(sk_buff結構)是兩種不同的實體,這就意味着,在分配一個緩衝區時,需要分配兩塊內存(一個是緩衝區,一個是緩衝區的描述結構sk_buff)。
-
static inline struct sk_buff *alloc_skb(unsigned int size,
-
gfp_t priority)
-
{
-
return __alloc_skb(size, priority, 0, -1);
- }
- /*
- 參數:
- size:skb的數據大小
- gfp_mask:不用解釋
- fclone:表示從哪個cache中分配
- 當fclone爲1時,從skbuff_fclone_cache上分配
- 當fclone爲0時,從skbuff_head_cache上分配
- node: NUMA節點
- */
-
struct sk_buff *__alloc_skb(unsigned int size, gfp_t
gfp_mask,
-
int fclone, int node)
-
{
-
struct kmem_cache *cache;
-
struct skb_shared_info *shinfo;
-
struct sk_buff *skb;
-
u8 *data;
-
cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;
-
/* Get the HEAD */
-
skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
-
if (!skb)
-
goto out;
-
prefetchw(skb);
-
-
size = SKB_DATA_ALIGN(size);
-
data = kmalloc_node_track_caller(size + sizeof(struct
skb_shared_info),
-
gfp_mask, node);
-
if (!data)
-
goto nodata;
-
prefetchw(data + size);
-
-
/*
-
* Only clear those fields we need to clear, not those
that we will
-
* actually initialise below. Hence, don't
put any more fields after
-
* the tail pointer in struct
-
*/
-
memset(skb, 0, offsetof(struct
sk_buff, tail));
-
skb->truesize = size + sizeof(struct
sk_buff);
-
atomic_set(&skb->users, 1);
-
skb->head = data;
-
skb->data = data;
-
skb_reset_tail_pointer(skb);
-
skb->end = skb->tail + size;
-
kmemcheck_annotate_bitfield(skb, flags1);
-
kmemcheck_annotate_bitfield(skb, flags2);
-
#ifdef NET_SKBUFF_DATA_USES_OFFSET
-
skb->mac_header = ~0U;
-
#endif
-
-
/* make sure we initialize shinfo sequentially */
-
shinfo = skb_shinfo(skb);
-
memset(shinfo, 0, offsetof(struct
skb_shared_info, dataref));
-
atomic_set(&shinfo->dataref, 1);
-
-
if (fclone) {
- /* 如果是fclone cache的話,那麼skb的下一個buf,也將被分配*/
-
struct sk_buff *child = skb + 1;
-
atomic_t *fclone_ref = (atomic_t *) (child + 1);
-
-
kmemcheck_annotate_bitfield(child, flags1);
-
kmemcheck_annotate_bitfield(child, flags2);
-
skb->fclone = SKB_FCLONE_ORIG;
-
atomic_set(fclone_ref, 1);
-
-
child->fclone = SKB_FCLONE_UNAVAILABLE;
-
}
-
out:
-
return skb;
-
nodata:
-
kmem_cache_free(cache, skb);
-
skb = NULL;
-
goto out;
- }
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 空間.
本文參考資料:感謝作者的分享