转自:http://blog.csdn.net/shanshanpt/article/details/21024465

在2.6.24之后这个结构体有了较大的变化,此处先说一说2.6.16版本的sk_buff,以及解释一些问题。

一、

先直观的看一下这个结构体~~~~~~~~~~~~~~~~~~~~~~在下面解释每个字段的意义~~~~~~~~~~~

  1. struct sk_buff {
  2. /* These two members must be first. */
  3. struct sk_buff          *next;
  4. struct sk_buff          *prev;
  5. struct sock             *sk;
  6. struct skb_timeval      tstamp;
  7. struct net_device       *dev;
  8. struct net_device       *input_dev;
  9. union {
  10. struct tcphdr   *th;
  11. struct udphdr   *uh;
  12. struct icmphdr  *icmph;
  13. struct igmphdr  *igmph;
  14. struct iphdr    *ipiph;
  15. struct ipv6hdr  *ipv6h;
  16. unsigned char   *raw;
  17. } h;
  18. union {
  19. struct iphdr    *iph;
  20. struct ipv6hdr  *ipv6h;
  21. struct arphdr   *arph;
  22. unsigned char   *raw;
  23. } nh;
  24. union {
  25. unsigned char   *raw;
  26. } mac;
  27. struct  dst_entry       *dst;
  28. struct  sec_path        *sp;
  29. /*
  30. * This is the control buffer. It is free to use for every
  31. * layer. Please put your private variables there. If you
  32. * want to keep them across layers you have to do a skb_clone()
  33. * first. This is owned by whoever has the skb queued ATM.
  34. */
  35. char                    cb[48];
  36. unsigned int            len,
  37. data_len,
  38. mac_len,
  39. csum;
  40. __u32                   priority;
  41. __u8                    local_df:1,
  42. cloned:1,
  43. ip_summed:2,
  44. nohdr:1,
  45. nfctinfo:3;
  46. __u8                    pkt_type:3,
  47. fclone:2,
  48. ipvs_property:1;
  49. __be16                  protocol;
  50. void                    (*destructor)(struct sk_buff *skb);
  51. #ifdef CONFIG_NETFILTER
  52. __u32                   nfmark;
  53. struct nf_conntrack     *nfct;
  54. #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
  55. struct sk_buff          *nfct_reasm;
  56. #endif
  57. #ifdef CONFIG_BRIDGE_NETFILTER
  58. struct nf_bridge_info   *nf_bridge;
  59. #endif
  60. #endif /* CONFIG_NETFILTER */
  61. #ifdef CONFIG_NET_SCHED
  62. __u16                   tc_index;       /* traffic control index */
  63. #ifdef CONFIG_NET_CLS_ACT
  64. __u16                   tc_verd;        /* traffic control verdict */
  65. #endif
  66. #endif
  67. /* These elements must be at the end, see alloc_skb() for details.  */
  68. unsigned int            truesize;
  69. atomic_t                users;
  70. unsigned char           *head,
  71. *data,
  72. *tail,
  73. *end;
  74. };

> : next和prev,这两个域是用来连接相关的skb的(例如如果有分片,将这些分片连接在一起可以)

> : sk,指向报文所属的套接字指针

> : tstamp,记录接收或者传输报文的时间戳

> : dev和input_dev,记录接收或者发送的设备

>: union u,对于一个层次,例如tcp层,可能有很多不同的协议,他们的协议头不一样,那么这个联合体就是记录这些协议头的。

此处u就是代表传输层

> : union nh,代表网络层头

> : union mac,代表链路层头

> : dst,指向des_entry结构,记录了到达目的地的路由信息,以及其他的一些网络特征信息。

> : sp:安全路径,用于xfrm

> : cb[],保存与协议相关的控制信息,每个协议可能独立使用这些信息。

> : 重要的字段 len 和 data_len:

len代: 表整个数据区域的长度!这里要提前解释几个定义,skb的组成是有sk_buff控制 + 线性数据 + 非线性数据

(skb_shared_info) 组成!

后面会具体解释是什么意思!在sk_buff这个里面没有实际的数据,这里仅仅是控制信息,数据是通过后面的data指针指向其他内

存块的!那个内存块中是线性数据和

非线性数据!那么len就是length(线性数据) + length(非线性数据)!!!

data_len: 指的是length(非线性数据)!!!那么可以知道:length(线性数据) =  skb->len - skb->data_len

> : mac_len,指的是mac头长度

> : csum,某时刻协议的校验和

