Linux2.6.39內核sk_buff的結構分析

sk_buff結構位於include/linux/skbuff.h中,其含義爲“套接字緩衝區”,用在linux網絡子系統中的各層之間的數據傳遞,是linux網絡子系統數據傳遞的"神經樞紐"

當發送數據包的時候,Linux內核的網絡處理模塊必須建立一個包含要傳送的數據包的sk_buff,然後將sk_buff遞交給下層,各層在sk_buff中添加不同的協議頭直至交給網絡設備發送,同理當網絡設備接收到數據包時,必須將接收到的數據轉換爲sk_buff數據結構並傳遞給上層。各層去掉相應的協議頭直至交給用戶

sk_buff_head的結構也就是sk_buff的頭

struct sk_buff_head {
    /* These two members must be first. */
    struct sk_buff    *next;
    struct sk_buff    *prev;
    __u32        qlen;
    spinlock_t    lock;
};

可以看到前兩個域是和sk_buff一致的,而且內核的註釋是必須放到最前面。這裏的原因是:
這使得兩個不同的結構可以放到同一個鏈表中,儘管sk_buff_head要比sk_buff小巧的多。另外,相同的函數可以同樣應用於sk_buff和sk_buff_head。
qlen域表示了當前的sk_buff鏈表上包含多少個skb。
lock域是自旋鎖。

然後我們來看sk_buff,下面就是skb的結構:

我這裏註釋了一些簡單的域,複雜的域下面會單獨解釋。
Java代碼  收藏代碼
  1. struct sk_buff {  
  2.     /* These two members must be first. */  
  3.     struct sk_buff      *next;  
  4.     struct sk_buff      *prev;  
  5.   
  6. //表示從屬於那個socket,主要是被4層用到。  
  7.     struct sock     *sk;  
  8. //表示這個skb被接收的時間。  
  9.     ktime_t         tstamp;  
  10. //這個表示一個網絡設備,當skb爲輸出時它表示skb將要輸出的設備,當接收時,它表示輸入設備。要注意,這個設備有可能會是虛擬設備(在3層以上看來)  
  11.     struct net_device   *dev;  
  12. ///這裏其實應該是dst_entry類型,不知道爲什麼內核要改爲ul。這個域主要用於路由子系統。這個數據結構保存了一些路由相關信息  
  13.     unsigned long       _skb_dst;  
  14. #ifdef CONFIG_XFRM  
  15.     struct  sec_path    *sp;  
  16. #endif  
  17. ///這個域很重要,我們下面會詳細說明。這裏只需要知道這個域是保存每層的控制信息的就夠了。  
  18.     char            cb[48];  
  19. ///這個長度表示當前的skb中的數據的長度,這個長度即包括buf中的數據也包括切片的數據,也就是保存在skb_shared_info中的數據。這個值是會隨着從一層到另一層而改變的。下面我們會對比這幾個長度的。  
  20.     unsigned int        len,  
  21. ///這個長度只表示切片數據的長度,也就是skb_shared_info中的長度。  
  22.                 data_len;  
  23. ///這個長度表示mac頭的長度(2層的頭的長度)  
  24.     __u16           mac_len,  
  25. ///這個主要用於clone的時候,它表示clone的skb的頭的長度。  
  26.                 hdr_len;  
  27.   
  28. ///接下來是校驗相關的域。  
  29.     union {  
  30.         __wsum      csum;  
  31.         struct {  
  32.             __u16   csum_start;  
  33.             __u16   csum_offset;  
  34.         };  
  35.     };  
  36. ///優先級,主要用於QOS。  
  37.     __u32           priority;  
  38.     kmemcheck_bitfield_begin(flags1);  
  39. ///接下來是一些標誌位。  
  40. //首先是是否可以本地切片的標誌。  
  41.     __u8            local_df:1,  
  42. ///爲1說明頭可能被clone。  
  43.                 cloned:1,  
  44. ///這個表示校驗相關的一個標記,表示硬件驅動是否爲我們已經進行了校驗(前面的blog有介紹)  
  45.                 ip_summed:2,  
  46. ///這個域如果爲1,則說明這個skb的頭域指針已經分配完畢,因此這個時候計算頭的長度只需要head和data的差就可以了。  
  47.                 nohdr:1,  
  48. ///這個域不太理解什麼意思。  
  49.                 nfctinfo:3;  
  50.   
  51. ///pkt_type主要是表示數據包的類型,比如多播,單播,迴環等等。  
  52.     __u8            pkt_type:3,  
  53. ///這個域是一個clone標記。主要是在fast clone中被設置,我們後面講到fast clone時會詳細介紹這個域。  
  54.                 fclone:2,  
  55. ///ipvs擁有的域。  
  56.                 ipvs_property:1,  
  57. ///這個域應該是udp使用的一個域。表示只是查看數據。  
  58.                 peeked:1,  
  59. ///netfilter使用的域。是一個trace 標記  
  60.                 nf_trace:1;  
  61. ///這個表示L3層的協議。比如IP,IPV6等等。  
  62.     __be16          protocol:16;  
  63.     kmemcheck_bitfield_end(flags1);  
  64. ///skb的析構函數,一般都是設置爲sock_rfree或者sock_wfree.  
  65.     void            (*destructor)(struct sk_buff *skb);  
  66.   
  67. ///netfilter相關的域。  
  68. #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)  
  69.     struct nf_conntrack *nfct;  
  70.     struct sk_buff      *nfct_reasm;  
  71. #endif  
  72. #ifdef CONFIG_BRIDGE_NETFILTER  
  73.     struct nf_bridge_info   *nf_bridge;  
  74. #endif  
  75.   
  76. ///接收設備的index。  
  77.     int         iif;  
  78.   
  79. ///流量控制的相關域。  
  80. #ifdef CONFIG_NET_SCHED  
  81.     __u16           tc_index;   /* traffic control index */  
  82. #ifdef CONFIG_NET_CLS_ACT  
  83.     __u16           tc_verd;    /* traffic control verdict */  
  84. #endif  
  85. #endif  
  86.   
  87.     kmemcheck_bitfield_begin(flags2);  
  88. ///多隊列設備的映射,也就是說映射到那個隊列。  
  89.     __u16           queue_mapping:16;  
  90. #ifdef CONFIG_IPV6_NDISC_NODETYPE  
  91.     __u8            ndisc_nodetype:2;  
  92. #endif  
  93.     kmemcheck_bitfield_end(flags2);  
  94.   
  95.     /* 0/14 bit hole */  
  96.   
  97. #ifdef CONFIG_NET_DMA  
  98.     dma_cookie_t        dma_cookie;  
  99. #endif  
  100. #ifdef CONFIG_NETWORK_SECMARK  
  101.     __u32           secmark;  
  102. #endif  
  103. ///skb的標記。  
  104.     __u32           mark;  
  105.   
  106. ///vlan的控制tag。  
  107.     __u16           vlan_tci;  
  108.   
  109. ///傳輸層的頭  
  110.     sk_buff_data_t      transport_header;  
  111. ///網絡層的頭  
  112.     sk_buff_data_t      network_header;  
  113. ///鏈路層的頭。  
  114.     sk_buff_data_t      mac_header;  
  115. ///接下來就是幾個操作skb數據的指針。下面會詳細介紹。  
  116.     sk_buff_data_t      tail;  
  117.     sk_buff_data_t      end;  
  118.     unsigned char       *head,  
  119.                 *data;  
  120. ///這個表示整個skb的大小,包括skb本身,以及數據。  
  121.     unsigned int        truesize;  
  122. ///skb的引用計數  
  123.     atomic_t        users;  
  124. };  


