网络驱动移植之sk_buff结构体及其相关操作函数

http://blog.csdn.net/npy_lp/article/details/7174124

 struct sk_buff是Linux操作系统网络相关代码中最重要的结构体之一,用于管理已接收或正要传输的网络数据包。此结构体定义在include/linux/skbuff.h头文件中。

    1、结构体成员 

[cpp]  view plain copy
  1. struct sk_buff      *next;  
  2. struct sk_buff      *prev;  

    内核通过一个双向链表来维护所有的sk_buff结构体,所以每个sk_buff结构体都使用next和prev这两个成员来实现与这个双向链表的联系。 

[cpp]  view plain copy
  1. ktime_t         tstamp;  

    时间戳记,常用于表示数据包何时被接收。 

[cpp]  view plain copy
  1. struct sock     *sk;  

    当数据在本地产生或者正由本地进程接收时,TCP或UDP以及用户程序就会使用sk这个指针。 

[cpp]  view plain copy
  1. struct net_device   *dev;  

    当接收数据包时,驱动程序使用接收设备的结构体变量更新此成员;当发送一个数据包时,此成员代表的是发送数据包的设备。 

[cpp]  view plain copy
  1. char            cb[48] __aligned(8);  

    控制缓冲区(controlbuffer),每一层都可以使用它来存储私有数据。 

[cpp]  view plain copy
  1. unsigned long       _skb_refdst;  

    dst引用计数。 

[cpp]  view plain copy
  1. #ifdef CONFIG_XFRM  
  2.     struct  sec_path    *sp;  
  3. #endif  

    由IPsec协议组使用,以记录转换信息。 

[cpp]  view plain copy
  1. unsigned int        len,  
  2.             data_len;  
  3. __u16           mac_len,  
  4.             hdr_len;  

    len是缓冲区以及一些片段的总长度,data_len只是一些片段的长度,mac_len是MAC报头的长度,hdr_len是克隆skb时可写报头的长度。 

[cpp]  view plain copy
  1. union {  
  2.     __wsum      csum;  
  3.     struct {  
  4.         __u16   csum_start;  
  5.         __u16   csum_offset;  
  6.     };  
  7. };  

    校验和以及相关的状态标识。 

[cpp]  view plain copy
  1. __u32           priority;  

    此成员表示正被传输或转发的数据包QoS等级。 

[cpp]  view plain copy
  1.     kmemcheck_bitfield_begin(flags1);  
  2.     __u8            local_df:1,  
  3.                 cloned:1,  
  4.                 ip_summed:2,  
  5.                 nohdr:1,  
  6.                 nfctinfo:3;  
  7.     __u8            pkt_type:3,  
  8.                 fclone:2,  
  9.                 ipvs_property:1,  
  10.                 peeked:1,  
  11.                 nf_trace:1;  
  12.     kmemcheck_bitfield_end(flags1);  
  13.   
  14.     kmemcheck_bitfield_begin(flags2);  
  15.     __u16           queue_mapping:16;  
  16. #ifdef CONFIG_IPV6_NDISC_NODETYPE  
  17.     __u8            ndisc_nodetype:2,  
  18.                 deliver_no_wcard:1;  
  19. #else  
  20.     __u8            deliver_no_wcard:1;  
  21. #endif  
  22.     __u8            ooo_okay:1;  
  23.     kmemcheck_bitfield_end(flags2);  

    各种标识(至于这些标识的用法,在以后用到时再加以说明)。

    kmemcheck_bitfield_begin和kmemcheck_bitfield_end分别用于定义零长数组,以确定这些成员的起始和终止的位置。 它们的源代码如下所示:

[cpp]  view plain copy
  1. /* linux-2.6.38.8/include/linux/kmemcheck.h */  
  2. #define kmemcheck_bitfield_begin(name)  \  
  3.     int name##_begin[0];  
  4.   
  5. #define kmemcheck_bitfield_end(name)    \  
  6.     int name##_end[0];  

    使用零长数组实现此功能的类似代码如下: 

