看流星社区

 找回密码
 注册账号
查看: 1728|回复: 0

非常详细的详谈struct sk_buff

[复制链接]

该用户从未签到

发表于 2017-6-1 12:52:27 | 显示全部楼层 |阅读模式
非常详细的详谈struct sk_buff
排版太慢
难看的话可以下载WOR文档
专门详解struct_sk_buff

链接: http://pan.baidu.com/s/1gf8VNKR 密码: pquc

注:本文大部分内容来源于网络收集加上自己理解,如有不托之处,请指出,本人无任何版权!
全称socket buffers,简称skb,中文名字叫套接字缓存。它作为网络数据包的存放地点,使得协议栈中每个层都可以对数据进行操作,从而实现了数据包自底向上的传递。该结构维护一个收到的或者要发送的网络包。但其本身并不包含存放网络包的数据的存储区。存储区是另外单独分配的内存空间,但该结构说明了如何访问存储区空间,如何维护多个存储区空间以及存储网络包解析的成果。
所有的sk_buff是通过一个双向链表进行维护的。需要说明的是该双向链表中的一个元素是struct sk_buff_head类型。它相当于该双向链表的表头,其中有锁,链表元素个数等维护链表的相关信息。链表中其它的元素都是sk_buff类型。
这个结构被网络的不同层(MAC或者其他二层链路协议,三层的IP,四层的TCP或UDP等)使用,并且其中的成员变量在结构从一层向另一层传递时改变。 L4向L3传递前会添加一个L4的头部,同样,L3向L2传递前,会添加一个L3的头部。添加头部比在不同层之间拷贝数据的效率更高。由于在缓冲区的头部添加数据意味着要修改指向缓冲区的指针,这是个复杂的操作,所以内核提供了一个函数skb_reserve来完成这个功能。协议栈中的每一层在往下一层传递缓冲区前,第一件事就是调用skb_reserve在缓冲区的头部给协议头预留一定的空间。
skb_reserve同样被设备驱动使用来对齐接收到包的包头。如果缓冲区向上层协议传递,旧的协议层的头部信息就没什么用了。例如,L2的头部只有在网络驱动处理L2的协议时有用,L3是不会关心它的信息的。但是,内核并没有把L2的头部从缓冲区中删除,而是把有效荷载的指针指向L3的头部,这样做,可以节省CPU时间。