我們來看前面沒有解釋的那些域。

先來看cb域,他保存了每層所獨自需要的內部數據。我們來看tcp的例子。

我們知道tcp層的控制信息保存在tcp_skb_cb中,因此來看內核提供的宏來存取這個數據結構:

Java代碼  收藏代碼
  1. #define TCP_SKB_CB(__skb)  ((struct tcp_skb_cb *)&((__skb)->cb[0]))  


在ip層的話,我們可能會用cb來存取切片好的幀。

Java代碼  收藏代碼
  1. #define FRAG_CB(skb)    ((struct ipfrag_skb_cb *)((skb)->cb))  


到這裏你可能會問如果我們想要在到達下一層後,還想保存當前層的私有信息怎麼辦。這個時候我們就可以使用skb的clone了。也就是之只複製sk_buff結構。

然後我們來看幾個比較比較重要的域 len,data,tail,head,end。

這幾個域都很簡單,下面這張圖表示了buffer從tcp層到鏈路層的過程中len,head,data,tail以及end的變化,通過這個圖我們可以非常清晰的瞭解到這幾個域的區別。




可以很清楚的看到head指針爲分配的buffer的起始位置,end爲結束位置,而data爲當前數據的起始位置,tail爲當前數據的結束位置。len就是數據區的長度。

然後來看transport_header,network_header以及mac_header的變化,這幾個指針都是隨着數據包到達不同的層次纔會有對應的值,我們來看下面的圖,這個圖表示了當從2層到達3層對應的指針的變化。