[cpp]  view plain copy
  1. #include <stdio.h>  
  2.   
  3. struct test {  
  4.     int len1;  
  5.     char ch_start[0];  
  6.     char ch1;  
  7.     char ch2;  
  8.     char ch_end[0];  
  9.     int len2;  
  10. };  
  11.   
  12. int main(void)  
  13. {  
  14.     char *p;  
  15.     int i;  
  16.   
  17.     struct test test_value = {  
  18.     .ch1 = 'a',  
  19.     .len1 = 1,  
  20.     .len2 = 2,  
  21.     .ch2 = 'b',  
  22.     };  
  23.   
  24.     for (p = test_value.ch_start, i = 0;   
  25.         p < test_value.ch_end; p++, i++)  
  26.     printf("*(p + %d) = %c\n", i, *p);  
  27.   
  28.     return 0;  
  29. }  

    例子输出结果:

[cpp]  view plain copy
  1. *(p + 0) = a  
  2. *(p + 1) = b  
[cpp]  view plain copy
  1. __be16          protocol;  

    由于每种协议都有自己处理接收数据包的处理函数,因此,驱动程序使用此成员来通知其上层该使用哪个处理函数。 

[cpp]  view plain copy
  1. void            (*destructor)(struct sk_buff *skb);  

    当此缓冲区被删除时,用它来完成某些工作。 

[cpp]  view plain copy
  1. #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)  
  2.     struct nf_conntrack *nfct;  
  3. #endif  
  4. #ifdef NET_SKBUFF_NF_DEFRAG_NEEDED  
  5.     struct sk_buff      *nfct_reasm;  
  6. #endif  
  7. #ifdef CONFIG_BRIDGE_NETFILTER  
  8.     struct nf_bridge_info   *nf_bridge;  
  9. #endif  

    这些成员由netfilter相关代码所使用。 

[cpp]  view plain copy
  1. int         skb_iif;  

    目的地网络设备的ifindex。 

[cpp]  view plain copy
  1. #ifdef CONFIG_NET_SCHED  
  2.     __u16           tc_index;   /* traffic control index */  
  3. #ifdef CONFIG_NET_CLS_ACT  
  4.     __u16           tc_verd;    /* traffic control verdict */  
  5. #endif  
  6. #endif  

    这些成员由流量控制功能所使用。 

[cpp]  view plain copy
  1. __u32           rxhash;  

    所接收的数据包的哈希表。 

[cpp]  view plain copy
  1. #ifdef CONFIG_NET_DMA  
  2.     dma_cookie_t        dma_cookie;  
  3. #endif  

    a cookie to one of several possible DMAoperations done by skb DMA functions. 

[cpp]  view plain copy
  1. #ifdef CONFIG_NETWORK_SECMARK  
  2.     __u32           secmark;  
  3. #endif  

     数据包安全等级标记。 

[cpp]  view plain copy
  1. union {  
  2.     __u32       mark;  
  3.     __u32       dropcount;  
  4. };  

    通用数据包标记。 

[cpp]  view plain copy
  1. __u16           vlan_tci;  

    VLAN控制信息。 

[cpp]  view plain copy
  1. sk_buff_data_t      transport_header;  
  2. sk_buff_data_t      network_header;  
  3. sk_buff_data_t      mac_header;  

    指向TCP/IP协议栈各协议报头的指针:transport_header指向传输层,network_header指向网络层,mac_header指向链路层。 

[cpp]  view plain copy
  1. sk_buff_data_t      tail;  
  2. sk_buff_data_t      end;  
  3. unsigned char       *head,  
  4.             *data;  

    这些成员代表缓冲区的边界以及其中的数据,head和end指向已分配缓冲区空间的开端和尾端,而data和tail则指向实际数据的开端和尾端,如下图(图片来自《Understanding Linux Network Internals》):

 

    其中,headroom可插入一个协议报头,tailroom可填入其他新的数据。 

[cpp]  view plain copy
  1. unsigned int        truesize;  

    此成员代表缓冲区总的大小,包括sk_buff结构本身。 

[cpp]  view plain copy
  1. atomic_t        users;  

    引用计数,避免正在使用时被其它用户所释放掉。

