sk_buff 剖析 (二)

来源:互联网 发布:lua nginx module安装 编辑:程序博客网 时间:2024/05/29 18:30

5 sk_buff管理和操作函数

5.1缓冲区操作函数

有很多函数,通常都比较短小而且简单,内核用这些函数操作sk_buff的成员变量或者sk_buff链表。首先来看分配和释放缓冲区的函数,然后是一些通过移动指针在缓冲区的头部或尾部预留空间的函数。如果你看过include/linux/skbuff.h和net/core/skbuff.c中的函数,你会发现,基本上每个函数都有两个版本,名字分别是do_something和__do_something。通常第一种函数是一个包装函数,它会在第二种函数的基础上增加合法性检查或者锁。一般来说,类似__do_something的函数不能被直接调用(除非满足特定的条件,比如说锁)。那些违反这条规则而直接引用这些函数的不良代码会最终被更正。

各操作函数缓冲区与移动指针变化如图所示。


操作前与操作后指针变化图: (a)skb_put,(b)skb_push, (c)skb_pull, and (d)skb_reserve

1 unsigned char *skb_put(struct sk_buff *skb, unsigned int len)

在缓冲区的尾部空间扩充len字节数据区l,将tail指针下移,并增加skb的len值。data和tail之间的空间就是可以存放网络报文的空间。这个操作增加了可以存储网络报文的空间,但是增加不能使 tail的值大于end的值,skb的len值大于truesize 的值。

2 unsignedchar *skb_push(struct sk_buff *skb, unsigned int len)

在缓冲区的头部空间扩充len字节的数据区。将data指针上移,并增加skb的len值。这个操作在存储空间的头部增加了一段可以存储网络报文的空间,但是增加不能使data的值小于 head的值,skb的len值大于truesize的值。

3 unsignedchar * skb_pull(struct sk_buff *skb, unsigned int len)

从缓冲区的数据区删除len字节,把腾出的内存归还给头部空间。将data指针下移,并减小skb的len值。这个操作使data指针指向下一层网络报文的头部。

4 voidskb_reserve(struct sk_buff *skb, unsigned int len)

从空白缓冲区中分配len字节的数据区,通过减少尾部空间,增加一个空&sk_buff的首部空间,将data指针和tail指针同时下移。这个操作在存储空间的头部预留len长度的空隙。

例如,由于以太网帧的头部长度是14个八位组,用这个函数把缓冲区的头部指针向后移动了2个字节。这样,紧跟在以太网头部之后的IP头部在缓冲区中存储时就可以在16字节的边界上对齐。如图所示。

(a)skb_reserve开始前, (b) skb_reserve后(c) 复制数据到缓冲区

5.2发送tcp报文示例

发送报文时,在不同协议层处理数据时,该数据要添加相应的协议头。因此,最高层添加数据和自身的协议头。alloc_skb用来申请一个sk_buff。skb_reserve用来创建头空间。skb_put用来创建用户数据空间,用户数据复制到sk->data指向的数据区。接下来使用skb_push是在用户数据的前面加上各层协议头。

tcp报文发送过程

1)当TCP发送数据时,它根据一些条件分配一个缓冲区(比如,TCP的最大分段长度(mss),是否支持散读散写I/O等

2)TCP在缓冲区的头部预留足够的空间(用skb_reserve)用于填充各层的头部(如TCP,IP,链路层 等)。MAX_TCP_HEADER参数是各层头部长度的总和,它考虑了最坏的情况:由于tcp层不知道将要用哪个接口发送包,它为每一层预留了最大的头部长度。它甚至考虑了出现多个IP头的可能性(如果内核编译支持IP over IP,我们就会遇到多个IP头的情况)。

3)把TCP的负载拷贝到缓冲区(用skb_put,复制数据)。需要注意的是:以上只是一个例子。TCP的负载可能会被组织成其他形式。例如它可以存储到分片中。

4)TCP层添加自己的头部(用skb_push)。

5)TCP层把缓冲区传递给IP层,IP层同样添加自己的头部(用skb_push)。

6)IP层把缓冲区传递给邻居层,邻居层添加链路层头部(用skb_push)。


接收报文时当缓冲区在协议栈中向下层传递时,每一层都把skb->data指针向下移动,然后拷贝自己的头部,同时更新skb->len。

5.3 缓冲区分配、克隆和释放函数分析

1 alloc_skb

alloc_skb是net/core/skbuff.c里面定义的,用于分配缓冲区的函数。我们已经知道,数据缓冲区和缓冲区的描述结构(sk_buff结构)是两种不同的实体,这就意味着,在分配一个缓冲区时,需要分配两块内存(一个是缓冲区,一个是缓冲区的描述结构sk_buff)。

alloc_skb函数起始可以看作三部分,第一部分是从cache中分配内存第二部分是初始化分配的skb的相关域第三部分是处理fclone

2 克隆skb_clone

如果一个缓冲区需要被不同的用户独立地操作,而这些用户可能会修改sk_buff中某些变量的值(比如h和nh值),内核没有必要为每个用户复制一份完整的 sk_buff以及相应的缓冲区。相反,为提高性能,内核克隆一个缓冲区。克隆过程只复制sk_buff结构,同时修改缓冲区的引用计数以避免共享的数据被提前释放。克隆缓冲区使用skb_clone函数。

一个使用包克隆的场景是:一个接收包的过程需要把这个包传递给多个接收者,例如包处理函数或者一个或多个网络模块。

被克隆的sk_buff不会放在任何链表中,同时也不会有到socket的引用。原始的和克隆的sk_buff中的skb->cloned值都被置为 1。克隆包的skb->users值被置为1,这样,在释放时,可以先释放sk_buff结构。同时,缓冲区的引用计数(dataref)增加1(因为有多个sk_buff结构指向它)。

3 pskb_copy, skb_copy

当一个skb被clone之后,这个skb的数据区是不能被修改的,这就意为着,我们存取数据不需要任何锁。可是有时我们需要修改数据区,这个时候会有两个选择,一个是我们只修改linear段,也就是head和end之间的段使用,可以使用pskb_copy来复制这部分数据,一种是还要修改切片数据,也就是skb_shared_info,就必须使用skb_copy。

这样就有两个函数供我们选择,第一个是pskb_copy,第二个是skb_copy.

pskb_copy,函数

先alloc一个新的skb,然后调用skb_copy_from_linear_data来复制线性区的数据,并更新相关域,最后复制切片数据的指针。

skb_copy 函数

先alloc一个新的skb,然后复制skb的所有数据段,包括切片数据。

 

4 释放缓冲区 kfree_skb

kfree_skb函数释放缓冲区,并把它返回给缓冲池(缓存)。只有在skb->users为1的情况下才释放内存(没有人引用这个结构)。否则,它只是简单地减小skb->users。如果缓冲区有三个引用者,那么只有第三次调用kfree_skb时才释放内存。

0 0
原创粉丝点击