天天看點

關于skb_header_pointer函數

              關于skb_header_pointer函數 2012-02-21 15:17:46

分類:

原文位址:關于skb_header_pointer函數 作者:luoyan_xy

   最近一段時間看核心代碼,總是看到skb_header_pointer函數,這個函數的主要功能很簡單,就是從skb字段中擷取指定長度到内容到緩存中。函數原型是這個樣子的: static inline void *skb_header_pointer(const struct sk_buff *skb, int offset,

           int len, void *buffer)    也就是從skb中skb->data開始的offset偏移處,擷取len長度的内容到buff中。       看起來這個并沒有什麼問題,不過無意中在網上搜了一下這個函數,卻發現還真有一些别的意義在裡面。。好吧,看一下代碼,依然是2.6.30版本核心。 static inline void *skb_header_pointer(const struct sk_buff *skb, int offset,

           int len, void *buffer)

    {

       int hlen = skb_headlen(skb);          if (hlen - offset >= len)

          return skb->data + offset;          if ( skb_copy_bits(skb, offset, buffer, len) < 0)

          return NULL;          return buffer;

    } 參數為:

   skb:資料包struct sk_buff的指針

   offset:相對資料起始頭(如IP頭)的偏移量

   len:資料長度

   buffer:緩沖區,大小不小于len      其中skb_headlen()函數的定義為: static inline unsigned int skb_headlen(const struct sk_buff *skb)

   {

       return skb->len - skb->data_len;

   }

   其中skb->len是資料包長度,在IPv4中就是單個完整IP包的總長,但這些資料并不一定都在目前記憶體頁;skb->data_len表示在其他頁的資料長度(包括本skb在其他頁中的資料以及分片skb中的資料),是以skb->len - skb->data_len表示在目前頁的資料大小。      如果skb->data_len不為0,表示該IP包的資料分屬不同的頁,該資料包也就被成為非線性化的,函數skb_is_nonlinear()就是通過該參數判斷,一般剛進行完碎片重組的skb包就屬于此類。    這樣skb_header_pointer()函數就好了解了,先判斷要處理的資料是否都在目前頁面内,如果是,則傳回可以直接對資料處理,傳回所求資料指針,否則用skb_copy_bits()函數進行拷貝,下面再來看一下這個函數的實作過程,并不複雜。      int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)

   {

      int i, copy;

      int start = skb_headlen(skb);         if (offset > (int)skb->len - len)

         goto fault;         

      if ((copy = start - offset) > 0) {

         if (copy > len)

             copy = len;

         skb_copy_from_linear_data_offset(skb, offset, to, copy);

      if ((len -= copy) == 0)

         return 0;

      offset += copy;

      to     += copy;

     }        

      for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {

         int end;            WARN_ON(start > offset + len);            end = start + skb_shinfo(skb)->frags[i].size;

         if ((copy = end - offset) > 0) {

              u8 *vaddr;                 if (copy > len)

                 copy = len;                 vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);

              memcpy(to,

                    vaddr + skb_shinfo(skb)->frags[i].page_offset+

                    offset - start, copy);

              kunmap_skb_frag(vaddr);                 if ((len -= copy) == 0)

                  return 0;

              offset += copy;

              to     += copy;

         }

         start = end;

     }        

     if (skb_shinfo(skb)->frag_list) {

         struct sk_buff *list = skb_shinfo(skb)->frag_list;            for (; list; list = list->next) {

            int end;               WARN_ON(start > offset + len);               end = start + list->len;

            if ((copy = end - offset) > 0) {

                if (copy > len)

                    copy = len;

                if ( skb_copy_bits(list, offset - start,

                               to, copy))

                    goto fault;

                if ((len -= copy) == 0)

                    return 0;

                offset += copy;

                to     += copy;

            }

            start = end;

        }

   }

   if (!len)

       return 0;   fault:

   return -EFAULT;

}      其中的skb_copy_from_linear_data_offset函數,就是一個線性拷貝的過程,内部是對memcpy的一個封裝。    kmap_skb_frap沒有看明白,不過其意思應該是把分片資料所在的page位址映射到了一個核心可通路的虛拟位址上,通過這個虛拟位址完成資料的拷貝,最後再通過kunmap_skb_frag完成映射位址的釋放。        這些都不是重點,主要的是我在網上看到了這樣一些話,便直接把它們拷貝過來吧:      在2.4中是沒有這一函數的,因為2.4的netfilter首先進行碎片包重組,随即進行skb的線性化檢查,對非線性skb包進行線性化,是以合法skb包進入後續hook點操作時實際skb->data_len就都是0了,可以直接操作。      netfilter的碎片重組函數為ip_ct_gather_frags(),在2.4中碎片重組完還進行線性化,而2.6中重組完就直接傳回了,并不進行線性化操作,是以以後在使用的時候必須檢查要處理的資料是否在記憶體頁面中。      由于2.6中的碎片重組操作後不進行skb資料包的線性化,是以資料可能存在于不同的記憶體頁面中,對于不在同一頁面中的情況不能直接進行資料操作,需要将資料拷貝到一個單獨緩沖區後再進行處理。      關于核心為什麼這樣做,我沒找到标志答案,下面是一個網友的回複,也粘貼過來,有機會的話就慢慢了解了:   

非線性化很重要的一點是為了支援網卡晶片的一些功能,這些功能可以大大增加TCP的性能。

   如TSO(tcp segment offload),在sendfile中。 系統隻是增加file對應的page cache 的引用數,接着将一個個頁面放在SKB中發送,一次可以放接近65535 - TCP頭的資料。網卡如E1000,會根據MSS大小切割封包并計算校驗後發送。      

繼續閱讀