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

alloc_skb申請函式分析

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
版權宣告:本文為博主原創文章,轉載請附上博文連結!