skbuff

在2.6.24以後這個結構體有了較大的變化,此處先說一說2.6.16版本的sk_buff,以及解釋一些問題。面試

 

1、數組

先直觀的看一下這個結構體~~~~~~~~~~~~~~~~~~~~~~在下面解釋每一個字段的意義~~~~~~~~~~~安全

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. struct sk_buff {  
  2.          /* These two members must be first. */  
  3.          struct sk_buff          *next;  
  4.          struct sk_buff          *prev;  
  5.    
  6.          struct sock             *sk;  
  7.          struct skb_timeval      tstamp;  
  8.          struct net_device       *dev;  
  9.          struct net_device       *input_dev;  
  10.    
  11.          union {  
  12.                  struct tcphdr   *th;  
  13.                  struct udphdr   *uh;  
  14.                  struct icmphdr  *icmph;  
  15.                  struct igmphdr  *igmph;  
  16.                  struct iphdr    *ipiph;  
  17.                  struct ipv6hdr  *ipv6h;  
  18.                  unsigned char   *raw;  
  19.          } h;  
  20.    
  21.          union {  
  22.                  struct iphdr    *iph;  
  23.                  struct ipv6hdr  *ipv6h;  
  24.                  struct arphdr   *arph;  
  25.                  unsigned char   *raw;  
  26.          } nh;  
  27.    
  28.          union {  
  29.                  unsigned char   *raw;  
  30.          } mac;  
  31.    
  32.          struct  dst_entry       *dst;  
  33.          struct  sec_path        *sp;  
  34.    
  35.          /* 
  36.           * This is the control buffer. It is free to use for every 
  37.           * layer. Please put your private variables there. If you 
  38.           * want to keep them across layers you have to do a skb_clone() 
  39.           * first. This is owned by whoever has the skb queued ATM. 
  40.           */  
  41.          char                    cb[48];  
  42.    
  43.          unsigned int            len,  
  44.                                  data_len,  
  45.                                  mac_len,  
  46.                                  csum;  
  47.          __u32                   priority;  
  48.          __u8                    local_df:1,  
  49.                                  cloned:1,  
  50.                                  ip_summed:2,  
  51.                                  nohdr:1,  
  52.                                  nfctinfo:3;  
  53.          __u8                    pkt_type:3,  
  54.                                  fclone:2,  
  55.                                  ipvs_property:1;  
  56.          __be16                  protocol;  
  57.    
  58.          void                    (*destructor)(struct sk_buff *skb);  
  59. #ifdef CONFIG_NETFILTER  
  60.          __u32                   nfmark;  
  61.          struct nf_conntrack     *nfct;  
  62. #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)  
  63.          struct sk_buff          *nfct_reasm;  
  64. #endif  
  65. #ifdef CONFIG_BRIDGE_NETFILTER  
  66.          struct nf_bridge_info   *nf_bridge;  
  67. #endif  
  68. #endif /* CONFIG_NETFILTER */  
  69. #ifdef CONFIG_NET_SCHED  
  70.          __u16                   tc_index;       /* traffic control index */  
  71. #ifdef CONFIG_NET_CLS_ACT  
  72.          __u16                   tc_verd;        /* traffic control verdict */  
  73. #endif  
  74. #endif  
  75.    
  76.    
  77.          /* These elements must be at the end, see alloc_skb() for details.  */  
  78.          unsigned int            truesize;  
  79.          atomic_t                users;  
  80.          unsigned char           *head,  
  81.                                  *data,  
  82.                                  *tail,  
  83.                                  *end;  
  84. };  

 

 

> : next和prev,這兩個域是用來鏈接相關的skb的(例如若是有分片,將這些分片鏈接在一塊兒能夠)網絡

> : sk,指向報文所屬的套接字指針tcp

> : tstamp,記錄接收或者傳輸報文的時間戳函數

> : dev和input_dev,記錄接收或者發送的設備atom

>: union u,對於一個層次,例如tcp層,可能有不少不一樣的協議,他們的協議頭不同,那麼這個聯合體就是記錄這些協議頭的。spa

     此處u就是表明傳輸層.net

