在skb_buff数据结果后紧跟了另一个数据结构struct skb_shared_info。在为socket Buffer分配内存时,也会同时为skb_shared_info分配相应大小的内存空间,并顺序地初始化改数据结构,struct skb_shared_info用于支持IP数据分片和TCP数据分段。
1 skb_shared_info结构体
当一个数据包的长度大于网络适配器硬件所能传送的最大传送单元MTU时,需要对数据包进行分割,将其分成更小的数据片,这些数据片存放在由struct skb_shared_info数据结果管理的Socket Buffer组成的单向链表中。
struct skb_shared_info {
unsigned char nr_frags;
__u8 tx_flags;
unsigned short gso_size;
/* Warning: this field is not always filled in (UFO)! */
unsigned short gso_segs;
unsigned short gso_type;
struct sk_buff *frag_list;
struct skb_shared_hwtstamps hwtstamps;
u32 tskey;
__be32 ip6_frag_id;
/*
* Warning : all fields before dataref are cleared in __alloc_skb()
*/
atomic_t dataref;
/* Intermediate layers must ensure that destructor_arg
* remains valid until skb destructor */
void * destructor_arg;
/* must be last field, see pskb_expand_head() */
skb_frag_t frags[MAX_SKB_FRAGS];
};
各数据域的含义如下:
dataref:描述对主要数据包缓冲区的引用计数,skb_buff克隆时,该计数加1.
nr_frags:数据包被分割的数据片的计数,描述了一个数据包最终被分成了多少个数据片,这个域是支持IP分片使用的。
frag_list:数据包被成片段,该域是指向存放分片数据链表其实地址的指针。
frags:一个页表入口数组,每一个入口就是一个TCP的段。
gso_size gso_type:这个两个域用于说明网络设备是否具有在硬件上实现对TCP分段的能力,是网络设备的功能特点。
gso_size:TCP数据包被分段的数量。
有的网卡在硬件上可以完成对TCP层数据的分段(segment),这种功能原来是由CPU来完成的,通过对大的数据包(如64k)的分割任务交给网络适配器硬件来完成,可以减轻CPU的负载。从而提高网络的熟读。这种技术称为TSO(TCP segmentation Offload)或GSO(Generic Segmentation Offload)。gso_size gso_type和gso_segs就是用于这类计数的。GSO可以用于各种协议。
2 设计skb_shared_info数据结构的目的
struct skb_shared_info数据结构紧接在数据包缓冲区之后,由sk_buff的end指针来寻址。使用struct skb_shared_info数据结构有几个目的:
- 支持IP数据分片
- 支持TCP数据分段
- 跟踪数据包的引用计数
当用于IP数据分片是,struct skb_shared_info数据结构中有指针指向包含IP数据片的Socket Buffer的链表。当用于TCP数据分段时,该结构中包含了一个相关的页面数据。其中存放了分段的数据。
frags数据中每个元素都时一个管理存放TCP数据段的skb_frag_t的结果,frags数组的大小总和时64KB。
struct skb_frag_struct {
struct {
struct page *p; //指向存放TCP数据段存放的页面
} page;
#if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)
__u32 page_offset; //TCP数据段在页面中相对页面的偏移量
__u32 size; //段的大小
#else
__u16 page_offset;
__u16 size;
#endif
};
3 skb_shared_info操作函数
- skb_is_nonlinear:查看Socket Buffer的数据包是否被分了片段
static inline bool skb_is_nonlinear(const struct sk_buff *skb)
{
return skb->data_len;
}
该函数实际返回skb->data_len,skb->data_len就是skb的非线性数据长度。如果skb->data_len大于0说明有分片段数据。
- skb_linearize:将分了片段的小的数据包组装成一个完整的大的数据包。
- skb_shinfo:在sk_buff结构中,并没有指针指向struct skb_shared_info数据结构,要访问该数据结构,需要用到宏,skb_shinfo宏返回sk_buffer的end指针。
#define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))
static inline unsigned char *skb_end_pointer(const struct sk_buff *skb)
{
return skb->end;
}