有些sk_buff成员变量的作用是方便查找或者是连接数据结构本身。内核可以把sk_buff组织成一个双向链表。当然,这个链表的结构要比常见的双向链表的结构复杂一点。就像任何一个双向链表一样,sk_buff中有两个指针next和prev,其中,next指向下一个节点,而prev指向上一个节点。在第一个节点前面会插入另一个结构sk_buff_head,这是一个辅助节点(作为sk_buff双向链表的头),它的定义如下:
struct sk_buff_head {
struct sk_buff -*next;
struct sk_buff -*prev;
__u32 qlen;
spinlock_t lock;
};
qlen代表链表元素的个数
lock用于防止对链表的并发访问
sk_buff和sk_buff_head的前两个元素是一样的:next和prev指针。这使得它们可以放到同一个链表中,尽管sk_buff_head要比sk_buff小得多。另外,相同的函数可以同样应用于sk_buff和sk_buff_head。
以下是<em>truct sk_buff 数据结构(源代码)</em>
<em>truct sk_buff - socket buffer</em>
struct sk_buff{
<em>/* These two members must be first. */</em>
struct sk_buff*next;//<em>@next:
Next buffer in list</em>链表中下一个缓存
struct
sk_buff*prev;<em>@prev: Previous buffer in
list</em>链表中前一个缓存
struct
sk_buff_head*list;<em>@list: List we are on</em>//当前链表
struct
sock*sk;<em>@sk: Socket we are owned by</em>//所属socket
struct timevalstamp;<em>@stamp:
Time we arrived</em>(分组)到达的时间
struct net_device*dev;<em>@dev:
Device we arrived on/are leaving by</em>分组离开的设备
struct net_device*input_dev;<em>@input_dev:
Device we arrived on</em>
struct net_device*real_dev;<em>@real_dev:
The real device we areusing</em>

union {
struct tcphdr*th;<em>@nh:
Network layer header</em>
struct udphdr*uh;
struct icmphdr*icmph;
struct
igmphdr*igmph;
struct iphdr*ipiph;
struct ipv6hdr*ipv6h;
unsigned char *raw;
} h;<em>@h:
Transport layer header</em>传输层头标(tcp,udp,icmp,igmp,spx,raw)
union {
struct
iphdr*iph;
struct
ipv6hdr*ipv6h;
struct
arphdr*arph;
unsigned char *raw;
} nh;<em>@nh: Network layer header</em>网络层头标(ip,ipv6,arp,ipx,raw)
union {
struct
ethhdr*ethernet;
unsigned char *raw;
}
mac;<em>@mac: Link layer header</em>链路层头标
struct dst_entry*dst;<em>@dst:
FIXME: Describe this field</em>目的地
struct
sec_path*sp;<em>every
layer. Put private vars here</em>

<em>* This is the control buffer. It is free to use for every</em>
<em>* layer. Please put your private variables there. If you</em>
<em>* want to keep them across layers you have to do a skb_clone()</em>
<em>* first. This is owned by whoever has the skb queued ATM. */</em>
char cb[40];<em>@cb:
Control buffer. Free for use by </em>控制缓存,后台使用
unsigned int len,<em>@len:
Length of actual data</em>实际数据长度
data_len,<em>@data_len:
Data length</em>
mac_len,<em>@mac_len: Length of link layer header</em>

csum;<em>@csum:
Checksum</em>校验和

unsigned char local_df,<em>@__unused:
Dead field, may be reused</em>
cloned,<em>@cloned: Head may be cloned (check
refcnt to be sure)</em>头标可被克隆
pkt_type,<em>@pkt_type: Packet class</em>分组等级分类
ip_summed;<em>@ip_summed: Driver fed us an IP checksum</em>驱动器将IP校验和反馈给我们
__u32priority;<em>@priority:
Packet queueing priority</em>分组排队优先级
unsigned short protocol,<em>@protocol:
Packet protocol from driver</em>驱动器的分组协议
security;<em>@security: Security level of packet</em>分组的安全级别
void (*destructor)(struct
sk_buff*skb);<em>@destructor:
Destruct function </em>
拆除功能
#ifdef CONFIG_NETFILTER
unsigned long nfmark;<em>@nfmark: Can be used for communication between hooks</em>
__u32nfcache;<em>@nfcache:
Cache info</em>络过滤器内在高速缓存信息
struct *nfct;<em>@nfct:
Associated connection, if any</em>相关联的连接
#ifdef CONFIG_NETFILTER_DEBUG
unsigned int nf_debug;<em>@nf_debug: Netfilter debugging</em>
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
struct
nf_bridge_info*nf_bridge;<em>@nf_bridge: Saved
data about a bridged frame - see br_netfilter.c</em>

#endif
#endif <em>/* CONFIG_NETFILTER */</em>
#if defined(CONFIG_HIPPI)
union {
__u32ifield;
}
private;<em>@private: Data which is private to the HIPPI
implementation</em>192<em>* </em>
#endif
#ifdef CONFIG_NET_SCHED
__u32tc_index;
<em>@tc_index: Traffic control index</em><em>*</em>流量控制索引
#ifdef CONFIG_NET_CLS_ACT
__u32tc_verd;
<em>/* traffic control verdict */</em>
__u32tc_classid;
<em>/* traffic control classid */</em>
#endif
#endif
<em>/* These elements must be at the end, see alloc_skb() for details. */</em>
unsigned int truesize;<em>@truesize:
Buffer size</em>缓存的真实尺寸
atomic_tusers;<em>@users:
User count - see {datagram,tcp}.c</em>用户计数
unsigned char *head,
<em>@head: Head of buffer</em>缓存头指针
*data,
<em>@data: Data head pointer</em>数据头指针
*tail,<em>@tail:
Tail pointer</em>尾指针
*end;<em>@end:
End pointer</em>结束指针
};<em></em>
#ifdef
__KERNEL__