> : priority,报文排队优先级,取决于ip中的tos域

> : local_df,允许在本地分配

> : cloned,保存当前的skb_buff是克隆的还是原始数据

> : ip_summed,是否计算ip校验和

> : nohdr,仅仅引用数据区域

> : pkt_type,报文类型,例如广播,多播,回环,本机,传出...

> : fclone,skb_buff克隆状态

> : ipvs_property,skb_buff是否属于ipvs

> : protocal,协议信息

> : nfmark,用于钩子之间通信

> : nfct_reasm,netfilter的跟踪连接重新组装指针

> : nf_bridge,保存桥接信息

> : tc_index: Traffic control index,tc_verd: traffic control verdict

> : truesize,该缓冲区分配的所有总的内存,包括:skb_buff + 所有数据大小

> : users,保存引用skb_buff的数量

> : 重要数据字段:head,data,tail,end!!!

head:指向分配给的线性数据内存首地址( 建立起一个观念:并不是分配这么多内存,就都能被使用作为数据存储,可能没这么多

数据也有可能!但是也不要认为分配这么多 就足够了,也不一定(非线性数据就是例子) )

data:指向保存数据内容的首地址!我们由head可以知道,head和data不一定就是指在同一个位置!!!

tail:指向数据的结尾!

end:指向分配的内存块的结尾! ( 由上面我们知道数据结尾 != 分配的内存块的结尾 )

下面还会具体分析!!!!!!!!!!!

二、

我觉得需要先了解一些对于一个数据skb到底有什么,或者说由哪些元素组成!这就需要知道所谓的 “线性数据” 和 “非线性数据”。

基本的组成如下:

> : sk_buff : 这是一个sk_buff的控制结构

> : 线性数据区域

> : 非线性数据区域( 由skb_shared_info结构体管理 )

那么下面通过一个图来看看这个skb结构到底是怎么样的!看(图一)

  (图一)

借助图一,我们先来分析两个重要字段:len和data_len!

之前说过len代表的是整个数据的长度,data_len代表的是非线性数据长度。我们由图一可以看到线性数据长度为l1,再看看非线性数据,其实就是看frags[]和frag_list

ok...那么我们可以知道非线性数据长度为( l2 + ... + ln ) + ( l(n+1) + ... + lm )

即:len = l1 + ( l2 + ... + ln ) + ( l(n+1) + ... + lm )

data_len = ( l2 + ... + ln ) + ( l(n+1) + ... + lm )

ok...

现在从分配内存开始解释这个图的由来:

我们使用skb_alloc给skb分配空间,那么刚刚分配结束返回时候,是什么样的情况呢?看下图(图二):

  (图二)

刚刚开始初始化的时候,预分配一个一块线性数据区域,这个区域一般放入的是各个协议层次的不同的头,还有一些实际数据,下面的非线性区域是为了弥补当数据真的很多的时候,作为数据区域的扩展!关于skb_shared_info具体意思下面会继续说!注意在初始化的时候,head,data和tail都指向内存的开始位置,head在这个位置始终不变,它表示的是分配的内存的开始位置。end的位置也是不变的,表示的是分配的内存的结束位置。data和tail会随着数据的加入和减少变化,总之表示的是放入数据的内存区域(由图一)可知。

现在需要解释一下skb_shared_info这个结构体,这个结构体真的是很很有特色!主要是其中的两个字段frags和frag_list,下面继续解释:

  1. struct skb_shared_info {
  2. atomic_t        dataref;        // 对象被引用次数
  3. unsigned short  nr_frags;       // 分页段数目,即frags数组元素个数
  4. unsigned short  tso_size;
  5. unsigned short  tso_segs;
  6. unsigned short  ufo_size;
  7. unsigned int    ip6_frag_id;
  8. struct sk_buff  *frag_list;    // 一般用于分段(还没有非常清楚的理解)
  9. skb_frag_t      frags[MAX_SKB_FRAGS]; // 保存分页数据(skb->data_len=所有的数组数据长度之和)
  10. };

关于frags和frag_list没有必然的联系!

> : 对于frags[]一般用在,当数据真的很多,而且在线性数据区域装不下的时候,需要使用这个,skb_frag_t中是一页一页的数据,先看看结构体:

  1. struct skb_frag_struct {
  2. struct page *page;    // 代表一页数据
  3. __u16 page_offset;    // 代表相对开始位置的页偏移量
  4. __u16 size;           // page中数据长度
  5. };

