新沂做网站,hxsp最新域名是什么,大众点评做团购网站,泰州网站建设优化在2.6.24之后这个结构体有了较大的变化#xff0c;此处先说一说2.6.16版本的sk_buff#xff0c;以及解释一些问题。一、先直观的看一下这个结构体~~~~~~~~~~~~~~~~~~~~~~在下面解释每个字段的意义~~~~~~~~~~~[cpp] view plaincopyprint?struct sk_buff { /* These…在2.6.24之后这个结构体有了较大的变化此处先说一说2.6.16版本的sk_buff以及解释一些问题。一、先直观的看一下这个结构体~~~~~~~~~~~~~~~~~~~~~~在下面解释每个字段的意义~~~~~~~~~~~[cpp] view plain copy print?struct sk_buff { /* These two members must be first. */ struct sk_buff *next; struct sk_buff *prev; struct sock *sk; struct skb_timeval tstamp; struct net_device *dev; struct net_device *input_dev; 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; struct dst_entry *dst; struct sec_path *sp; /* * This is the control buffer. It is free to use for every * layer. Please put your private variables there. If you * want to keep them across layers you have to do a skb_clone() * first. This is owned by whoever has the skb queued ATM. */ char cb[48]; unsigned int len, data_len, mac_len, csum; __u32 priority; __u8 local_df:1, cloned:1, ip_summed:2, nohdr:1, nfctinfo:3; __u8 pkt_type:3, fclone:2, ipvs_property:1; __be16 protocol; void (*destructor)(struct sk_buff *skb); #ifdef CONFIG_NETFILTER __u32 nfmark; struct nf_conntrack *nfct; #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) struct sk_buff *nfct_reasm; #endif #ifdef CONFIG_BRIDGE_NETFILTER struct nf_bridge_info *nf_bridge; #endif #endif /* CONFIG_NETFILTER */ #ifdef CONFIG_NET_SCHED __u16 tc_index; /* traffic control index */ #ifdef CONFIG_NET_CLS_ACT __u16 tc_verd; /* traffic control verdict */ #endif #endif /* These elements must be at the end, see alloc_skb() for details. */ unsigned int truesize; atomic_t users; unsigned char *head, *data, *tail, *end; }; : 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报文类型例如广播多播回环本机传出... : fcloneskb_buff克隆状态 : ipvs_propertyskb_buff是否属于ipvs : protocal协议信息 : nfmark用于钩子之间通信 : nfct_reasmnetfilter的跟踪连接重新组装指针 : nf_bridge保存桥接信息 : tc_index: Traffic control indextc_verd: traffic control verdict : truesize该缓冲区分配的所有总的内存包括skb_buff 所有数据大小 : users保存引用skb_buff的数量 : 重要数据字段headdatatailend 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_listok...那么我们可以知道非线性数据长度为( l2 ... ln ) ( l(n1) ... lm )即len l1 ( l2 ... ln ) ( l(n1) ... lm ) data_len ( l2 ... ln ) ( l(n1) ... lm )ok...现在从分配内存开始解释这个图的由来我们使用skb_alloc给skb分配空间那么刚刚分配结束返回时候是什么样的情况呢看下图图二 图二刚刚开始初始化的时候预分配一个一块线性数据区域这个区域一般放入的是各个协议层次的不同的头还有一些实际数据下面的非线性区域是为了弥补当数据真的很多的时候作为数据区域的扩展关于skb_shared_info具体意思下面会继续说注意在初始化的时候headdata和tail都指向内存的开始位置head在这个位置始终不变它表示的是分配的内存的开始位置。end的位置也是不变的表示的是分配的内存的结束位置。data和tail会随着数据的加入和减少变化总之表示的是放入数据的内存区域(由图一)可知。现在需要解释一下skb_shared_info这个结构体这个结构体真的是很很有特色主要是其中的两个字段frags和frag_list下面继续解释[cpp] view plain copy print?struct skb_shared_info { atomic_t dataref; // 对象被引用次数 unsigned short nr_frags; // 分页段数目即frags数组元素个数 unsigned short tso_size; unsigned short tso_segs; unsigned short ufo_size; unsigned int ip6_frag_id; struct sk_buff *frag_list; // 一般用于分段(还没有非常清楚的理解) skb_frag_t frags[MAX_SKB_FRAGS]; // 保存分页数据(skb-data_len所有的数组数据长度之和) }; 关于frags和frag_list没有必然的联系 : 对于frags[]一般用在当数据真的很多而且在线性数据区域装不下的时候需要使用这个skb_frag_t中是一页一页的数据先看看结构体[cpp] view plain copy print?struct skb_frag_struct { struct page *page; // 代表一页数据 __u16 page_offset; // 代表相对开始位置的页偏移量 __u16 size; // page中数据长度 }; 需要注意的是只有在DMA支持物理分散页的Scatter/GatherSG分散/聚集操作时候才可以使用frags[]来保存剩下的数据否则只能扩展线性数据区域进行保存这些页其实是其实就是虚拟页映射到物理页的结构看下图(图三) 图三 : 对于frag_list来说一般我们在分片的时候里面装入每个片的信息注意每个片最终也都是被封装成一个小的skb这个必须 的 注意具体怎么分片的看上一篇博文数据分片 ( 看其中的ip_fragment函数 ) 那么看一下其基本结构如图四 图四三、最重要的是需要理解对于这个skb是怎么操作的在操作的过程中每一块的内存分配是怎么变化的这才更重要看下面的函数们 : alloc_skb()函数[cpp] view plain copy print?static inline struct sk_buff *alloc_skb(unsigned int size, gfp_t priority) { return __alloc_skb(size, priority, 0); } 其实看__alloc_skb函数[cpp] view plain copy print?struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask, int fclone) { kmem_cache_t *cache; struct skb_shared_info *shinfo; struct sk_buff *skb; u8 *data; cache fclone ? skbuff_fclone_cache : skbuff_head_cache; // 根据克隆状态来判断在哪一个缓冲区进行分配cache /* Get the HEAD */ skb kmem_cache_alloc(cache, gfp_mask ~__GFP_DMA); // 得到skb注意这里没有包含数据仅仅是skb_buff这个结构体 if (!skb) goto out; /* Get the DATA. Size must match skb_add_mtu(). */ size SKB_DATA_ALIGN(size); // 获得线性数据分片长度注意对齐 data kmalloc(size sizeof(struct skb_shared_info), gfp_mask); // 注意分配的是什么是size skb_shared_info if (!data) goto nodata; memset(skb, 0, offsetof(struct sk_buff, truesize)); // 初始化 skb-truesize size sizeof(struct sk_buff); // 实际大小等于sk_buff size刚刚开始还没有非线性数据 atomic_set(skb-users, 1); skb-head data; // 注意指针这个结合上面的图一清二楚 skb-data data; skb-tail data; skb-end data size; /* make sure we initialize shinfo sequentially */ shinfo skb_shinfo(skb); atomic_set(shinfo-dataref, 1); shinfo-nr_frags 0; shinfo-tso_size 0; shinfo-tso_segs 0; shinfo-ufo_size 0; shinfo-ip6_frag_id 0; shinfo-frag_list NULL; if (fclone) { struct sk_buff *child skb 1; atomic_t *fclone_ref (atomic_t *) (child 1); skb-fclone SKB_FCLONE_ORIG; atomic_set(fclone_ref, 1); child-fclone SKB_FCLONE_UNAVAILABLE; } out: return skb; nodata: kmem_cache_free(cache, skb); skb NULL; goto out; } 那么alloc之后的图就是(图五) 图五其实和图二是一样的我们可以看到现在仅仅是分配了线束数据区域但是现在还没有数据一定要注意所以前面三个指针指在一起因为没有数据那么len和data_len的值就是0 : skb_reserve函数[cpp] view plain copy print?static inline void skb_reserve(struct sk_buff *skb, int len) { skb-data len; skb-tail len; } 代码其实很easy、就是移动两个指针而已~这个函数很重要是为“协议头”预留空间而且是尽最大的空间预留因为很多头都会有可选项那么我们不知道可选项是多大所以只能是按照最大的分配那么也说明了一点预留的空间headroom也就是不一定都能使用完的可能还有剩余的由上面的图也可以看出来这也是为什么需要这么多指针的问题那么这个函数直接导致head指针和tail、data指针分离入下面图六所示 图六注意headroom就是用来存储各个协议头的足够大的空间tailroom就可以认为是存储其他线性数据的空间。( 这里不要曲解协议头不是线性数据其实协议头也是所以当增加头的时候data指针向上移动当增加其他数据的时候tail指针向下移动 )。现在data和tail指向一起那么还是说明数据没有 : skb_put函数 ---- 用于操作线性数据区域(tailroom区域)的用户数据[cpp] view plain copy print?static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len) { unsigned char *tmp skb-tail; SKB_LINEAR_ASSERT(skb); skb-tail len; // 移动指针 skb-len len; // 数据空间增大len if (unlikely(skb-tailskb-end)) // 如果tail指针超过end指针了那么处理错误~ skb_over_panic(skb, len, current_text_addr()); return tmp; } 这函数其实就是从tailroom预留空间相当于是移动tail指针这样如果从上图图六开始看也就是tail开始向下移动和data分离了。。。一般来说这样做都是为了用户数据再次处理或者说为TCP/IP的负载预留空间看图七当使用skb_put时候由图六----图七 图七我们可以看到指针的移动data还是在headroom的下面中间的是用户数据预留的部分由skb_put得到tail表示数据结尾再看一下sk_buff中的len变成了数据长度ld : skb_push函数---------- 用于操作headroom区域的协议头[cpp] view plain copy print?static inline unsigned char *skb_push(struct sk_buff *skb, unsigned int len) { skb-data - len; // 向上移动指针 skb-len len; // 数据长度增加 if (unlikely(skb-dataskb-head)) // data指针超过head那么就是处理错误~ skb_under_panic(skb, len, current_text_addr()); return skb-data; } 和skb_put对应上面试操作用户数据的这里是操作协议头的其实就是data指针向上移动而已~注意len增大了哦~前面说了协议头也是属于数据如下面图所示由图七----图八 图八我们可以知道data向上移动了同时注意len变成ldlp了其中lp是这个增加的协议头的长度 : skb_pull函数----------- 其实这个函数才是与skb_push函数对应的函数因为这是去头函数而skb_push是增头函数所以这个函数一般用在解包的时候[cpp] view plain copy print?static inline unsigned char *skb_pull(struct sk_buff *skb, unsigned int len) { return unlikely(len skb-len) ? NULL : __skb_pull(skb, len); } static inline unsigned char *__pskb_pull(struct sk_buff *skb, unsigned int len) { if (len skb_headlen(skb) !__pskb_pull_tail(skb, len-skb_headlen(skb))) return NULL; skb-len - len; // 长度减小 return skb-data len; // 移动指针 } 其实就是data指针向下移动当前一个协议头被去掉headroom剩余的空间增大了看下图由图八----图九 图九虚线是data之前的指针位置现在移动到下面实线需注意len的长度减小减小的大小是剥去的头的大小四、最后我们从两条线整体分析一下1从应用层用户数据开始直到物理层发送出去 初始化的什么就不多说了和前面的差不多现在也加入用户数据已经在了如图七所示一样那么到了TCP层需要增加 TCP层的头 如图10所示 图10 需要注意的是这里是传输层那么传输层的结构u中的th代表的是tcp的头那么tcp指向tcp头OK同时注意 len长度l1 哦~~~ 再看到了IP层如图11 图11 至于需要解释什么就没什么了都是一样的~ 到链路层了如图12 图12 OK2第二个过程其实是第一个逆过程都差不多所以不多说了~五、最后看一下操作skb的两个函数pskb_copy和skb_copy前者仅仅是将sk_buff的结构体和线性数据copy过来对于非线性数据是引用原始的skb的数据的而后者是不仅将sk_buff和线性数据拷贝同时将非线性数据也copy了一份看下面就明白了这就在效率上就差了很多所以如果不想修改数据那么还是使用pskb_copy更好对于pskb_copy对于skb_copyOK 我觉得差不多了~~~~~结束~~~~~~~~~~~~~