http://blog.csdn.net/npy_lp/article/details/7263902

   2、结构体相关操作函数

    (1)、dev_alloc_skb

    实际上,函数dev_alloc_skb最终是调用__alloc_skb函数来分配数据缓冲区和sk_buff结构体的,如下图:

 

    从dev_alloc_skb到__alloc_skb所涉及的源代码如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/net/core/skbuff.c */  
  2. struct sk_buff *dev_alloc_skb(unsigned int length)  
  3. {  
  4.     /* 
  5.      * There is more code here than it seems: 
  6.      * __dev_alloc_skb is an inline 
  7.      */  
  8.     return __dev_alloc_skb(length, GFP_ATOMIC);  
  9. }  
  10.   
  11. /* linux-2.6.38.8/include/linux/skbuff.h */  
  12. static inline struct sk_buff *__dev_alloc_skb(unsigned int length,  
  13.                           gfp_t gfp_mask)  
  14. {  
  15.     struct sk_buff *skb = alloc_skb(length + NET_SKB_PAD, gfp_mask);  
  16.     if (likely(skb))  
  17.         skb_reserve(skb, NET_SKB_PAD);  
  18.     return skb;  
  19. }  
  20.   
  21. /* linux-2.6.38.8/include/linux/skbuff.h */  
  22. static inline struct sk_buff *alloc_skb(unsigned int size,  
  23.                     gfp_t priority)  
  24. {  
  25.     return __alloc_skb(size, priority, 0, NUMA_NO_NODE);  
  26. }  

    其中,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高速缓存的源代码如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/net/socket.c */  
  2. static int __init sock_init(void)  
  3. {  
  4.     ...  
  5.   
  6.     /* Initialize skbuff SLAB cache */  
  7.     skb_init();  
  8.   
  9.     ...  
  10. }  
  11. core_initcall(sock_init);   /* early initcall */  
  12.   
  13. /* linux-2.6.38.8/net/core/skbuff.c */  
  14. void __init skb_init(void)  
  15. {  
  16.     skbuff_head_cache = kmem_cache_create("skbuff_head_cache",  
  17.                           sizeof(struct sk_buff),  
  18.                           0,  
  19.                           SLAB_HWCACHE_ALIGN|SLAB_PANIC,  
  20.                           NULL);  
  21.     skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",  
  22.                         (2*sizeof(struct sk_buff)) +  
  23.                         sizeof(atomic_t),  
  24.                         0,  
  25.                         SLAB_HWCACHE_ALIGN|SLAB_PANIC,  
  26.                         NULL);  
  27. }  

    申请sk_buff结构体对象的代码如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/net/core/skbuff.c */  
  2.     skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);  
  3.     if (!skb)  
  4.         goto out;  
  5.     prefetchw(skb);  

    对于S3C2410,prefetchw函数的实现是使用GCC的内置函数__builtin_prefetch,定义如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/include/linux/prefetch.h */  
  2. #ifndef ARCH_HAS_PREFETCHW  
  3. #define prefetchw(x) __builtin_prefetch(x,1)  
  4. #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函数分配内存,代码如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/net/core/skbuff.c */  
  2.     size = SKB_DATA_ALIGN(size);  
  3.     data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info),  
  4.             gfp_mask, node);  
  5.     if (!data)  
  6.         goto nodata;  
  7.     prefetchw(data + size);  

    其中两个主要函数的实现如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/include/linux/skbuff.h */  
  2. #define SKB_DATA_ALIGN(X)   (((X) + (SMP_CACHE_BYTES - 1)) & \  
  3.                  ~(SMP_CACHE_BYTES - 1))  
  4.   
  5. /* linux-2.6.38.8/include/linux/slab.h */  
  6. #define kmalloc_node_track_caller(size, flags, node) \  
  7.     kmalloc_track_caller(size, flags)  
  8.   
  9. #define kmalloc_track_caller(size, flags) \  
  10.     __kmalloc(size, flags)  
  11.   
  12. /* linux-2.6.38.8/mm/slab.c */  
  13. void *__kmalloc(size_t size, gfp_t flags)  
  14. {  
  15.     return __do_kmalloc(size, flags, NULL);  
  16. }  

    最后,__alloc_skb函数会完成对sk_buff和skb_shared_info两个结构体变量部分成员的初始化。 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/net/core/skbuff.c */  
  2.     memset(skb, 0, offsetof(struct sk_buff, tail));  
  3.     skb->truesize = size + sizeof(struct sk_buff);  
  4.     atomic_set(&skb->users, 1);  
  5.     skb->head = data;  
  6.     skb->data = data;  
  7.     skb_reset_tail_pointer(skb);  
  8.     skb->end = skb->tail + size;  
  9. #ifdef NET_SKBUFF_DATA_USES_OFFSET  
  10.     skb->mac_header = ~0U;  
  11. #endif  

    其中,当NET_SKBUFF_DATA_USES_OFFSET未定义时,skb_reset_tail_pointer函数的定义如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/include/linux/skbuff.h */  
  2. static inline void skb_reset_tail_pointer(struct sk_buff *skb)  
  3. {  
  4.     skb->tail = skb->data;  
  5. }  

 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/net/core/skbuff.c */  
  2.     shinfo = skb_shinfo(skb);  
  3.     memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));  
  4.     atomic_set(&shinfo->dataref, 1);  
  5.     kmemcheck_annotate_variable(shinfo->destructor_arg);  

    其中,skb_shinfo函数的定义如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/include/linux/skbuff.h */  
  2.     #define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))  

    当NET_SKBUFF_DATA_USES_OFFSET未定义时,skb_end_pointer函数的定义如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/include/linux/skbuff.h */  
  2. static inline unsigned char *skb_end_pointer(const struct sk_buff *skb)  
  3. {  
  4.     return skb->end;  
  5. }  

     __alloc_skb函数完成的工作大致如下图(图片来自《Understanding Linux Network Internals》):

 

    另外,当NET_SKBUFF_DATA_USES_OFFSET未定义时,sk_buff_data_t的声明如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/include/linux/skbuff.h */  
  2. typedef unsigned char *sk_buff_data_t;  

    (2)、skb_reserve

    skb_reserve函数用于在缓冲区的头部预留一些空间,其定义如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/include/linux/skbuff.h */  
  2. static inline void skb_reserve(struct sk_buff *skb, int len)  
  3. {  
  4.     skb->data += len;  
  5.     skb->tail += len;  
  6. }  

    skb_reserve函数只是简单地更新data和tail两个指针而已,如下图(图片来自《Understanding LinuxNetwork Internals》):

 

    (3)、skb_put

    skb_put函数会把一个数据块添加到缓冲区的尾端。 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/net/core/skbuff.c */  
  2. unsigned char *skb_put(struct sk_buff *skb, unsigned int len)  
  3. {  
  4.     unsigned char *tmp = skb_tail_pointer(skb);  
  5.     SKB_LINEAR_ASSERT(skb);  
  6.     skb->tail += len;  
  7.     skb->len  += len;  
  8.     if (unlikely(skb->tail > skb->end))  
  9.         skb_over_panic(skb, len, __builtin_return_address(0));  
  10.     return tmp;  
  11. }  

    其中,skb_tail_pointer函数在NET_SKBUFF_DATA_USES_OFFSET未定义时,其定义如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/include/linux/skbuff.h */  
  2. static inline unsigned char *skb_tail_pointer(const struct sk_buff *skb)  
  3. {  
  4.     return skb->tail;  
  5. }  

    对于ARM体系结构,在CONFIG_BUG和CONFIG_DEBUG_BUGVERBOSE都配置的情况下,SKB_LINEAR_ASSERT的定义如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/include/linux/skbuff.h */  
  2. #define SKB_LINEAR_ASSERT(skb)  BUG_ON(skb_is_nonlinear(skb))  
  3.   
  4. /* linux-2.6.38.8/include/asm-generic/bug.h */  
  5. #ifndef HAVE_ARCH_BUG_ON  
  6. #define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while(0)  
  7. #endif  
  8.   
  9. /* linux-2.6.38.8/arch/arm/include/asm/bug.h */  
  10. extern void __bug(const char *file, int line) __attribute__((noreturn));  
  11.   
  12. #define BUG()       __bug(__FILE__, __LINE__) /* give file/line information */  
  13.   
  14. /* linux-2.6.38.8/arch/arm/kernel/traps.c */  
  15. void __attribute__((noreturn)) __bug(const char *file, int line)  
  16. {  
  17.     printk(KERN_CRIT"kernel BUG at %s:%d!\n", file, line);  
  18.     *(int *)0 = 0;  
  19.   
  20.     /* Avoid "noreturn function does return" */  
  21.     for (;;);  
  22. }  

    当函数skb_is_nonlinear返回非零值(也就是skb->data_len的值不为0)时,SKB_LINEAR_ASSERT将产生一个oops消息。skb_is_nonlinear的定义如下: 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/include/linux/skbuff.h */  
  2. static inline int skb_is_nonlinear(const struct sk_buff *skb)  
  3. {  
  4.     return skb->data_len;  
  5. }  

    skb_put函数其实也没有真的把数据添加到缓冲区中,而只是简单地更新了skb->tail和skb->len的值,如下图(图片来自《Understanding Linux Network Internals》):

 

    (4)、skb_push

    skb_push函数会把一个数据块添加到缓冲区的开端。 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/net/core/skbuff.c */  
  2. unsigned char *skb_push(struct sk_buff *skb, unsigned int len)  
  3. {  
  4.     skb->data -= len;  
  5.     skb->len  += len;  
  6.     if (unlikely(skb->data<skb->head))  
  7.         skb_under_panic(skb, len, __builtin_return_address(0));  
  8.     return skb->data;  
  9. }  

    skb_push函数其实也没有真的把数据添加到缓冲区中,而只是简单地更新了skb->data和skb->len的值,如下图(图片来自《Understanding Linux Network Internals》):

 

    (5)、skb_pull

    skb_pull函数会把一个数据块从缓冲区中的顶端删除。 

