关于skb_header_pointer函数

摘自:https://blog.csdn.net/adamska0104/article/details/41245579      最近一段时间看内核代码,总是看到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()函数进行拷贝,下面再来看一下这个函数的实现过程,并不复杂。      /* Copy some data bits from skb to kernel buffer. */ 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;         /* Copy header. */
      /*拷贝在本页中的部分*/
      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;
     }         /*拷贝本skb中其他碎片的部分*/
      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;
     }        /*拷贝其他碎片skb中的数据部分,对于skb的递归调用*/
     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大小切割报文并计算校验后发送。
 
上一篇:网络协议栈(5)sendto/send返回成功意味着什么


下一篇:[转载]Linux 网络驱动详解