<em>/*</em><em>* Handling routines are only of interest to the kernel</em>
<em>*/</em>
#include &lt;linux/slab.h&gt;
#include &lt;asm/system.h&gt;
extern void __kfree_skb(struct
sk_buff*skb);//skb使用计数减一,如果没有任何可参照的说明,释放skb
extern struct
sk_buff*alloc_skb(unsigned int
size, int priority);//此函数分配了一个新的skb,skb层提供此函数来初始化一些私有数据,同时作内存统计。返回的缓存没有头空间,有一个尺寸/字节尾空间;
extern void kfree_skbmem(struct
sk_buff*skb);
extern struct
sk_buff*skb_clone(struct
sk_buff*skb, int priority);//这个函数克隆一个skb。两份拷贝共享分组数据,但分别拥有自己的sk_buff结构。新拷贝不属于任何socket,参考计数为1。
extern struct
sk_buff*skb_copy(const struct
sk_buff*skb, int priority);//产生一个实实在在的skb拷贝,包含分组数据。但你想要修改分组数据时,需要这么做。新拷贝的参考计数为1。
extern struct
sk_buff*pskb_copy(struct
sk_buff*skb, int gfp_mask);
extern int pskb_expand_head(struct
sk_buff*skb,
int nhead, int ntail, int gfp_mask);
extern struct
sk_buff*skb_realloc_headroom(struct
sk_buff*skb,
unsigned int headroom);
extern struct
sk_buff*skb_copy_expand(const struct
sk_buff*skb,
int newheadroom, int newtailroom,
int priority);//产生一个skb拷贝,包含分组数据。另外,新skb还拥有一个/new_headroom/字节大小的头空间,和一个/new_tailroom/字节大小的尾空间。
extern struct
sk_buff* skb_pad(struct
sk_buff*skb, int pad);
#define
dev_kfree_skb(a) kfree_skb(a)
extern void skb_over_panic(struct
sk_buff*skb, int
len,
void *here);
extern void skb_under_panic(struct
sk_buff*skb, int
len,
void *here);
<em>/* Internal */</em>
#define skb_shinfo(SKB) ((struct
skb_shared_info*)((SKB)-&gt;end))
<em>/**</em><em>* skb_queue_empty - check if a queue is empty</em>
<em>* @list: queue head</em>
<em>*</em> <em>* Returns true if the queue is empty, false otherwise.</em>
<em>*/</em>
sk_buff-&gt;sk
这是一个指向拥有这个sk_buff的sock结构的指针。这个指针在网络包由本机发出或者由本机进程接收时有效,因为插口相关的信息被L4(TCP或 UDP)或者用户空间程序使用。如果sk_buff只在转发中使用(这意味着,源地址和目的地址都不是本机地址),这个指针是NULL
sk_buff-&gt;len
表示当前协议数据包的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。
sk_buff-&gt;data_len
和len不同,data_len只计算分片中数据的长度
sk_buff-&gt;mac_len
这是mac头的长度
sk_buff-&gt;users
这是一个引用计数,用于计算有多少实体引用了这个sk_buff缓冲区。它的主要用途是防止释放sk_buff后,还有其他实体引用这个sk_buff。因此,每个引用这个缓冲区的实体都必须在适当的时候增加或减小这个变量。这个计数器只保护sk_buff结构本身,而缓冲区的数据部分由类&#20284;的计数器 (dataref)来保护.有时可以用atomic_inc和atomic_dec函数来直接增加或减小users,但是,通常还是使用函数 skb_get和kfree_skb来操作这个变量。
sk_buff-&gt;truesize
这是缓冲区的总长度,包括sk_buff结构和数据部分。如果申请一个len字节的缓冲区,alloc_skb函数会把它初始化成len&#43;sizeof(sk_buff)。当skb-&gt;len变化时,这个变量也会变化。
sk_buff-&gt;head
sk_buff-&gt;data
sk_buff-&gt;tail
sk_buff-&gt;end
它们表示缓冲区和数据部分的边界。在每一层申请缓冲区时,它会分配比协议头或协议数据大的空间。head和end指向缓冲区的头部和尾部,而data和 tail指向实际数据的头部和尾部。每一层会在head和data之间填充协议头,或者在tail和end之间添加新的协议数据。数据部分会在尾部包含一个附加的头部。


