1. 程式人生 > >alloc_skb申請函數分析

alloc_skb申請函數分析

sign star actually 的區別 init res user hose any

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

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

接下來看__alloc_skb
/*
參數:
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 */
cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;
/* 從cache上分配, 如果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;
}
這裏有兩個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 空間.


本文參考資料:感謝作者的分享
http://blog.chinaunix.net/uid-23629988-id-257902.html
http://blog.chinaunix.net/uid-1728743-id-24312.html
---------------------
作者:不會遊泳的魚star
來源:CSDN
原文:https://blog.csdn.net/star_xiong/article/details/17588629
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

alloc_skb申請函數分析