[cpp]  view plain copy
  1. /* linux-2.6.38.8/net/core/skbuff.c */  
  2. unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)  
  3. {  
  4.     return skb_pull_inline(skb, len);  
  5. }  
  6.   
  7. /* linux-2.6.38.8/include/linux/skbuff.h */  
  8. static inline unsigned char *skb_pull_inline(struct sk_buff *skb, unsigned int len)  
  9. {  
  10.     return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);  
  11. }  
  12.   
  13. static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)  
  14. {  
  15.     skb->len -= len;  
  16.     BUG_ON(skb->len < skb->data_len);  
  17.     return skb->data += len;  
  18. }  

    skb_pull函数其实也没有真的把数据从缓冲区中删除,而只是简单地更新了skb->data和skb->len的值,如下图(图片来自《Understanding Linux Network Internals》):

 


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Linux 内核网络协议栈中,sk_buff 是一个非常重要的数据结构,用于存储网络数据包及其相关信息。sk_buff 结构体定义在 include/linux/skbuff.h 头文件中,它的定义如下: ``` struct sk_buff { /* 网络数据包 */ struct sk_buff *next; struct sk_buff *prev; ktime_t tstamp; struct sock *sk; struct net_device *dev; struct net_device *real_dev; struct net_device *vlan_dev; unsigned char cb[48]; /* 网络数据包的数据部分 */ unsigned int len, data_len; __u16 mac_len, hdr_len; union { __wsum csum; struct { u16 csum_start; u16 csum_offset; }; }; unsigned char *head, *data; struct skb_shared_info *shinfo; /* 与协议栈相关的信息 */ unsigned int truesize; atomic_t users; unsigned char *tail; unsigned char *end; unsigned int frag_list_len; struct sk_buff *frag_list; skb_frag_t frags[MAX_SKB_FRAGS]; struct skb_ext *ext; }; ``` 下面是一些重要的字段及其含义: - next/prev:指向链表中下一个/上一个 sk_buff 结构体,用于构成链表结构,例如网络驱动程序的接收队列。 - tstamp:记录从协议栈接收到网络数据包的时间戳。 - sk:指向与这个网络数据包相关的套接字,用于实现协议栈和应用层之间的通信。 - dev:指向接收或发送这个网络数据包的网络设备结构体。 - len/data_len/mac_len/hdr_len:表示网络数据包的长度、负载长度、MAC 头长度和协议头长度。 - csum/csum_start/csum_offset:表示网络数据包的校验和值、校验和计算开始位置和偏移量。 - head/data/tail/end:指向网络数据包的头部、负载、尾部和结束位置。 - frag_list/frag_list_len/frags:表示网络数据包的分片信息。 - truesize:表示 sk_buff 结构体的大小。 - users:表示 sk_buff 结构体的引用计数。 - shinfo:指向一个共享信息结构体,用于记录一些共享的数据。 总之,sk_buff 结构体非常重要,它是整个协议栈中重要的数据缓存。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值