void (*destructor)(struct sk_buff *skb)
这个函数指针可以初始化成一个在缓冲区释放时完成某些动作的函数。如果缓冲区不属于一个socket,这个函数指针通常是不会被赋&#20540;的。如果缓冲区属于一个socket,这个函数指针会被赋&#20540;为sock_rfree或sock_wfree(分别由skb_set_owner_r或 skb_set_owner_w函数初始化)。这两个sock_xxx函数用于更新socket的队列中的内存容量。
sk_buff-&gt;tstamp
这个变量只对接收到的包有意义。它代表包接收时的时间戳,或者有时代表包准备发出时的时间戳。它在netif_rx里面由函数net_timestamp设置,而netif_rx是设备驱动收到一个包后调用的函数。
sk_buff-&gt;dev
这个变量的类型是net_device,net_device它代表一个网络设备。dev的作用与这个包是准备发出的包还是刚接收的包有关。当收到一个包时,设备驱动会把sk_buff的dev指针指向收到这个包的网络设备;当一个包被发送时,这个变量代表将要发送这个包的设备。在发送网络包时设置这个&#20540;的代码要比接收网络包时设置这个&#20540;的代码复杂。有些网络功能可以把多个网络设备组成一个虚拟的网络设备(也就是说,这些设备没有和物理设备直接关联),并由一个虚拟网络设备驱动管理。当虚拟设备被使用时,dev指针指向虚拟设备的net_device结构。而虚拟设备驱动会在一组设备中选择一个设备并把dev指针修改为这个设备的net_device结构。因此,在某些情况下,指向传输设备的指针会在包处理过程中被改变。
sk_buff-&gt;input_dev
这是收到包的网络设备的指针。如果包是本地生成的,这个&#20540;为NULL。对以太网设备来说,这个&#20540;由eth_type_trans初始化,它主要被流量控制代码使用。
sk_buff-&gt;h
sk_buff-&gt;nh
sk_buff-&gt;mac
这些是指向TCP/IP各层协议头的指针:h指向L4(传输层),nh指向L3(网络层),mac指向L2(数据链路层)。每个指针的类型都是一个联合,包含多个数据结构,每一个数据结构都表示内核在这一层可以解析的协议。例如,h是一个包含内核所能解析的L4协议的数据结构的联合。每一个联合都有一个 raw变量用于初始化,后续的访问都是通过协议相关的变量进行的。
当接收一个包时,处理n层协议头的函数从其下层(n-1层)收到一个缓冲区,它的skb-&gt;data指向n层协议的头。处理n层协议的函数把本层的指针(例如,L3对应的是skb-&gt;nh指针)初始化为skb-&gt;data,因为这个指针(data指针)的&#20540;会在处理下一层协议时改变 (skb-&gt;data将被初始化成缓冲区里的其他地址)。在处理n层协议的函数结束时,在把包传递给n&#43;1层的处理函数前,它会把skb-&gt; data指针指向n层协议头的末尾,这正好是n&#43;1层协议的协议头。
当网卡驱动程序收到一个UDP数据报后,它创建一个结构体struct sk_buff,确保sk_buff-&gt;data成员指向的空间足够存放收到的数据(对于数据报分片的情况,因为比较复杂,我们暂时忽略,我们假设一次收到的是一个完整的UDP数据报)。把收到的数据全部拷贝到sk_buff-&gt;data指向的空间,然后,把skb-&gt;mac.raw指向data,此时,数据报的开始位置是一个以太网头,所以skb-&gt;mac.raw指向链路层的以太网头。然后通过调用skb_pull剥掉以太网头,所谓剥掉以太网头,只是把data加上sizeof(struct
ethhdr),同时len减去这个&#20540;,这样,在逻辑上,skb已经不包含以太网头了,但通过skb-&gt;mac.raw还能找到它。这就是我们通常所说的,IP数据报被收到后,在链路层被剥去以太网头。
sk_buff-&gt;dst
这个变量在路由子系统中使用
sk_buff-&gt;sp
这个变量被IPSec协议用于跟踪传输的信息
sk_buff-&gt;cb[48]
这是一个“control buffer”,或者说是一个私有信息的存储空间,由每一层自己维护并使用。它在分配sk_buff结构时分配(它目前的大小是48字节,已经足够为每一层存储必要的私有信息了)。在每一层中,访问这个变量的代码通常用宏实现以增强代码的可读性。例如,TCP用这个变量存储tcp_skb_cb结构。
下面这个宏被TCP代码用来访问cb变量。在这个宏里面,有一个简单的类型转换:
#define TCP_SKB_CB(__skb) ((struct tcp_skb_cb *)&amp;((__skb)-&gt;cb[0]))

