sk_buff 源文件:linux-2.6.37/ include/ linux/ skbuff.h
linux-2.6.37/ include/ linux/ skbuff.c
sk_buff数据结构:
/* struct sk_buff - socket buffer */
struct sk_buff {
/* These two members must be first */
struct sk_buff *next ; /* Next buffer in list */
struct sk_buff *prev ; /* Previous buffer in list */
ktime_t tstamp ; /* Time we arrived,记录接收或发送报文的时间戳*/
struct sock *sk ; /* Socket we are owned by */
/* Device we arrived on / are leaving by
* 通过该设备接收或发送,记录网络接口的信息和完成操作
*/
struct net_device *dev ;
/* This is the control buffer. It is free to use for every
* layer. Please put your private variables there.
*/
char cb[48] __aligned (8) ;
...
/* data_len为分页数据所包含的全部报文长度
* len为某时刻的报文总长度
* 那么,线性数据的长度为:skb->len - skb->data_len
*/
unsigned int len , data_len ;
/* 保存了下一个协议层的信息,在处理报文时由当前协议层设置 */
__be16 protocol ;
...
/* head指向线性数据区的开始
* data指向驻留线性数据区中数据的起始位置
*/
unsigned char *head , *data ;
...
/* 协议头表示 */
sk_buff_data_t transport_header ; /* 传输层协议头 */
sk_buff_data_t network_header ; /* 网络层协议头 */
sk_buff_data_t mac_header ; /* 链路层协议头 */
sk_buff_data_t tail ; /* 指向驻留在线性数据区的最后一字节数据*/
sk_buff_data_end ; /* 指向线性数据区的结尾,确保不超出可用存储缓冲区 */
atomic_t users ; /* 引用该sk_buff的数量*/
/* 该缓冲区所分配的总内存,包括sk_buff结构大小 + 数据块大小 (应该不包括分页大小?)*/
unsigned int truesize ;
}
/* This data is invariant across clones and lives at
* the end of the header data, ie. at skb->end.
*/
struct skb_shared_info {
/* number of fragments belonged to this sk_buff
* 此sk_buff分页段的数目,它表示frags[]数组的元素数量,该数组包含sk_buff的分页数据
*/
unsigned short nr_frags;
...
/* 指向其分段列表,此sk_buff的总长度为frag_list链表中每个分段长度(skb->len)的和,
* 再加上原始的sk_buff的长度
* 通过此域可进行报文分段!!
*/
struct sk_buff *frag_list ;
/*
* Warning : all fields before dataref are cleared in __alloc_skb()
* 此sk_buff被引用的次数
*/
atomic_t dataref ;
/*
* must be last field
* 分段的数组,包含sk_buff的分页数据
*/
skb_frag_t frags[MAX_SKB_FRAGS] ;
}
/* To allow 64K frame to be packed as single skb without frag_list
* 允许小于64K的数据不用分段,即不适用frag_list
*/
#define MAX_SKB_FRAGS (65536 / PAGE_SIZE + 2 )
typedef struct skb_frag_struct skb_frag_t ;
struct skb_frag_struct {
struct page *page ; /* 该页的虚拟地可用page_address()得到*/
#if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)
__u32 page_offset ;
__u32 size;
#else
__u16 page_offset ;
__u16 size ;
#endif
};
buff 结构体非常重要,它的含义为“套接字缓冲区”,用于在Linux 网络子系统中的各层之间传递数据,是Linux网络子系统数据传递的“中枢神经”。当发送数据包时,Linux 内核的网络处理模块必须建立一个包含要传输的数据包的sk_buff,然后将sk_buff 递交给下层,各层在sk_buff 中添加不同的协议头直至交给网络设备发送。同样地,当网络设备从网络媒介上接收到数据包后,它必须将接收到的数据转换为sk_buff 数据结构并传递给上层,各层剥去相应的协议头直至交给用户。
1.套接字缓冲区成员
参看 linux/skbuff.h中的源代码,sk_buff结构体包含的主要成员如下
(1)各层协议头h、nh和mac。
sk_buff结构体中定义了3个协议头以对应于网络协议的不同层次,这3 个协议头为传输层TCP/UDP(及ICMP和IGMP)协议头h、网络层协议头nh和链路层协议头mac。这3个协议头数据结构都被定义为联合体。
union{
struct tcphdr *th; /* TCP头部*/
struct udphdr *uh; /* UDP头部*/
struct icmphdr *icmph; /* ICMP头部*/
struct igmphdr *igmph; /* IGMP头部*/
struct iphdr *ipiph; /* IP头部*/
struct ipv6hdr *ipv6h; /* IPv6头部*/
unsigned char *raw; /* 数据链路层头部*/
}h;
union{
struct iphdr *iph; /* IP头部*/
struct ipv6hdr *ipv6h; /* IPv6头部*/
struct arphdr *arph; /*ARP头部*/
unsigned char *raw; /* 数据链路层头部*/
}nh;
union{
unsigned char *raw; /* 数据链路层头部*/
} mac;
(2)数据缓冲区指针head、data、tail和end。
Linux 内核必须分配用于容纳数据包的缓冲区,sk_buff结构体定义了4 个指向这片缓冲区不同位置的指针head、data、tail和end。head 指针指向内存中已分配的用于承载网络数据的缓冲区的起始地址,sk_buff和相关数据块在分配之后,该指针的值就被固定了。data 指针则指向对应当前协议层有效数据的起始地址。每个协议层的有效数据含义并不相同,各层的有效数据信息包含的内容如下。
l 对于传输层而言,用户数据和传输层协议头属于有效数据。
l 对于网络层而言,用户数据、传输层协议头和网络层协议头是其有效数据。
l 对于数据链路层而言,用户数据、传输层协议头、网络层协议头和链路层头部都属于有效数据。
因此,data 指针的值需随着当前拥有sk_buff的协议层的变化进行相应的移动。tail指针则指向对应当前协议层有效数据负载的结尾地址,与data指针对应。end 指针指向内存中分配的数据缓冲区的结尾,与head 指针对应。和head 指针一样,sk_buff被分配之后,end指针的值也就固定不变了。很显然,有效数据必须位于分配的数据缓冲区内,即(data,tail)区间位于(head,end)区间内。因此,head、data、tail和end
这4 个指针间存在如下关系:
head <- data <- tail <- end。
2.套接字缓冲区操作
下面我们来分析套接字缓冲区涉及到的操作函数,Linux 套接字缓冲区支持分配、
释放、指针移动等功能函数。
(1)分配。
Linux 内核用于分配套接字缓冲区的函数有:
struct sk_buff *alloc_skb(unsigned int len,int priority);
struct sk_buff *dev_alloc_skb(unsigned int len);
alloc_skb()函数分配一个套接字缓冲区和一个数据缓冲区,参数len为数据缓冲区的空间大小,以16 字节对齐,参数priority为内存分配的优先级。
dev_alloc_skb()函数只是以GFP_ATOMIC优先级(代表分配过程不能被中断)调用上面的alloc_skb()函数,并保存skb->head和skb->data 之间的16个字节。分配成功之后,因为还没有存放具体的网络数据包,所以sk_buff的data、tail指针都指向存储空间的起始地址head,而len的大小则为0。
(2)释放。
Linux 内核用于释放套接字缓冲区的函数有:
void kfree_skb(struct sk_buff *skb);
void dev_kfree_skb(struct sk_buff *skb);
void dev_kfree_skb_irq(struct sk_buff *skb);
void dev_kfree_skb_any(struct sk_buff *skb);
上述函数用于释放被alloc_skb()函数分配的套接字缓冲区和数据缓冲区。Linux 内核内部使用kree_skb()函数,而网络设备驱动程序中则必须用dev_kfree_skb()、dev_kfree_skb_irq()或dev_kfree_skb_any()函数进行套接字缓冲区的释放。其中,dev_kfree_skb()函数用于非中断上下文,dev_kfree_skb_irq()函数用于中断上下文,而dev_kfree_skb_any()函数则在中断和非中断上下文中皆可采用。
(3)指针移动。
Linux 套接字缓冲区中的数据缓冲区指针移动操作包括put(放置)、push(推)、pull(拉)、reserve(保留)等。
① put 操作
数据缓冲区指针put 操作以下列函数完成:
unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
unsigned char *_ _skb_put(struct sk_buff *skb, unsigned int len);
上述函数将tail 指针下移,增加sk_buff 的len 值,并返回skb->tail 的当前值。skb_put()和__ skb_put()的区别在于前者会检测放入缓冲区的数据,而后者不会检查。这两个函数主要用于在缓冲区尾部添加数据。
② push操作
数据缓冲区指针put 操作以下列函数完成:
unsigned char *skb_push(struct sk_buff *skb, unsigned int len);
unsigned char *_ _skb_push(struct sk_buff *skb, unsigned int len);
与skb_put()和_ _skb_put()不同,skb_push()和_ _skb_push()会将data 指针上移,因此也要增加sk_buff 的len 值。push 操作在存储空间的头部增加一段可以存储网络数据包的空间,而put 操作则在存储空间的尾部增加一段可以存储网络数据包的空间,因此主要用于在数据包发送时添加头部skb_push()与_ _skb_push()的区别和skb_put()和_ _skb_put()的区别类似。③ pull操作
数据缓冲区指针pull操作以下列函数完成:
unsigned char * skb_pull(struct sk_buff *skb, unsigned int len);
skb_pull()函数将data 指针下移,并减小skb 的len值。这个操作一般用于下层协议向上层协议移交数据包,使data 指针指向上一层协议的协议头。④ reserve操作
数据缓冲区指针reserve操作以下列函数完成:
void skb_reserve(struct sk_buff *skb, unsigned int len);
skb_reserve()函数将data 指针和tail指针同时下移,这个操作主要用于在存储空间的头部预留len长度的空隙。