需要注意的是:只有在DMA支持物理分散页的Scatter/Gather(SG,分散/聚集)操作时候才可以使用frags[]来保存剩下的数据,否则,只能扩展线性数据区域进行保存!!!

这些页其实是其实就是虚拟页映射到物理页的结构,看下图(图三):

  (图三)

> : 对于frag_list来说,一般我们在分片的时候里面装入每个片的信息,注意,每个片最终也都是被封装成一个小的skb,这个必须

的!

注意:具体怎么分片的看上一篇博文:数据分片 (  看其中的ip_fragment函数  )

那么看一下其基本结构如图四:

    (图四)

 

三、

最重要的是需要理解对于这个skb是怎么操作的,在操作的过程中,每一块的内存分配是怎么变化的,这才更重要!

看下面的函数们:

> : alloc_skb()函数

  1. static inline struct sk_buff *alloc_skb(unsigned int size,
  2. gfp_t priority)
  3. {
  4. return __alloc_skb(size, priority, 0);
  5. }

其实看__alloc_skb函数:

  1. struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
  2. int fclone)
  3. {
  4. kmem_cache_t *cache;
  5. struct skb_shared_info *shinfo;
  6. struct sk_buff *skb;
  7. u8 *data;
  8. cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;    // 根据克隆状态来判断在哪一个缓冲区进行分配cache
  9. /* Get the HEAD */
  10. skb = kmem_cache_alloc(cache, gfp_mask & ~__GFP_DMA);        // 得到skb,注意这里没有包含数据,仅仅是skb_buff这个结构体
  11. if (!skb)
  12. goto out;
  13. /* Get the DATA. Size must match skb_add_mtu(). */
  14. size = SKB_DATA_ALIGN(size);                                     // 获得线性数据分片长度(注意对齐)
  15. data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask); // 注意分配的是什么,是size + skb_shared_info!!!!!
  16. if (!data)
  17. goto nodata;
  18. memset(skb, 0, offsetof(struct sk_buff, truesize));          // 初始化
  19. skb->truesize = size + sizeof(struct sk_buff);               // 实际大小等于sk_buff + size,刚刚开始还没有非线性数据
  20. atomic_set(&skb->users, 1);
  21. skb->head = data;                                            // 注意指针,这个结合上面的图一清二楚
  22. skb->data = data;
  23. skb->tail = data;
  24. skb->end  = data + size;
  25. /* make sure we initialize shinfo sequentially */
  26. shinfo = skb_shinfo(skb);
  27. atomic_set(&shinfo->dataref, 1);
  28. shinfo->nr_frags  = 0;
  29. shinfo->tso_size = 0;
  30. shinfo->tso_segs = 0;
  31. shinfo->ufo_size = 0;
  32. shinfo->ip6_frag_id = 0;
  33. shinfo->frag_list = NULL;
  34. if (fclone) {
  35. struct sk_buff *child = skb + 1;
  36. atomic_t *fclone_ref = (atomic_t *) (child + 1);
  37. skb->fclone = SKB_FCLONE_ORIG;
  38. atomic_set(fclone_ref, 1);
  39. child->fclone = SKB_FCLONE_UNAVAILABLE;
  40. }
  41. out:
  42. return skb;
  43. nodata:
  44. kmem_cache_free(cache, skb);
  45. skb = NULL;
  46. goto out;
  47. }

那么alloc之后的图就是(图五):

   (图五)

其实和图二是一样的!我们可以看到,现在仅仅是分配了线束数据区域,但是现在还没有数据!一定要注意!所以前面三个指针指在一起!因为没有数据,那么len和data_len的值就是0 !

> : skb_reserve函数

  1. static inline void skb_reserve(struct sk_buff *skb, int len)
  2. {
  3. skb->data += len;
  4. skb->tail += len;
  5. }

代码其实很easy、就是移动两个指针而已~

这个函数很重要,是为“协议头”预留空间!而且是尽最大的空间预留,因为很多头都会有可选项,那么我们不知道可选项是多大,所以只能是按照最大的分配,那么也说明了一点,预留的空间headroom也就是不一定都能使用完的!可能还有剩余的,由上面的图也可以看出来!这也是为什么需要这么多指针的问题!那么这个函数直接导致head指针和tail、data指针分离,入下面图六所示:

    (图六)