下面的例子是TCP子系统在收到一个分段时填充相关数据结构的代码:
int tcp_v4_rcv(struct sk_buff *skb)
{
...
th = skb-&gt;h.th;
TCP_SKB_CB(skb)-&gt;seq = ntohl(th-&gt;seq);
TCP_SKB_CB(skb)-&gt;end_seq = (TCP_SKB_CB(skb)-&gt;seq &#43; th-&gt;syn &#43; th-&gt;fin &#43;
skb-&gt;len - th-&gt;doff * 4);
TCP_SKB_CB(skb)-&gt;ack_seq = ntohl(th-&gt;ack_seq);
TCP_SKB_CB(skb)-&gt;when = 0;
TCP_SKB_CB(skb)-&gt;flags = skb-&gt;nh.iph-&gt;tos;
TCP_SKB_CB(skb)-&gt;sacked = 0;
...
}
如果想要了解cb中的参数是如何被取出的,可以查看net/ipv4/tcp_output.c中的tcp_transmit_skb函数。这个函数被TCP用于向IP层发送一个分段。
sk_buff-&gt;csum
sk_buff-&gt;ip_summed
表示校验和以及相关状态标记
sk_buff-&gt;cloned
一个布尔标记,当被设置时,表示这个结构是另一个sk_buff的克隆
sk_buff-&gt;pkt_type
这个变量表示帧的类型,分类是由L2的目的地址来决定的。这个&#20540;在网卡驱动程序中由函数eth_type_trans通过判断目的以太网地址来确定。如果目的地址是FF:FF:FF:FF:FF:FF,则为广播地址,pkt_type = PACKET_BROADCAST;如果最高位为1,则为组播地址,pkt_type = PACKET_MULTICAST;如果目的mac地址跟本机mac地址不相等,则不是发给本机的数据报,pkt_type = PACKET_OTHERHOST;否则就是缺省&#20540ACKET_HOST。
/* Packet types */
#define PACKET_HOST 0 /* To us */
#define PACKET_BROADCAST 1 /* To all */
#define PACKET_MULTICAST 2 /* To group */
#define PACKET_OTHERHOST 3 /* To someone else */
#define PACKET_OUTGOING 4 /* Outgoing of any type */
sk_buff-&gt;priority
这个变量描述发送或转发包的QoS类别。如果包是本地生成的,socket层会设置priority变量。如果包是将要被转发的, rt_tos2priority函数会根据ip头中的Tos域来计算赋给这个变量的&#20540;。这个变量的&#20540;与DSCP(DiffServ CodePoint)没有任何关系。
sk_buff-&gt;protocol
这个变量是高层协议从二层设备的角度所看到的协议。典型的协议包括IP,IPV6和ARP。完整的列表在 include/linux/if_ether.h中。由于每个协议都有自己的协议处理函数来处理接收到的包,因此,这个域被设备驱动用于通知上层调用哪个协议处理函数。每个网络驱动都调用netif_rx来通知上层网络协议的协议处理函数,因此protocol变量必须在这些协议处理函数调用之前初始化。
linux内核是模块化的,你可以选择包含或者删除某些功能。因此,sk_buff结构里面的一些成员变量只有在内核选择支持某些功能时才有效,比如防火墙(netfilter)或者qos:
__u32 nfctinfo
...
#ifdef CONFIG_NETFILTER
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
__u32 nfmark;
#endif /* CONFIG_NETFILTER */
这些变量被netfilter使用(防火墙代码),内核编译选项是“Device Drivers-&gt;Networking support-&gt; Networking options-&gt; Network packet filtering”和两个子选项“Network packet filtering debugging”和“Bridged IP/ARP packets filtering”
#ifdef CONFIG_NET_SCHED
__u16 tc_index;
#ifdef CONFIG_NET_CLS_ACT
__u16 tc_verd;
#endif
#endif
这两个变量被流量控制代码使用。tc_index只有在编译时定义了CONFIG_NET_SCHED符号才有效;tc_verd只有在编译时定义了CONFIG_NET_CLS_ACT符号才有效。这两个符号可以通过选择特定的编译选项来定义:
Networking ---&gt;
Networking options ---&gt;
QoS and/or fair queueing ---&gt;
[*] QoS and/or fair queueing
[*] Actions