> : union nh,表明網絡層頭指針

> : union mac,表明鏈路層頭

> : dst,指向des_entry結構,記錄了到達目的地的路由信息,以及其餘的一些網絡特徵信息。

> : sp:安全路徑,用於xfrm

> : cb[],保存與協議相關的控制信息,每一個協議可能獨立使用這些信息。

> : 重要的字段 len 和 data_len:

      len代: 表整個數據區域的長度!這裏要提早解釋幾個定義,skb的組成是有sk_buff控制 + 線性數據 + 非線性數據 

      (skb_shared_info) 組成!

     後面會具體解釋是什麼意思!在sk_buff這個裏面沒有實際的數據,這裏僅僅是控制信息,數據是經過後面的data指針指向其餘內

     存塊的!那個內存塊中是線性數據和

     非線性數據!那麼len就是length(線性數據) + length(非線性數據)!!!

     data_len: 指的是length(非線性數據)!!!那麼能夠知道:length(線性數據) =  skb->len - skb->data_len

> : mac_len,指的是mac頭長度

> : csum,某時刻協議的校驗和

> : priority,報文排隊優先級,取決於ip中的tos域

> : local_df,容許在本地分配

> : cloned,保存當前的skb_buff是克隆的仍是原始數據

> : ip_summed,是否計算ip校驗和

> : nohdr,僅僅引用數據區域

> : pkt_type,報文類型,例如廣播,多播,迴環,本機,傳出...

> : fclone,skb_buff克隆狀態

> : ipvs_property,skb_buff是否屬於ipvs

> : protocal,協議信息

> : nfmark,用於鉤子之間通訊

> : nfct_reasm,netfilter的跟蹤鏈接從新組裝指針

> : nf_bridge,保存橋接信息

> : tc_index: Traffic control index,tc_verd: traffic control verdict

> : truesize,該緩衝區分配的全部總的內存,包括:skb_buff + 全部數據大小

> : users,保存引用skb_buff的數量

> : 重要數據字段:head,data,tail,end!!!

    head:指向分配給的線性數據內存首地址( 創建起一個觀念:並非分配這麼多內存,就都能被使用做爲數據存儲,可能沒這麼多

    數據也有可能!可是也不要認爲分配這麼多 就足夠了,也不必定(非線性數據就是例子) )

    data:指向保存數據內容的首地址!咱們由head能夠知道,head和data不必定就是指在同一個位置!!!

    tail:指向數據的結尾!

    end:指向分配的內存塊的結尾! ( 由上面咱們知道數據結尾 != 分配的內存塊的結尾 )

    下面還會具體分析!!!!!!!!!!!

 

2、

我以爲須要先了解一些對於一個數據skb到底有什麼,或者說由哪些元素組成!這就須要知道所謂的 「線性數據」 和 「非線性數據」。

基本的組成以下:

> : sk_buff : 這是一個sk_buff的控制結構

> : 線性數據區域

> : 非線性數據區域( 由skb_shared_info結構體管理 )

 

那麼下面經過一個圖來看看這個skb結構究竟是怎麼樣的!看(圖一)

                                                                                    (圖一)

藉助圖一,咱們先來分析兩個重要字段:len和data_len

以前說過len表明的是整個數據的長度,data_len表明的是非線性數據長度。咱們由圖一能夠看到線性數據長度爲l1,再看看非線性數據,其實就是看frags[]和frag_list

ok...那麼咱們能夠知道非線性數據長度爲( l2 + ... + ln ) + ( l(n+1) + ... + lm )

即:len = l1 + ( l2 + ... + ln ) + ( l(n+1) + ... + lm )

        data_len = ( l2 + ... + ln ) + ( l(n+1) + ... + lm )

 

ok...

 

如今從分配內存開始解釋這個圖的由來:

咱們使用skb_alloc給skb分配空間,那麼剛剛分配結束返回時候,是什麼樣的狀況呢?看下圖(圖二):

                                                

                                                                                     (圖二)

 

