关于skb_header_pointer函数

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: [email protected]
来源:http://yfydz.cublog.cn

1. 前言

在2.6.17内核中获取sk_buff中各协议头的参数不再是直接读取skb包中定义的各协议头部分指针,而是使用skb_header_pointer()函数来获取,为什么要进行这种变化值得玩味。

以下内核代码版本为2.6.17.11。

2. skb_header_pointer函数

该函数本身很简单,定义如下:
/* include/linux/skbuff.h */
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()定义为:

/* include/linux/skbuff.h */
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->len - skb->data_len表示在当前页的数据大小。

如果skb->data_len不为0,表示该IP包的数据分属不同的页,该数据包也就被成为非线性化的,函数 skb_is_nonlinear()就是通过该参数判断,一般刚进行完碎片重组的skb包就属于此类。更详细的关于sk_buff的那几个数据指针的意思可参考:
http://vger.kernel.org/~davem/skb_data.html

这样skb_header_pointer()函数就好理解了,先判断要处理的数据是否都在当前页面内,如果是,则返回可以直接对数据处理,返回所求数据指针,否则用skb_copy_bits()函数进行拷贝,这个函数也是个比较复杂的处理函数:

/* net/core/skbuff.c */

/* 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;
  memcpy(to, skb->data + offset, copy);
  if ((len -= copy) == 0)
   return 0;
  offset += copy;
  to     += copy;
 }
// 拷贝本skb中其他碎片中的部分
 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  int end;
  BUG_TRAP(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中的数据部分
 if (skb_shinfo(skb)->frag_list) {
  struct sk_buff *list = skb_shinfo(skb)->frag_list;
  for (; list; list = list->next) {
   int end;
   BUG_TRAP(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;
}
3. 与2.4的区别

在2.4中是没有这一函数的,因为2.4的netfilter首先进行碎片包重组,随即进行skb的线性化检查,对非线性skb包进行线性化,因此合法skb包进入后续hook点操作时实际skb->data_len就都是0了,可以直接操作。
 
netfilter的碎片重组函数为ip_ct_gather_frags(),在2.4中碎片重组完还进行线性化,而2.6.17中重组完就直接返回了,并不进行线性化操作,因此以后在使用的时候必须检查要处理的数据是否在内存页面中。

4. 结论

由于2.6.17中的碎片重组操作后不进行skb数据包的线性化,因此数据可能存在于不同的内存页面中,对于不在同一页面中的情况不能直接进行数据操作,需要将数据拷贝到一个单独缓冲区后再进行处理。
不知道为什么要这么作,是为提高效率?是否因为现在网络上碎片包已经很少了,大部分情况下都是完整包,但提前线性化检查一下会降低多少效率呢?是为以后发出包的时候不需要再重新分片?

发表于: 2006-08-31,修改于: 2006-09-14 08:59,已浏览3391次,有评论2条 推荐 投诉
	网友: ydwoo0722 	时间:2006-09-13 21:13:51 IP地址:221.219.29.★
	

非线性化很重要的一点是为了支持网卡芯片的一些功能,这些功能可以大大增加TCP的性能。



如TSO(tcp segment offload),在sendfile中。 系统只是增加file对应的page cache 的引用数,接着将一个个页面放在SKB中发送,一次可以放接近65535 - TCP头的数据。网卡如E1000,会根据MSS大小切割报文并计算校验后发送。


	网友: yfydz 	时间:2006-09-14 08:59:55 IP地址:218.247.216.★
	

多谢!



你可能感兴趣的:(.net,linux,cache,网络协议)