QoS选项不能被编译成内核模块。原因就是,内核编译之后,由某个选项所控制的数据结构是不能动态变化的。一般来说,如果某个选项会修改内核数据结构(比如说,在sk_buff里面增加一个项tc_index),那么,包含这个选项的组件就不能被编译成内核模块。
你可能经常需要查找是哪个make menuconfig编译选项或者变种定义了某个#ifdef标记,以便理解内核中包含的某段代码。在2.6内核中,最快的,查找它们之间关联关系的方法,就是查找分布在内核源代码树中的kconfig文件中是否定义了相应的符号(每个目录都有一个这样的文件)。在2.4内核中,你需要查看 Documentation/Configure.help文件。
Quality of Service(QoS)服务质量

该结构维护一个收到的或者要发送的网络包。但其本身并不包含存放网络包的数据的存储区。存储区是另外单独分配的内存空间,但该结构说明了如何访问存储区空间,如何维护多个存储区空间以及存储网络包解析的成果。
所有的sk_buff是通过一个双向链表进行维护的。需要说明的是该双向链表中的一个元素是struct sk_buff_head类型。它相当于该双向链表的表头,其中有锁,链表元素个数等维护链表的相关信息。链表中其它的元素都是sk_buff类型。
链表结构图如下:
而对于每个sk_buff而言,如前所述,其本身并不维护网络包的存储区。该结构跟存储区的关系如下图所示:
图的左边表示sk_buff结构,右侧表示网络包的存储区。可以看到,sk_buff中有四个指针指向存储区。其中head一定指向存储区的开头,end一定指向存储区的结尾。data指向实际内容的开头,tail指向实际内容的结尾。这样做有两个原因,一是在分配空间的时候我们尚不知道具体需要多大的空间,只能按照最大可能空间来分配;二是为了满足字节对齐的需要。
图中Data部分的内容包括网络包的所有内容。对于输入包而言,其就是从当前层向上的所有层的头和最后的负载,每解析掉一层的头,该协议的协议头对应的数据就不再继续处理,所以data指针在每层会逐渐增大。对于输出包而言,其每向下传输一层,都会添加一层的头,所以sk_buff的data指针也会不断减小。
对于输入包而言,存储区的内容是不变的。但我们在分析包时,在每层都会剥除该层的头。为了保留我们的剥除结果,结构中会有三个指针分别指向以太,网络和传输三层的协议头。这三个指针依次分别是union {…} mac,union {…} nh和union{…} h。三个成员都是联合,其中的内容是该层可能的协议类型的指针。
还有一组成员表示存储区中数据的长度。len表示存储区的数据长度和分片长度之和。data_len表示分片长度。mac_len表示mac头的长度。truesize表示存储区总长度(即end-head)和sk_buff本身长度之和。
下面的内容是关于如何维护存储区。
alloc_skb和dev_alloc_skb用来分配sk_buff和存储区,kfree_skb和dev_kfree_skb则释放sk_buff和其对应的存储区。刚初始化完的存储区,head,data,tail都指向存储区的开头,end指向结尾。下面这些函数是修改data和tail指针的指向。
skb_reserve是在存储区的开头保留一段空间。其有两个作用,对于接收到的报文而言,可以保持字节对齐;对于发送的报文而言,可以保留空间用于存放各层的协议头。毕竟我们在高层协议就会分配该存储区,所以需要预留下层的协议头的空间。该函数的作用会导致data和tail两个指针都下移,在head和它们之间留出空间。
skb_put则是将tail下移,即增加了真正空间的长度。
skb_push是将data上移,也增加了真正空间的长度。
skb_pull将data下移,减少了真正空间的长度。