剛剛開始初始化的時候,預分配一個一塊線性數據區域,這個區域通常放入的是各個協議層次的不一樣的頭,還有一些實際數據,下面的非線性區域是爲了彌補當數據真的不少的時候,做爲數據區域的擴展!關於skb_shared_info具體意思下面會繼續說!注意在初始化的時候,head,data和tail都指向內存的開始位置,head在這個位置始終不變,它表示的是分配的內存的開始位置。end的位置也是不變的,表示的是分配的內存的結束位置。data和tail會隨着數據的加入和減小變化,總之表示的是放入數據的內存區域(由圖一)可知。

 

如今須要解釋一下skb_shared_info這個結構體,這個結構體真的是很頗有特點!主要是其中的兩個字段frags和frag_list,下面繼續解釋:

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. struct skb_shared_info {  
  2.          atomic_t        dataref;        // 對象被引用次數  
  3.          unsigned short  nr_frags;       // 分頁段數目,即frags數組元素個數  
  4.          unsigned short  tso_size;         
  5.          unsigned short  tso_segs;  
  6.          unsigned short  ufo_size;  
  7.          unsigned int    ip6_frag_id;  
  8.          struct sk_buff  *frag_list;    // 通常用於分段(尚未很是清楚的理解)  
  9.          skb_frag_t      frags[MAX_SKB_FRAGS]; // 保存分頁數據(skb->data_len=全部的數組數據長度之和)  
  10. };  


關於frags和frag_list沒有必然的聯繫!

 

 

> : 對於frags[]通常用在,當數據真的不少,並且在線性數據區域裝不下的時候,須要使用這個,skb_frag_t中是一頁一頁的數據,先看看結構體:

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. struct skb_frag_struct {  
  2.          struct page *page;    // 表明一頁數據  
  3.          __u16 page_offset;    // 表明相對開始位置的頁偏移量  
  4.          __u16 size;           // page中數據長度  
  5. };  


須要注意的是:只有在DMA支持物理分散頁的Scatter/Gather(SG,分散/彙集)操做時候纔可使用frags[]來保存剩下的數據,不然,只能擴展線性數據區域進行保存!!!

 

這些頁實際上是其實就是虛擬頁映射到物理頁的結構,看下圖(圖三):

                                                                                         (圖三)

 

> : 對於frag_list來講,通常咱們在分片的時候裏面裝入每一個片的信息,注意,每一個片最終也都是被封裝成一個小的skb,這個必須

     的!

     注意:具體怎麼分片的看上一篇博文:數據分片 (  看其中的ip_fragment函數  )

     那麼看一下其基本結構如圖四:

                                                         

                                                                                         (圖四)

 

3、

最重要的是須要理解對於這個skb是怎麼操做的,在操做的過程當中,每一塊的內存分配是怎麼變化的,這才更重要!

看下面的函數們:

 

> : alloc_skb()函數

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. static inline struct sk_buff *alloc_skb(unsigned int size,  
  2.                                          gfp_t priority)  
  3. {  
  4.          return __alloc_skb(size, priority, 0);  
  5. }  

 

 

