http://blog.csdn.net/npy_lp/article/details/7174124
struct sk_buff是Linux操作系统网络相关代码中最重要的结构体之一,用于管理已接收或正要传输的网络数据包。此结构体定义在include/linux/skbuff.h头文件中。
1、结构体成员
- struct sk_buff *next;
- struct sk_buff *prev;
内核通过一个双向链表来维护所有的sk_buff结构体,所以每个sk_buff结构体都使用next和prev这两个成员来实现与这个双向链表的联系。
- ktime_t tstamp;
时间戳记,常用于表示数据包何时被接收。
- struct sock *sk;
当数据在本地产生或者正由本地进程接收时,TCP或UDP以及用户程序就会使用sk这个指针。
- struct net_device *dev;
当接收数据包时,驱动程序使用接收设备的结构体变量更新此成员;当发送一个数据包时,此成员代表的是发送数据包的设备。
- char cb[48] __aligned(8);
控制缓冲区(controlbuffer),每一层都可以使用它来存储私有数据。
- unsigned long _skb_refdst;
dst引用计数。
- #ifdef CONFIG_XFRM
- struct sec_path *sp;
- #endif
由IPsec协议组使用,以记录转换信息。
- unsigned int len,
- data_len;
- __u16 mac_len,
- hdr_len;
len是缓冲区以及一些片段的总长度,data_len只是一些片段的长度,mac_len是MAC报头的长度,hdr_len是克隆skb时可写报头的长度。
- union {
- __wsum csum;
- struct {
- __u16 csum_start;
- __u16 csum_offset;
- };
- };
校验和以及相关的状态标识。
- __u32 priority;
此成员表示正被传输或转发的数据包QoS等级。
- kmemcheck_bitfield_begin(flags1);
- __u8 local_df:1,
- cloned:1,
- ip_summed:2,
- nohdr:1,
- nfctinfo:3;
- __u8 pkt_type:3,
- fclone:2,
- ipvs_property:1,
- peeked:1,
- nf_trace:1;
- kmemcheck_bitfield_end(flags1);
- kmemcheck_bitfield_begin(flags2);
- __u16 queue_mapping:16;
- #ifdef CONFIG_IPV6_NDISC_NODETYPE
- __u8 ndisc_nodetype:2,
- deliver_no_wcard:1;
- #else
- __u8 deliver_no_wcard:1;
- #endif
- __u8 ooo_okay:1;
- kmemcheck_bitfield_end(flags2);
各种标识(至于这些标识的用法,在以后用到时再加以说明)。
kmemcheck_bitfield_begin和kmemcheck_bitfield_end分别用于定义零长数组,以确定这些成员的起始和终止的位置。 它们的源代码如下所示:
- /* linux-2.6.38.8/include/linux/kmemcheck.h */
- #define kmemcheck_bitfield_begin(name) \
- int name##_begin[0];
- #define kmemcheck_bitfield_end(name) \
- int name##_end[0];
使用零长数组实现此功能的类似代码如下:
- #include <stdio.h>
- struct test {
- int len1;
- char ch_start[0];
- char ch1;
- char ch2;
- char ch_end[0];
- int len2;
- };
- int main(void)
- {
- char *p;
- int i;
- struct test test_value = {
- .ch1 = 'a',
- .len1 = 1,
- .len2 = 2,
- .ch2 = 'b',
- };
- for (p = test_value.ch_start, i = 0;
- p < test_value.ch_end; p++, i++)
- printf("*(p + %d) = %c\n", i, *p);
- return 0;
- }
例子输出结果:
- *(p + 0) = a
- *(p + 1) = b
- __be16 protocol;
由于每种协议都有自己处理接收数据包的处理函数,因此,驱动程序使用此成员来通知其上层该使用哪个处理函数。
- void (*destructor)(struct sk_buff *skb);
当此缓冲区被删除时,用它来完成某些工作。
- #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
- struct nf_conntrack *nfct;
- #endif
- #ifdef NET_SKBUFF_NF_DEFRAG_NEEDED
- struct sk_buff *nfct_reasm;
- #endif
- #ifdef CONFIG_BRIDGE_NETFILTER
- struct nf_bridge_info *nf_bridge;
- #endif
这些成员由netfilter相关代码所使用。
- int skb_iif;
目的地网络设备的ifindex。
- #ifdef CONFIG_NET_SCHED
- __u16 tc_index; /* traffic control index */
- #ifdef CONFIG_NET_CLS_ACT
- __u16 tc_verd; /* traffic control verdict */
- #endif
- #endif
这些成员由流量控制功能所使用。
- __u32 rxhash;
所接收的数据包的哈希表。
- #ifdef CONFIG_NET_DMA
- dma_cookie_t dma_cookie;
- #endif
a cookie to one of several possible DMAoperations done by skb DMA functions.
- #ifdef CONFIG_NETWORK_SECMARK
- __u32 secmark;
- #endif
数据包安全等级标记。
- union {
- __u32 mark;
- __u32 dropcount;
- };
通用数据包标记。
- __u16 vlan_tci;
VLAN控制信息。
- sk_buff_data_t transport_header;
- sk_buff_data_t network_header;
- sk_buff_data_t mac_header;
指向TCP/IP协议栈各协议报头的指针:transport_header指向传输层,network_header指向网络层,mac_header指向链路层。
- sk_buff_data_t tail;
- sk_buff_data_t end;
- unsigned char *head,
- *data;
这些成员代表缓冲区的边界以及其中的数据,head和end指向已分配缓冲区空间的开端和尾端,而data和tail则指向实际数据的开端和尾端,如下图(图片来自《Understanding Linux Network Internals》):
其中,headroom可插入一个协议报头,tailroom可填入其他新的数据。
- unsigned int truesize;
此成员代表缓冲区总的大小,包括sk_buff结构本身。
- atomic_t users;
引用计数,避免正在使用时被其它用户所释放掉。
http://blog.csdn.net/npy_lp/article/details/72639022、结构体相关操作函数
(1)、dev_alloc_skb
实际上,函数dev_alloc_skb最终是调用__alloc_skb函数来分配数据缓冲区和sk_buff结构体的,如下图:
从dev_alloc_skb到__alloc_skb所涉及的源代码如下:
- /* linux-2.6.38.8/net/core/skbuff.c */
- struct sk_buff *dev_alloc_skb(unsigned int length)
- {
- /*
- * There is more code here than it seems:
- * __dev_alloc_skb is an inline
- */
- return __dev_alloc_skb(length, GFP_ATOMIC);
- }
- /* linux-2.6.38.8/include/linux/skbuff.h */
- static inline struct sk_buff *__dev_alloc_skb(unsigned int length,
- gfp_t gfp_mask)
- {
- struct sk_buff *skb = alloc_skb(length + NET_SKB_PAD, gfp_mask);
- if (likely(skb))
- skb_reserve(skb, NET_SKB_PAD);
- return skb;
- }
- /* linux-2.6.38.8/include/linux/skbuff.h */
- static inline struct sk_buff *alloc_skb(unsigned int size,
- gfp_t priority)
- {
- return __alloc_skb(size, priority, 0, NUMA_NO_NODE);
- }
其中,NET_SKB_PAD的值在ARM体系架构上为32。
接下来,在__alloc_skb函数中,首先通过kmem_cache_alloc_node函数(在未配置CONFIG_NUMA和CONFIG_SLOB的情况下,它的实现就是直接调用kmem_cache_alloc函数)从skbuff_head_cache高速缓存中申请一个sk_buff结构体对象。创建skbuff_head_cache高速缓存的源代码如下:
- /* linux-2.6.38.8/net/socket.c */
- static int __init sock_init(void)
- {
- ...
- /* Initialize skbuff SLAB cache */
- skb_init();
- ...
- }
- core_initcall(sock_init); /* early initcall */
- /* linux-2.6.38.8/net/core/skbuff.c */
- void __init skb_init(void)
- {
- skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
- sizeof(struct sk_buff),
- 0,
- SLAB_HWCACHE_ALIGN|SLAB_PANIC,
- NULL);
- skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",
- (2*sizeof(struct sk_buff)) +
- sizeof(atomic_t),
- 0,
- SLAB_HWCACHE_ALIGN|SLAB_PANIC,
- NULL);
- }
申请sk_buff结构体对象的代码如下:
- /* linux-2.6.38.8/net/core/skbuff.c */
- skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
- if (!skb)
- goto out;
- prefetchw(skb);
对于S3C2410,prefetchw函数的实现是使用GCC的内置函数__builtin_prefetch,定义如下:
- /* linux-2.6.38.8/include/linux/prefetch.h */
- #ifndef ARCH_HAS_PREFETCHW
- #define prefetchw(x) __builtin_prefetch(x,1)
- #endif
__builtin_prefetch的函数原型为void __builtin_prefetch (const void *addr, ...),常用于最小化数据的存取时间。参数addr的值为将要预取的内存地址,另外,它还有两个可选的参数rw 和 locality,rw的值只能为常量0或者1,1用于写的预取,默认值0用于读的预取。关于它的详细使用说明请参考网址http://gcc.gnu.org/onlinedocs/gcc-4.6.2/gcc/Other-Builtins.html#Other-Builtins。
对于ARMv5,prefetchw函数使用另一种实现,而S3C2410是无法支持的。另外,对于S3C2410,__LINUX_ARM_ARCH__的值为4,在linux-2.6.38.8/arch/arm/Makefile文件中被声明。
__alloc_skb函数的另一个重要功能就是分配数据缓冲区,包括skb_shared_info结构体。先使用SKB_DATA_ALIGN宏以SMP_CACHE_BYTES(对于ARM体系架构,它的值为32)位对齐数据缓冲区(这里不包括skb_shared_info结构体)的大小,然后调用kmalloc_node_track_caller函数分配内存,代码如下:
- /* linux-2.6.38.8/net/core/skbuff.c */
- size = SKB_DATA_ALIGN(size);
- data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info),
- gfp_mask, node);
- if (!data)
- goto nodata;
- prefetchw(data + size);
其中两个主要函数的实现如下:
- /* linux-2.6.38.8/include/linux/skbuff.h */
- #define SKB_DATA_ALIGN(X) (((X) + (SMP_CACHE_BYTES - 1)) & \
- ~(SMP_CACHE_BYTES - 1))
- /* linux-2.6.38.8/include/linux/slab.h */
- #define kmalloc_node_track_caller(size, flags, node) \
- kmalloc_track_caller(size, flags)
- #define kmalloc_track_caller(size, flags) \
- __kmalloc(size, flags)
- /* linux-2.6.38.8/mm/slab.c */
- void *__kmalloc(size_t size, gfp_t flags)
- {
- return __do_kmalloc(size, flags, NULL);
- }
最后,__alloc_skb函数会完成对sk_buff和skb_shared_info两个结构体变量部分成员的初始化。
- /* linux-2.6.38.8/net/core/skbuff.c */
- memset(skb, 0, offsetof(struct sk_buff, tail));
- skb->truesize = size + sizeof(struct sk_buff);
- atomic_set(&skb->users, 1);
- skb->head = data;
- skb->data = data;
- skb_reset_tail_pointer(skb);
- skb->end = skb->tail + size;
- #ifdef NET_SKBUFF_DATA_USES_OFFSET
- skb->mac_header = ~0U;
- #endif
其中,当NET_SKBUFF_DATA_USES_OFFSET未定义时,skb_reset_tail_pointer函数的定义如下:
- /* linux-2.6.38.8/include/linux/skbuff.h */
- static inline void skb_reset_tail_pointer(struct sk_buff *skb)
- {
- skb->tail = skb->data;
- }
- /* linux-2.6.38.8/net/core/skbuff.c */
- shinfo = skb_shinfo(skb);
- memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
- atomic_set(&shinfo->dataref, 1);
- kmemcheck_annotate_variable(shinfo->destructor_arg);
其中,skb_shinfo函数的定义如下:
- /* linux-2.6.38.8/include/linux/skbuff.h */
- #define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))
当NET_SKBUFF_DATA_USES_OFFSET未定义时,skb_end_pointer函数的定义如下:
- /* linux-2.6.38.8/include/linux/skbuff.h */
- static inline unsigned char *skb_end_pointer(const struct sk_buff *skb)
- {
- return skb->end;
- }
__alloc_skb函数完成的工作大致如下图(图片来自《Understanding Linux Network Internals》):
另外,当NET_SKBUFF_DATA_USES_OFFSET未定义时,sk_buff_data_t的声明如下:
- /* linux-2.6.38.8/include/linux/skbuff.h */
- typedef unsigned char *sk_buff_data_t;
(2)、skb_reserve
skb_reserve函数用于在缓冲区的头部预留一些空间,其定义如下:
- /* linux-2.6.38.8/include/linux/skbuff.h */
- static inline void skb_reserve(struct sk_buff *skb, int len)
- {
- skb->data += len;
- skb->tail += len;
- }
skb_reserve函数只是简单地更新data和tail两个指针而已,如下图(图片来自《Understanding LinuxNetwork Internals》):
(3)、skb_put
skb_put函数会把一个数据块添加到缓冲区的尾端。
- /* linux-2.6.38.8/net/core/skbuff.c */
- unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
- {
- unsigned char *tmp = skb_tail_pointer(skb);
- SKB_LINEAR_ASSERT(skb);
- skb->tail += len;
- skb->len += len;
- if (unlikely(skb->tail > skb->end))
- skb_over_panic(skb, len, __builtin_return_address(0));
- return tmp;
- }
其中,skb_tail_pointer函数在NET_SKBUFF_DATA_USES_OFFSET未定义时,其定义如下:
- /* linux-2.6.38.8/include/linux/skbuff.h */
- static inline unsigned char *skb_tail_pointer(const struct sk_buff *skb)
- {
- return skb->tail;
- }
对于ARM体系结构,在CONFIG_BUG和CONFIG_DEBUG_BUGVERBOSE都配置的情况下,SKB_LINEAR_ASSERT的定义如下:
- /* linux-2.6.38.8/include/linux/skbuff.h */
- #define SKB_LINEAR_ASSERT(skb) BUG_ON(skb_is_nonlinear(skb))
- /* linux-2.6.38.8/include/asm-generic/bug.h */
- #ifndef HAVE_ARCH_BUG_ON
- #define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while(0)
- #endif
- /* linux-2.6.38.8/arch/arm/include/asm/bug.h */
- extern void __bug(const char *file, int line) __attribute__((noreturn));
- #define BUG() __bug(__FILE__, __LINE__) /* give file/line information */
- /* linux-2.6.38.8/arch/arm/kernel/traps.c */
- void __attribute__((noreturn)) __bug(const char *file, int line)
- {
- printk(KERN_CRIT"kernel BUG at %s:%d!\n", file, line);
- *(int *)0 = 0;
- /* Avoid "noreturn function does return" */
- for (;;);
- }
当函数skb_is_nonlinear返回非零值(也就是skb->data_len的值不为0)时,SKB_LINEAR_ASSERT将产生一个oops消息。skb_is_nonlinear的定义如下:
- /* linux-2.6.38.8/include/linux/skbuff.h */
- static inline int skb_is_nonlinear(const struct sk_buff *skb)
- {
- return skb->data_len;
- }
skb_put函数其实也没有真的把数据添加到缓冲区中,而只是简单地更新了skb->tail和skb->len的值,如下图(图片来自《Understanding Linux Network Internals》):
(4)、skb_push
skb_push函数会把一个数据块添加到缓冲区的开端。
- /* linux-2.6.38.8/net/core/skbuff.c */
- unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
- {
- skb->data -= len;
- skb->len += len;
- if (unlikely(skb->data<skb->head))
- skb_under_panic(skb, len, __builtin_return_address(0));
- return skb->data;
- }
skb_push函数其实也没有真的把数据添加到缓冲区中,而只是简单地更新了skb->data和skb->len的值,如下图(图片来自《Understanding Linux Network Internals》):
(5)、skb_pull
skb_pull函数会把一个数据块从缓冲区中的顶端删除。
- /* linux-2.6.38.8/net/core/skbuff.c */
- unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
- {
- return skb_pull_inline(skb, len);
- }
- /* linux-2.6.38.8/include/linux/skbuff.h */
- static inline unsigned char *skb_pull_inline(struct sk_buff *skb, unsigned int len)
- {
- return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
- }
- static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)
- {
- skb->len -= len;
- BUG_ON(skb->len < skb->data_len);
- return skb->data += len;
- }
skb_pull函数其实也没有真的把数据从缓冲区中删除,而只是简单地更新了skb->data和skb->len的值,如下图(图片来自《Understanding Linux Network Internals》):