下面是sk_buff的其它成员:
struct sock *sk:表示该网络包所属的socket。对于该主机发送的和接收的网络包,自然对应着一个socket套接字。对于转发的网络包,则该成员为NULL。
atomic_t users:表示该sk_buff结构实例被引用的计数。这个是结构本身的引用计数,而不是其对应的存储区的引用计数。
void (*destructor)():析构函数。
struct timeval stamp:对于接受包,表示该包接收到的时间。
struct net_device *dev:接收或者发送包的网卡接口。
struct net_device *input_dev:接收到包的网卡接口。
struct net_device *real_dev:在某些机器中,多个物理网卡接口可以被看作一个虚拟设备。这中情况下real_dev表示物理结构。
char cb[40]:一个用于对该网络包进行处理的通用缓冲区。可以存放一些在不同层之间传输的数据。
unsigned int csum,ipsum:校验和。
unsigned char cloned:
unsigned char pkt_type:包类型,可以是单发给该主机的包,广播包,多播包,转发包等。
unsigned short protocol:表示以太层的上层协议,可以是IP,IPv6和ARP。

下面是关于skb_shared_info结构的。当我们使用alloc_skb分配空间时,会在存储区的后面紧接着一个该结构的实例。如下图所示:

其中存储了关于网络块的附加信息。不知为何不也放在sk_buff中。其中维护了存储块的引用计数,IP分片数据的信息。

对于sk_buff和存储块,有时是需要拷贝的。分为三种情况,一是只拷贝sk_buff,存储块复用,此时使用skb_clone函数;二是拷贝sk_buff和存储块的主体,但存储块后的skb_shared_info中frags数组指向的分片数据不拷贝,这使用pskb_copy函数;三是上述的全都拷贝,使用skb_copy函数。在不同情况下不同使用。


sk_buff的实现方式是双向链表。不仅每个节点有next指针指向下一元素,prev指针指向前一元素,又包含一个指向头结点的指针;同时链表的头结点比较特殊,包括了链表长度和一个自旋锁用于同步,因此形成了如下的结构:

关于布局,sk_buff实现的另一个重要功能就是:当数据包从最底层的数据链路层向上传递到传输层时,进行一些处理。例如,IP层把数据包交给传输层时,就需要把IP头去掉。下面的图演示了一个UDP的数据包在发送时添加UDP报头和IP报头的过程:

这个功能还需要另外三个成员变量,分别指向了传输层的头部(h),网络层的头部(nh=network header),数据链路层的头部(mac):
union {...} h
union {...} nh
union {...} mac
在2.6.30内核中这三个字段改为了:
sk_buff_data_t transport_header;
sk_buff_data_t network_header;
sk_buff_data_t mac_header;
下面的例子表示了在经过mac层的传输过程中data字段的变化,被mac层接收之后,data指向了IP包头开始的地方(请回忆相关的网络知识):

管理函数
skb_reserve, skb_put, skb_push, and skb_pull等函数可以用于缓存的空间管理。例如,在数据包从上层向下传输时,因为要添加下层协议的报头,这个时候就可以根据最大可能添加的长度预留空间,这样就无需每经过一层分配一次空间,降低了系统开销。还有另外一些以skb_queue开头的函数,表示对双向链表操作的函数。
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-3-29 03:08

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表