這裏可以看到data指針會由於數據包到了三層,而跳過2層的頭。這裏我們就可以得到data起始真正指的是本層的頭以及數據的起始位置。

然後我們來看skb的幾個重要操作函數。

首先是skb_put,skb_push,skb_pull以及skb_reserve這幾個最長用的操作data指針的函數。

這裏可以看到內核skb_XXX都還有一個__skb_XXX函數,這是因爲前一個只是將後一個函數進行了一個包裝,加了一些校驗。

先來看__skb_put函數。
可以看到它只是將tail指針移動len個位置,然後len也相應的增加len個大小。

Java代碼  收藏代碼
  1. static inline unsigned char *__skb_put(struct sk_buff *skb, unsigned int len)  
  2. {  
  3.     unsigned char *tmp = skb_tail_pointer(skb);  
  4.     SKB_LINEAR_ASSERT(skb);  
  5. ///改變相應的域。  
  6.     skb->tail += len;  
  7.     skb->len  += len;  
  8.     return tmp;  
  9. }  


然後是__skb_push,它是將data指針向上移動len個位置,對應的len肯定也是增加len大小。

Java代碼  收藏代碼
  1. static inline unsigned char *__skb_push(struct sk_buff *skb, unsigned int len)  
  2. {  
  3.     skb->data -= len;  
  4.     skb->len  += len;  
  5.     return skb->data;  
  6. }  


剩下的兩個就不貼代碼了,都是很簡單的函數,__skb_pull是將data指針向下移動len個位置,然後len減小len大小。__skb_reserve是將整個數據區,也就是data以及tail指針一起向下移動len大小。這個函數一般是用來對齊地址用的。

看下面的圖,描述了4個函數的操作:




接着是skb的alloc函數。

在內核中分配一個skb是在__alloc_skb中實現的,接下來我們就來看這個函數的具體實現。

這個函數起始可以看作三部分,第一部分是從cache中分配內存,第二部分是初始化分配的skb的相關域。第三部分是處理fclone。

還有一個要注意的就是這裏__alloc_skb是被三個函數包裝後才能直接使用的,我們只看前兩個,一個是skb_alloc_skb,一個是alloc_skb_fclone函數,這兩個函數傳遞進來的第三個參數,也就是fclone前一個是0,後一個是1.

那麼這個函數是什麼意思呢,它和alloc_skb有什麼區別的。

這個函數可以叫做Fast SKB cloning函數,這個函數存在的主要原因是,以前我們每次skb_clone一個skb的時候,都是要調用kmem_cache_alloc從cache中alloc一塊新的內存。而現在當我們擁有了fast clone之後,通過調用alloc_skb_fclone函數來分配一塊大於sizeof(struct sk_buff)的內存,也就是在這次請求的skb的下方多申請了一些內存,然後返回的時候設置返回的skb的fclone標記爲SKB_FCLONE_ORIG,而多申請的那塊內存的sk_buff的fclone爲SKB_FCLONE_UNAVAILABLE,這樣當我們調用skb_clone克隆這個skb的時候看到fclone的標記就可以直接將skb的指針+1,而不需要從cache中取了。這樣的話節省了一次內存存取,提高了clone的效率,不過調用flcone 一般都是我們確定接下來這個skb會被clone很多次。

更詳細的fclone的介紹可以看這裏:

http://lwn.net/Articles/140552/

這樣我們先來看_alloc_skb,然後緊接着看skb_clone,這樣就能更好的理解這些。

這裏fclone的多分配的內存部分,沒太弄懂從那裏多分配的,自己對內核的內存子系統還是不太熟悉。覺得應該是skbuff_fclone_cache中會自動多分配些內存。

