sk_buff详解

1.sk_buff 
sk_buff是linux内核TCP/IP协议栈最重要的结构,它是网络数据报在内核中的表现形式。sk_buff的定义在$KERN_DIR/incude/linux/skbuff.h中。 
1.1 sk_buff最重要的几个成员: 


1.sk_buff 
sk_buff是linux内核TCP/IP协议栈最重要的结构,它是网络数据报在内核中的表现形式。sk_buff的定义在$KERN_DIR/incude/linux/skbuff.h中。 
1.1 sk_buff最重要的几个成员: 
union { 
struct tcphdr *th; 
struct udphdr *uh; 
struct icmphdr *icmph; 
struct igmphdr *igmph; 
struct iphdr *ipiph; 
struct ipv6hdr *ipv6h; 
unsigned char *raw; 
} h; 

union { 
struct iphdr *iph; 
struct ipv6hdr *ipv6h; 
struct arphdr *arph; 
unsigned char *raw; 
} nh; 

union { 
unsigned char *raw; 
} mac; 
这几个成员定义了各个层协议的头部,h是传输层头部,他是tcp/udp/icmp/igmp/ipip(ip over ip)/ipv6几个协议头部中的一种。nh是网络层协议的头部,他是ip/ipv6/arp几个协议头部中的一个。需要指出的是,Linux 2.6内核对mac层头部做了变动,在以前的sk_buff定义中,mac层头部中还有一个struct ethhdr *ethernet成员。目前还不清楚为什么要做这样的改变(兼容不同的标准?)。 
unsigned int len, 
data_len, 
mac_len, 
csum; 
这几个成员定义了几个长度,len指的是不同层的数据报长度,包括头部和数据。在不同的层,len的值是不同的。也就是说,如果处在网络层,len指的是ip包的长度,如果包已经到了应用层,则len是应用层头部和数据载荷的长度。 
data_len用在当数据包被分片存储时(不同于ip分片,只是在内存中用不连续的空间存储数据包)指定frags和frag_list所指空间的大小,一般为0。由于mac头部取消了ethernet成员,因此增加了mac_len来描述mac头部的长度。csum是以太网校验和。 
unsigned int truesize; 
atomic_t users; 
unsigned char *head, 
*data, 
*tail, 
*end; 
truesize指报文的存储区长度,为16字节对齐。users为控制结构(sk_buff结构本身占据的内存)的引用技术。head指针指向存储空间的起始地址,end指针指向存储空间的结束地,data指针指向网络报文的起始地址,tail指针指向网络报文的结束地址,也就是说end-head=存储空间大小,tail-data=网络报文大小。网络报文不一定要填满整个存储空间,它可以在head和data之间,tail和end之间留下一些空间,这些空间可以做优化用,也可以为继续扩展网络报文提供空间。网络报文在存储空间里的存放的顺序依次是:链路层的头部,网络层的头部,传输层的头部,传输层的数据。


1.2 sk_buf的memory layout 
________
| head     |--------->_______________ 
|-----------|             |                              | 
| data      |--------->|______________| 
|-----------|              |                             | 
| tail        |              |          数据报         | 
|-----------|---------->|______________| 
| end       |            |                             | 
--------------------->|____ __________| 
sk_buff 
在alloc_skb时,开辟两个内存区间,第一个存放sk_buff结构本身,第二个存放数据网络数据报。使用alloc_skb函数分配一个skb时,初始data,tail均和head一起,指向存储空间的开始出。如果使用dev_alloc_skb,则自动会在head和data之间保留一点空间,做优化用。 
2. sk_buff相关的系列函数 
1). struct sk_buff *alloc_skb(unsigned int size, int gfp_mask) 
前面已经介绍过了,分配一个sk_buff。size指网络数据报的存储空间大小,sk_buff结构本身需要的内存空间不需要指定。因此在谈论sk_buff大小时,指的均是网络数据报的存储空间大小。最终分配出来的存储空间大小可能比size要大,因为在alloc_skb内部还要对size进行字节对齐处理size = SKB_DATA_ALIGN(size)。gfp_mask为内存分配优先级,常见的有GFP_ATOMIC和GFP_KERNEL. 
2).struct sk_buff *dev_alloc_skb(unsigned int length) 
内部以GFP_ATOMIC调用alloc_skb并在head和data之间保留16个字节(headroom) 
3).struct sk_buff *skb_clone(struct sk_buff *skb, int gfp_mask) 
克隆一个skb新的控制结构,克隆出来的skb中的cloned标记被置位并且增加网络报文的引用计数。 
atomic_inc(&(skb_shinfo(skb)->dataref)); 
4). struct sk_buff *skb_copy(struct sk_buff *skb, int gfp_mask) 
复制一个skb,包括控制结构和存储区域。复制成功后,新skb和原来的skb相对独立,cloned为0,并且引用计数为1。因此,我们可以修改存储区域中的数据报。 
5). void kfree_skb(struct sk_buff *skb) 
释放一个skb,当引用计数为1时,同时释放控制结构和存储区域。否则,只释放控制结构。 
6). unsigned char *skb_put(struct sk_buff *skb, unsigned int len) 
同时增加len和tail。用于向数据报尾部追加数据。返回原来tail所在位置。例如:对于一个新分配的skb,要增加mac,ip,tcp三个头部,就可以使用skb_put函数。 
memcpy(skb_put(skb, ETH_HLEN),ethhd, ETH_HLEN); 
memcpy(skb_put(skb, sizeof(struct iphdr), iphd, sizeof(struct iphdr)); 
memcpy(skb_put(skb, sizeof(struct tcphdr), tcph, sizeof(struct tcphdr)); 
7).unsigned char *skb_push(struct sk_buff *skb, unsigned int len) 
将data指针上移(即往head方向移动)并增加len长度。这个函数用来向头部添加一些数据。当然前提是有足够的headroom。 
8). unsigned char * skb_pull(struct sk_buff *skb, unsigned int len) 
将data指针下移(往tail方向移动),并减小len的值。这个函数一般用来除去某个头部,将data指针指向上一层头部,或者数据。 
9). void skb_reserve(struct sk_buff *skb, unsigned int len) 
将data指针和tail指针同时下移。这个操作在存储空间的头部预留len长度的空隙(headroom)。 
10). void skb_trim(struct sk_buff *skb, unsigned int len) 
将网络报文的长度缩减到len。这个操作丢弃了网络报文尾部的填充值。 
11). int skb_cloned(struct sk_buff *skb) 
判断skb是否是一个clone的控制结构。如果是clone的,它的cloned标记是1,而且它指向的存储空间的引用计数大于1。 

3.总结 
sk_buff是linux网络协议栈中最重要的数据结构,理解它对于理解整个网络协议栈非常有帮助。需要注意的是,一个网络数据包存储于一片存储区域中,而该片存储区域可能同时被许多sk_buff控制结构所共享。该共享信息存储与end指针之后,可以通过(&(skb_shinfo(skb)->dataref来查看其引用状态。只有当引用为1的时候,我们才能修改数据区域。否则将造成不可预料的后果,包括内核崩溃。

你可能感兴趣的:(sk_buff详解)