Chinaunix首页 | 论坛 | 博客
  • 博客访问: 303928
  • 博文数量: 42
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 847
  • 用 户 组: 普通用户
  • 注册时间: 2013-02-28 14:14
个人简介

北冥有鱼,其名曰鲲,鲲之大,不知其几千里也;化而为鸟,其名为鹏,鹏之背,不知其几千里也,怒而飞,其翼若垂天之云。

文章分类

全部博文(42)

文章存档

2015年(6)

2014年(7)

2013年(29)

分类: LINUX

2013-06-27 19:23:37

原文地址:关于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()函数进行拷贝,下面再来看一下这个函数的实现过程,并不复杂。
 
   /* 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大小切割报文并计算校验后发送。


阅读(2190) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~