sk_buff是内核网络子系统的一个支柱结构,各层协议都依赖于sk_buff而存在,所以了解它的设计是很重要的,这其中当然也包括了很重要的网络缓冲区的管理机制,下面一个系列都围绕它来展开。

sk_buff作为一个buffer,最重要的当然还是数据,所以sk_buff对数据的组织是学习重点。另外,为了满足上层的需求,sk_buff还有一些其它必要的功能。整理一下,可划分为以下几个方面(当然字段也可以这样划分):
  * sk_buff的组织形式
  * sk_buff对header的处理
  * sk_buff对payload的处理
  * sk_buff的clone
  * sk_buff的内存限额机制
  当然,由于本人才疏学浅,还有其它 一些 尚未接触到的地方,以后再做补充吧。
这里遵循内核的习惯叫法:struct sk_buff<=>skb, struct sock<=>sk,struct skb_share_info<=>shinfo
1.sk_buff的组织形式
首先来看一张图:
 

(图1)
从图中可以清除的看到sk_buff是以一个链表存在的,每一个sock都维护了一个发送队列,接收队列。相比来说这些队列对stream类型的协议更重要,因为对于stream,发送队列中的数据只有在确保对方受到以后才能删除,而对于dgram类型的协议,在使用网络层协议将其发送以后即可从队列中删掉。
这些队列所隐含的一个东西就是内存管理机制,因为队列成员不能不停的添加,当内存使用达到一个限额后就应该谨慎分配sk_buff甚至是拒绝分配。这个sk_buff的这个内存限额机制会在下一篇文章中介绍。
 
2.sk_buff对header的处理
sk_buff结构体中不包含任何的数据(header和payload的集合),数据则放在一片独立的内存中。header和payload都放在这片内存中,所以组织的当然要严谨一些,也就相对复杂一些。对于这个结构,当然以一幅图来描述更好:(下图着重于header部分)
 

(图2)
上图有一个需要注意的地方,指针data是浮动的,图中的假设是skb刚进入物理层,所以指向了网络层的首部。
从图中可以对sk_buff的数据区一目了然,下面对各个成员具体介绍一下:
truesize: skb"真正"占用空间的大小(实际上还不是真正的,因为不包括clone的skb所占空间)
nohdr: 标志位,表示该skb中是否包含header,若为1则和header相关的几个成员均无效。
data_len:  payload的长度
len: 目前的header+payload,len是变化的,每一层在添加各自的包头时都会将其长度加到len。
注意图中的len只显示了包头部分,没有包括payload。
即:len=(tail-data)+payload_length。
hdr_len: header缓冲区的总长度,即:hdr_len=tail-head。
head: header缓冲区的头指针。
data: 目前处理的header的头指针,data介于head和tail之间。
tail: 有效header的尾指针。
end: header缓冲区的尾指针,同时也是shinfo的指针。
transport_header:transport header的指针
network_header:network header的指针
mac_header: mac header的指针
 
看到这可能会有这样一个疑问,为什么tail和end要分成两个指针?的确,tail就已经能够指向header的尾端了,看似end有些多余。但考虑一下在end之后还有一个重要的结构:shinfo,如果由tail来身兼两职的话,那么一旦transport header长度发生变化,tail指向出错,shinfo当然也就找不到了。而实际上这确实是可能发生的,因为TCP协议头最后的option就是可变长的。
至于header缓冲区的空间是怎样确定的,在任何一个skb alloc接口都要指定该缓冲区的大小,创建者一般会依据所有协议头的最大值的和来设置它。而这个数值并不是最终的hdr_len,因为在确定header缓冲区的大小之前还要对其进行一个cache line的对齐操作,以在多核情况下提高寻址效率。
 
3.sk_buff对payload的处理
对payload的操作应该做到以下几点:
  * 空间分配的弹性强,因为它的大小是很不确定的
  * 访存要尽量的快
  * 复制操作要尽量少
所以,内核里采用了直接分配page来拷贝payload。但使用page来做为缓冲区的一个缺点就是会增加一些程序的复杂度,sk_buf就使用了skb_shared_info来组织payload,此外,sock中还有两个成员用来记录没有用完的page,毕竟作为系统里最珍贵的资源,使用还是要谨慎的。
另外,之所以名字叫做shared info,也说明了其上有一些共享数据。因为一个数据缓冲区(包括header和payload)可能会被多个skb共享,就需要一个地方能存储这些公共的信息。这里共享的情形有两种:在clone的两个skb间共享;在不同的两个skb间共享。
还是通过图更能说明问题:
 

(图3)
图中只注明了目前读到的几个字段,其它的目前还没有接触到,等接触到了再回来补充吧。
dataref比较难搞,因为它一变量两用,而且意思不同。它首先被分成了两部分,各16位:低16位是对整个数据缓冲区的引用计数,高16位是对header没有引用的计数。这个概念很别扭,但它确实通过了一个字段完成了对三个实体的引用计数...因为这个计数从目前看来还是主要用在clone的情况下,具体还是在那一节中介绍吧。
至于对page的组织,图中已经很明显了。nr_frags用来指示现在的frags中有几个是有效的。每个frags都代表了一个page,通过page和page_offset,size的组合就可以唯一确定一段内存。需要指出frag是有数量限制的,最多只能有MAX_SKB_FRAGS个,这个值要满足 (MAX_SKB_FRAGS-2)*PAGE_SIZE = 64K,也就是说一个skb理论上可以支持传输最多64K的payload,但考虑到frag中可能存在和别的skb共享的page,这个值一般情况下是无法达到的。
 
最近一直没有时间写,学习和写作的进度减慢了很多,以后要多努力了。