其實看__alloc_skb函數:

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,  
  2.                              int fclone)  
  3. {  
  4.          kmem_cache_t *cache;  
  5.          struct skb_shared_info *shinfo;  
  6.          struct sk_buff *skb;  
  7.          u8 *data;  
  8.    
  9.          cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;    // 根據克隆狀態來判斷在哪個緩衝區進行分配cache  
  10.    
  11.          /* Get the HEAD */  
  12.          skb = kmem_cache_alloc(cache, gfp_mask & ~__GFP_DMA);        // 獲得skb,注意這裏沒有包含數據,僅僅是skb_buff這個結構體  
  13.          if (!skb)  
  14.                  goto out;  
  15.    
  16.          /* Get the DATA. Size must match skb_add_mtu(). */  
  17.          size = SKB_DATA_ALIGN(size);                                     // 得到線性數據分片長度(注意對齊)  
  18.          data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask); // 注意分配的是什麼,是size + skb_shared_info!!!!!  
  19.          if (!data)  
  20.                  goto nodata;  
  21.    
  22.          memset(skb, 0, offsetof(struct sk_buff, truesize));          // 初始化  
  23.          skb->truesize = size + sizeof(struct sk_buff);               // 實際大小等於sk_buff + size,剛剛開始尚未非線性數據  
  24.          atomic_set(&skb->users, 1);                                    
  25.          skb->head = data;                                            // 注意指針,這個結合上面的圖一清二楚  
  26.          skb->data = data;  
  27.          skb->tail = data;  
  28.          skb->end  = data + size;  
  29.          /* make sure we initialize shinfo sequentially */  
  30.          shinfo = skb_shinfo(skb);  
  31.          atomic_set(&shinfo->dataref, 1);  
  32.          shinfo->nr_frags  = 0;  
  33.          shinfo->tso_size = 0;  
  34.          shinfo->tso_segs = 0;  
  35.          shinfo->ufo_size = 0;  
  36.          shinfo->ip6_frag_id = 0;  
  37.          shinfo->frag_list = NULL;  
  38.    
  39.          if (fclone) {  
  40.                  struct sk_buff *child = skb + 1;  
  41.                  atomic_t *fclone_ref = (atomic_t *) (child + 1);  
  42.    
  43.                  skb->fclone = SKB_FCLONE_ORIG;  
  44.                  atomic_set(fclone_ref, 1);  
  45.    
  46.                  child->fclone = SKB_FCLONE_UNAVAILABLE;  
  47.          }  
  48. out:  
  49.          return skb;  
  50. nodata:  
  51.          kmem_cache_free(cache, skb);  
  52.          skb = NULL;  
  53.          goto out;  
  54. }  

 

 

那麼alloc以後的圖就是(圖五):

                                               

                                                                                          (圖五)

其實和圖二是同樣的!咱們能夠看到,如今僅僅是分配了線束數據區域,可是如今尚未數據!必定要注意!因此前面三個指針指在一塊兒!由於沒有數據,那麼len和data_len的值就是0 !

 

> : skb_reserve函數

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. static inline void skb_reserve(struct sk_buff *skb, int len)  
  2. {  
  3.          skb->data += len;  
  4.          skb->tail += len;  
  5.  }  

 

 

代碼其實很easy、就是移動兩個指針而已~

 

這個函數很重要,是爲「協議頭」預留空間!並且是盡最大的空間預留,由於不少頭都會有可選項,那麼咱們不知道可選項是多大,因此只能是按照最大的分配,那麼也說明了一點,預留的空間headroom也就是不必定都能使用完的!可能還有剩餘的,由上面的圖也能夠看出來!這也是爲何須要這麼多指針的問題!那麼這個函數直接致使head指針和tail、data指針分離,入下面圖六所示:

                                           

                                                                                           (圖六)

 

注意headroom就是用來存儲各個協議頭的足夠大的空間,tailroom就能夠認爲是存儲其餘線性數據的空間。( 這裏不要曲解協議頭不是線性數據,其實協議頭也是!!!因此當增長頭的時候,data指針向上移動,當增長其餘數據的時候,tail指針向下移動 )。如今data和tail指向一塊兒,那麼仍是說明數據沒有!!!

 

> : skb_put函數 ----> 用於操做線性數據區域(tailroom區域)的用戶數據

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len)  
  2. {  
  3.          unsigned char *tmp = skb->tail;  
  4.          SKB_LINEAR_ASSERT(skb);            
  5.          skb->tail += len;                 // 移動指針  
  6.          skb->len  += len;                 // 數據空間增大len  
  7.          if (unlikely(skb->tail>skb->end)) // 若是tail指針超過end指針了,那麼處理錯誤~  
  8.                  skb_over_panic(skb, len, current_text_addr());  
  9.          return tmp;  
  10. }  



 