注意headroom就是用来存储各个协议头的足够大的空间,tailroom就可以认为是存储其他线性数据的空间。( 这里不要曲解协议头不是线性数据,其实协议头也是!!!所以当增加头的时候,data指针向上移动,当增加其他数据的时候,tail指针向下移动 )。现在data和tail指向一起,那么还是说明数据没有!!!

> : skb_put函数 ----> 用于操作线性数据区域(tailroom区域)的用户数据

  1. static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
  2. {
  3. unsigned char *tmp = skb->tail;
  4. SKB_LINEAR_ASSERT(skb);
  5. skb->tail += len;                 // 移动指针
  6. skb->len  += len;                 // 数据空间增大len
  7. if (unlikely(skb->tail>skb->end)) // 如果tail指针超过end指针了,那么处理错误~
  8. skb_over_panic(skb, len, current_text_addr());
  9. return tmp;
  10. }

这函数其实就是从tailroom预留空间,相当于是移动tail指针,这样如果从上图(图六)开始看,也就是tail开始向下移动,和data分离了。。。一般来说,这样做都是为了用户数据再次处理,或者说为TCP/IP的负载预留空间!

看图七,当使用skb_put时候,由图六---->图七

     (图七)

我们可以看到指针的移动data还是在headroom的下面,中间的是用户数据预留的部分,由skb_put得到,tail表示数据结尾!再看一下sk_buff中的len,变成了数据长度ld!!

> : skb_push函数:----------> 用于操作headroom区域的协议头

  1. static inline unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
  2. {
  3. skb->data -= len;      // 向上移动指针
  4. skb->len  += len;      // 数据长度增加
  5. if (unlikely(skb->data<skb->head))  // data指针超过head那么就是处理错误~
  6. skb_under_panic(skb, len, current_text_addr());
  7. return skb->data;
  8. }

和skb_put对应,上面试操作用户数据的,这里是操作协议头的!其实就是data指针向上移动而已~注意len增大了哦~前面说了协议头也是属于数据!

如下面图所示,由图七---->图八

   (图八)

我们可以知道,data向上移动了,同时注意len变成ld+lp了,其中lp是这个增加的协议头的长度!

> : skb_pull函数:-----------> 其实这个函数才是与skb_push函数对应的函数!因为这是去头函数,而skb_push是增头函数!所以这个函数一般用在解包的时候!

  1. static inline unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
  2. {
  3. return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
  4. }
  5. static inline unsigned char *__pskb_pull(struct sk_buff *skb, unsigned int len)
  6. {
  7. if (len > skb_headlen(skb) &&
  8. !__pskb_pull_tail(skb, len-skb_headlen(skb)))
  9. return NULL;
  10. skb->len -= len;                              // 长度减小
  11. return skb->data += len;                      // 移动指针
  12. }

其实就是data指针向下移动,当前一个协议头被去掉,headroom剩余的空间增大了!看下图:

由图八---->图九:

 (图九)

虚线是data之前的指针位置,现在移动到下面实线!!需注意:len的长度减小,减小的大小是剥去的头的大小!!

四、

最后我们从两条线整体分析一下:

1:从应用层用户数据开始,直到物理层发送出去

> 初始化的什么就不多说了,和前面的差不多,现在也加入用户数据已经在了,如图七所示一样!那么到了TCP层,需要增加

TCP层的头:

如图10所示:

         (图10)

需要注意的是这里是传输层,那么传输层的结构u中的th代表的是tcp的头,那么tcp指向tcp头OK!同时注意 len长度+=l1 哦~~~

> 再看到了IP层:如图11

 (图11)

至于需要解释什么就没什么了,都是一样的~

> 到链路层了:如图12

  (图12)

OK!

2:第二个过程其实是第一个逆过程,都差不多,所以不多说了~

五、

最后看一下操作skb的两个函数pskb_copy和skb_copy

前者仅仅是将sk_buff的结构体和线性数据copy过来,对于非线性数据,是引用原始的skb的数据的!而后者是不仅将sk_buff和线性数据拷贝,同时将非线性数据也copy了一份,看下面就明白了!这就在效率上就差了很多!所以如果不想修改数据,那么还是使用pskb_copy更好!

对于pskb_copy:

对于skb_copy:

OK  我觉得差不多了~~~~~结束~~~~~~~~~~~~~