Java代碼  收藏代碼
  1. struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,  
  2.                 int fclone, int node)  
  3. {  
  4.     struct kmem_cache *cache;  
  5.     struct skb_shared_info *shinfo;  
  6.     struct sk_buff *skb;  
  7.     u8 *data;  
  8.   
  9. ///這裏通過fclone的值來判斷是要從fclone cache還是說從head cache中取。  
  10.     cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;  
  11.   
  12. ///首先是分配skb,也就是包頭。  
  13.     skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);  
  14.     if (!skb)  
  15.         goto out;  
  16. ///首先將size對齊,這裏是按一級緩存的大小來對齊。  
  17.     size = SKB_DATA_ALIGN(size);  
  18. ///然後是數據區的大小,大小爲size+ sizeof(struct skb_shared_info的大小。  
  19.     data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info),  
  20.             gfp_mask, node);  
  21.     if (!data)  
  22.         goto nodata;  
  23.   
  24. ///初始化相關域。  
  25.     memset(skb, 0, offsetof(struct sk_buff, tail));  
  26. ///這裏truesize可以看到就是我們分配的整個skb+data的大小  
  27.     skb->truesize = size + sizeof(struct sk_buff);  
  28. ///users加一。  
  29.     atomic_set(&skb->users, 1);  
  30. ///一開始head和data是一樣大的。  
  31.     skb->head = data;  
  32.     skb->data = data;  
  33. ///設置tail指針  
  34.     skb_reset_tail_pointer(skb);  
  35. ///一開始tail也就是和data是相同的。  
  36.     skb->end = skb->tail + size;  
  37.     kmemcheck_annotate_bitfield(skb, flags1);  
  38.     kmemcheck_annotate_bitfield(skb, flags2);  
  39. #ifdef NET_SKBUFF_DATA_USES_OFFSET  
  40.     skb->mac_header = ~0U;  
  41. #endif  
  42.   
  43. ///初始化shinfo,這個我就不介紹了,前面的blog分析切片時,這個結構很詳細的分析過了。  
  44.     shinfo = skb_shinfo(skb);  
  45.     atomic_set(&shinfo->dataref, 1);  
  46.     shinfo->nr_frags  = 0;  
  47.     shinfo->gso_size = 0;  
  48.     shinfo->gso_segs = 0;  
  49.     shinfo->gso_type = 0;  
  50.     shinfo->ip6_frag_id = 0;  
  51.     shinfo->tx_flags.flags = 0;  
  52.     skb_frag_list_init(skb);  
  53.     memset(&shinfo->hwtstamps, 0, sizeof(shinfo->hwtstamps));  
  54.   
  55. ///fclone爲1,說明多分配了一塊內存,因此需要設置對應的fclone域。  
  56.     if (fclone) {  
  57. ///可以看到多分配的內存剛好在當前的skb的下方。  
  58.         struct sk_buff *child = skb + 1;  
  59.         atomic_t *fclone_ref = (atomic_t *) (child + 1);  
  60.   
  61.         kmemcheck_annotate_bitfield(child, flags1);  
  62.         kmemcheck_annotate_bitfield(child, flags2);  
  63. ///設置標記。這裏要注意,當前的skb和多分配的skb設置的fclone是不同的。  
  64.         skb->fclone = SKB_FCLONE_ORIG;  
  65.         atomic_set(fclone_ref, 1);  
  66.   
  67.         child->fclone = SKB_FCLONE_UNAVAILABLE;  
  68.     }  
  69. out:  
  70.     return skb;  
  71. nodata:  
  72.     kmem_cache_free(cache, skb);  
  73.     skb = NULL;  
  74.     goto out;  
  75. }  


下圖就是alloc_skb之後的skb的指針的狀態。這裏忽略了fclone。




然後我們來看skb_clone函數,clone的意思就是隻複製skb而不復制data域。

這裏它會先判斷將要被clone的skb的fclone段,以便與決定是否重新分配一塊內存來保存skb。

然後調用__skb_clone來初始化相關的域。

Java代碼  收藏代碼
  1. struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)  
  2. {  
  3.     struct sk_buff *n;  
  4.   
  5. ///n爲skb緊跟着那塊內存,這裏如果skb是通過skb_fclone分配的,那麼n就是一個skb。  
  6.     n = skb + 1;  
  7. ///skb和n的fclone都要符合要求,可以看到這裏的值就是我們在__alloc_skb中設置的值。  
  8.     if (skb->fclone == SKB_FCLONE_ORIG &&  
  9.         n->fclone == SKB_FCLONE_UNAVAILABLE) {  
  10. ///到這裏,就說明我們不需要alloc一個skb,直接取n就可以了,並且設置fclone的標記。並修改引用計數。  
  11.         atomic_t *fclone_ref = (atomic_t *) (n + 1);  
  12.         n->fclone = SKB_FCLONE_CLONE;  
  13.         atomic_inc(fclone_ref);  
  14.     } else {  
  15.   
  16. ///這裏就需要從cache中取得一塊內存。  
  17.         n = kmem_cache_alloc(skbuff_head_cache, gfp_mask);  
  18.         if (!n)  
  19.             return NULL;  
  20.   
  21.         kmemcheck_annotate_bitfield(n, flags1);  
  22.         kmemcheck_annotate_bitfield(n, flags2);  
  23. ///設置新的skb的fclone域。這裏我們新建的skb,沒有被fclone的都是這個標記。  
  24.         n->fclone = SKB_FCLONE_UNAVAILABLE;  
  25.     }  
  26.   
  27.     return __skb_clone(n, skb);  
  28. }  