這函數其實就是從tailroom預留空間,至關因而移動tail指針,這樣若是從上圖(圖六)開始看,也就是tail開始向下移動,和data分離了。。。通常來講,這樣作都是爲了用戶數據再次處理,或者說爲TCP/IP的負載預留空間!

看圖七,當使用skb_put時候,由圖六---->圖七

                                                  

                                                                                          (圖七)

咱們能夠看到指針的移動data仍是在headroom的下面,中間的是用戶數據預留的部分,由skb_put獲得,tail表示數據結尾!再看一下sk_buff中的len,變成了數據長度ld!!

 

> : skb_push函數:----------> 用於操做headroom區域的協議頭

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. static inline unsigned char *skb_push(struct sk_buff *skb, unsigned int len)  
  2. {  
  3.          skb->data -= len;      // 向上移動指針  
  4.          skb->len  += len;      // 數據長度增長  
  5.          if (unlikely(skb->data<skb->head))  // data指針超過head那麼就是處理錯誤~  
  6.                  skb_under_panic(skb, len, current_text_addr());  
  7.          return skb->data;  
  8. }  

 

 

和skb_put對應,上面試操做用戶數據的,這裏是操做協議頭的!其實就是data指針向上移動而已~注意len增大了哦~前面說了協議頭也是屬於數據!

以下面圖所示,由圖七---->圖八

                                           

                                                                                        (圖八)

 

咱們能夠知道,data向上移動了,同時注意len變成ld+lp了,其中lp是這個增長的協議頭的長度!

 

> : skb_pull函數:-----------> 其實這個函數纔是與skb_push函數對應的函數!由於這是去頭函數,而skb_push是增頭函數!因此這個函數通常用在解包的時候!

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到個人代碼片
  1. static inline unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)  
  2. {  
  3.          return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);  
  4. }  
  5.    
  6.    
  7. static inline unsigned char *__pskb_pull(struct sk_buff *skb, unsigned int len)  
  8. {  
  9.          if (len > skb_headlen(skb) &&  
  10.              !__pskb_pull_tail(skb, len-skb_headlen(skb)))  
  11.                  return NULL;  
  12.          skb->len -= len;                              // 長度減少  
  13.          return skb->data += len;                      // 移動指針  
  14. }  

 

 

其實就是data指針向下移動,當前一個協議頭被去掉,headroom剩餘的空間增大了!看下圖:

由圖八---->圖九:

                                                     

                                                                                      (圖九)

 

虛線是data以前的指針位置,如今移動到下面實線!!需注意:len的長度減少,減少的大小是剝去的頭的大小!!

 

4、

最後咱們從兩條線總體分析一下:

1:從應用層用戶數據開始,直到物理層發送出去

      > 初始化的什麼就很少說了,和前面的差很少,如今也加入用戶數據已經在了,如圖七所示同樣!那麼到了TCP層,須要增長

         TCP層的頭:

         如圖10所示:

                                                   

                                                                                                  (圖10)

 

            須要注意的是這裏是傳輸層,那麼傳輸層的結構u中的th表明的是tcp的頭,那麼tcp指向tcp頭OK!同時注意 len長度+=l1 哦~~~

        > 再看到了IP層:如圖11

                                                   

                                                                                                 (圖11)

 

                至於須要解釋什麼就沒什麼了,都是同樣的~

             > 到鏈路層了:如圖12

                                                     

                                                                                             (圖12)

 

  OK!

 

2:第二個過程實際上是第一個逆過程,都差很少,因此很少說了~

 

5、

最後看一下操做skb的兩個函數pskb_copy和skb_copy

前者僅僅是將sk_buff的結構體和線性數據copy過來,對於非線性數據,是引用原始的skb的數據的!然後者是不只將sk_buff和線性數據拷貝,同時將非線性數據也copy了一份,看下面就明白了!這就在效率上就差了不少!因此若是不想修改數據,那麼仍是使用pskb_copy更好!

 

對於pskb_copy:

 

對於skb_copy:

 

OK  我以爲差很少了~~~~~結束~~~~~~~~~~~~~

相關文章
相關標籤/搜索