Linux 内核网络协议栈 ------sk_buff 结构体 以及 完全解释 (2.6.16)的更多相关文章

  1. 【Linux 内核网络协议栈源码剖析】网络栈主要结构介绍(socket、sock、sk_buff,etc)

    原文:http://blog.csdn.net/wenqian1991/article/details/46700177 通过前面的分析,可以发现,网络协议栈中的数据处理,都是基于各类结构体,所有有关 ...

  2. Linux内核网络协议栈优化总纲

    本文原创为freas_1990  转载请标明出处:http://blog.csdn.net/freas_1990/article/details/9474121 Jack:淫龙,Linux内核协议栈如 ...

  3. 5.2【Linux 内核网络协议栈源码剖析】socket 函数剖析 ☆☆☆

    深度剖析网络协议栈中的 socket 函数,可以说是把前面介绍的串联起来,将网络协议栈各层关联起来. 应用层 FTP SMTP HTTP ... 传输层 TCP UDP 网络层 IP ICMP ARP ...

  4. 【驱动】网卡驱动·linux内核网络分层结构

    Preface   Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计. Linux内核采用分层结构处理网络数据包.分层结构与网络协议的结构匹配,既能简化数据包处理流程 ...

  5. [转]linux内核网络分层结构

    Preface   Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计. Linux内核采用分层结构处理网络数据包.分层结构与网络协议的结构匹配,既能简化数据包处理流程 ...

  6. Linux 网络设备驱动开发(一) —— linux内核网络分层结构

    Preface Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计. Linux内核采用分层结构处理网络数据包.分层结构与网络协议的结构匹配,既能简化数据包处理流程,又 ...

  7. Linux内核网络数据包处理流程

    Linux内核网络数据包处理流程 from kernel-4.9: 0. Linux内核网络数据包处理流程 - 网络硬件 网卡工作在物理层和数据链路层,主要由PHY/MAC芯片.Tx/Rx FIFO. ...

  8. Linux内核--网络栈实现分析(七)--数据包的传递过程(下)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7545855 更多请查看专栏,地 ...

  9. Linux内核--网络栈实现分析(四)--网络层之IP协议(上)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7514017 更多请看专栏,地址 ...

随机推荐

  1. java采用zip方式实现String的压缩和解压缩CompressStringUtil类

    CompressStringUtil类:不多说,直接贴代码: /** * 压缩 * * @param paramString * @return */ public static final byte ...

  2. HDOJ题目分类

    模拟题, 枚举1002 1004 1013 1015 1017 1020 1022 1029 1031 1033 1034 1035 1036 1037 1039 1042 1047 1048 104 ...

  3. windows下安装apache zookeeper

    ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件,提供的功 ...

  4. AngularJs(SPA)单页面SEO以及百度统计应用(下)

    苍苍之天不得久视,堂堂之地不得久履 当你小心翼翼的开启服务端渲染的同时,一个问题不得不注意,使用内存模式去保存渲染过的页面,这样服务断掉重启后,缓存也没有了,所以这里我们使用mongdodb进行本地化 ...

  5. CF 554A 字符串水题

    给出一个字符串,问再加入一个字母,最多能形成多少种字符串 inputaoutput51inputhioutput76 # include <iostream> # include < ...

  6. 【LOJ】#2509. 「AHOI / HNOI2018」排列

    题解 虽然要求一个dfs序,但是不是从根开始贪心 从最小的点开始贪心,最小的点显然是父亲选了之后马上就选它 那么我们每次把最小的点和父亲合并,两个联通块之间也是如此 对于两个联通块,他们合并的顺序应该 ...

  7. 【LOJ】#2098. 「CQOI2015」多项式

    题解 令x = x - t代换一下会发现 \(\sum_{i = 0}^{n}a_i (x + t)^i = \sum_{i = 0}^{n} b_{i} x^{i}\) 剩下的就需要写高精度爆算了- ...

  8. 将 Ubuntu 16.04 LTS 升级到 Ubuntu 18.04 LTS

    将 Ubuntu 16.04 LTS 升级到 Ubuntu 18.04 LTS   Ubuntu 18.04 LTS(Bionic Beaver)即将发布, 如果您正在使用Ubuntu 16.04LT ...

  9. windows下mysql配置

    windows下mysql配置   忙活了大半天,总算配置好了,本文献给windows下没试用过Mysql的小白,勿喷 http://blog.csdn.net/z1074907546/article ...

  10. P1244 青蛙过河

    P1244 青蛙过河NOI2000主要思想:数学归纳法 递推 压位高精度 化归 理解能力和找规律的能力题意再述:1.青蛙从上到下必须连续递增或者下面是石墩 而不能是1 12 33 4而且每时每刻都要满 ...