這裏__skb_clone就不介紹了,函數就是將要被clone的skb的域賦值給clone的skb。

下圖就是skb_clone之後的兩個skb的結構圖:



當一個skb被clone之後,這個skb的數據區是不能被修改的,這就意爲着,我們存取數據不需要任何鎖。可是有時我們需要修改數據區,這個時候會有兩個選擇,一個是我們只修改linear段,也就是head和end之間的段,一種是我們還要修改切片數據,也就是skb_shared_info.

這樣就有兩個函數供我們選擇,第一個是pskb_copy,第二個是skb_copy.

我們先來看pskb_copy,函數先alloc一個新的skb,然後調用skb_copy_from_linear_data來複制線性區的數據。

Java代碼  收藏代碼
  1. struct sk_buff *pskb_copy(struct sk_buff *skb, gfp_t gfp_mask)  
  2. {  
  3.     /* 
  4.      *  Allocate the copy buffer 
  5.      */  
  6.     struct sk_buff *n;  
  7. #ifdef NET_SKBUFF_DATA_USES_OFFSET  
  8.     n = alloc_skb(skb->end, gfp_mask);  
  9. #else  
  10.     n = alloc_skb(skb->end - skb->head, gfp_mask);  
  11. #endif  
  12.     if (!n)  
  13.         goto out;  
  14.   
  15.     /* Set the data pointer */  
  16.     skb_reserve(n, skb->data - skb->head);  
  17.     /* Set the tail pointer and length */  
  18.     skb_put(n, skb_headlen(skb));  
  19. ///複製線性數據段。  
  20.     skb_copy_from_linear_data(skb, n->data, n->len);  
  21. ///更新相關域  
  22.     n->truesize += skb->data_len;  
  23.     n->data_len  = skb->data_len;  
  24.     n->len        = skb->len;  
  25.   
  26. ///下面只是複製切片數據的指針  
  27. if (skb_shinfo(skb)->nr_frags) {  
  28.         int i;  
  29.   
  30.         for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {  
  31.             skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i];  
  32.             get_page(skb_shinfo(n)->frags[i].page);  
  33.         }  
  34.         skb_shinfo(n)->nr_frags = i;  
  35.     }  
  36.   
  37. ...............................  
  38.     copy_skb_header(n, skb);  
  39. out:  
  40.     return n;  
  41. }  


然後是skb_copy,它是複製skb的所有數據段,包括切片數據:

Java代碼  收藏代碼
  1. struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)  
  2. {  
  3.     int headerlen = skb->data - skb->head;  
  4.     /* 
  5.      *  Allocate the copy buffer 
  6.      */  
  7. //先alloc一個新的skb  
  8.     struct sk_buff *n;  
  9. #ifdef NET_SKBUFF_DATA_USES_OFFSET  
  10.     n = alloc_skb(skb->end + skb->data_len, gfp_mask);  
  11. #else  
  12.     n = alloc_skb(skb->end - skb->head + skb->data_len, gfp_mask);  
  13. #endif  
  14.     if (!n)  
  15.         return NULL;  
  16.   
  17.     /* Set the data pointer */  
  18.     skb_reserve(n, headerlen);  
  19.     /* Set the tail pointer and length */  
  20.     skb_put(n, skb->len);  
  21. ///然後複製所有的數據。  
  22.     if (skb_copy_bits(skb, -headerlen, n->head, headerlen + skb->len))  
  23.         BUG();  
  24.   
  25.     copy_skb_header(n, skb);  
  26.     return n;  
  27. }  


下面這張圖就表示了psb_copy和skb_copy調用後的內存模型,其中a是pskb_copy,b是skb_copy:





最後來看skb的釋放:
這裏主要是判斷一個引用標記位users,將它減一,如果大於0則直接返回,否則釋放skb。

Java代碼  收藏代碼
  1. void kfree_skb(struct sk_buff *skb)  
  2. {  
  3.     if (unlikely(!skb))  
  4.         return;  
  5.     if (likely(atomic_read(&skb->users) == 1))  
  6.         smp_rmb();  
  7. ///減一,然後判斷。  
  8.     else if (likely(!atomic_dec_and_test(&skb->users)))  
  9.         return;  
  10.     trace_kfree_skb(skb, __builtin_return_address(0));  
  11.     __kfree_skb(skb);  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章