-
sk_buff
结构可能是
linux
网络代码中最重要的数据结构,它表示接收或发送数据包的包头信息。它在
中定义,并包含很多成员变量供
网络代码中的各子系统使用。
这个
结构在
linux
内核的发展过程中改动过很多次,或者是增加
新的选项,或者是重新组织已存在的成员
变量以使得成员变量的布局更加清晰。它的成员
变量可以大致分为以下几类:
?
Layout
布局
?
General
通用
?
Feature-
specific
功能相关
?
Management
functions
管理函数
这个
结构被不同的网络层(
MAC
或者其他二层链路协议,三层的<
/p>
IP
,四层的
TCP
或
UDP
等)使用,并
且其中的成
员变量在结构从一层向另一层传递时改变。
L4
向
L3
传递前会添加一个
L4
的头部,同样,
L3
向
L2
传递前,会添加一个
L3
的头部。添加头部比在不
同层之间拷贝数据的效率更高。由于在缓冲区的头
部添加数据意味着要修改指向缓冲区的
指针,这是个复杂的操作,所以内核提供了一个函数
skb_reserve
(在后面的章节中描述)来完成这个功能。协议栈中的每一层在往下一层传递缓冲区前,第一件事就 是调
用
skb_reserve
在缓冲
区的头部给协议头预留一定的空间。
skb_reserv
e
同样被设备驱动使用来对齐接收到包的包头。如果缓冲区向上层协议传递,旧的协议层
的头
部信息就没什么用了。
例如,
L2
的头部只有在网络驱动处理
L2
的协议
时有用,
L3
是不会关心它的信息的。
但是,内核并没有把
L2
的头部从缓冲区中删除,而是把有效荷
载的指针指向
L3
的头部,这样做,可以节
省
CPU
时间。
1.
网络参数和内核数据结构
<
/p>
就像你在浏览
TCP/IP
规范或者配置
内核时所看到的一样,网络代码提供了很多有用的功能,但是这些功
能并不是必须的,比
如说,防火墙,多播,还有其他一些功能。大部分的功能都需要在内核数据结构中添
加自
己的成员变量。因此,
sk_buff
里面包含了很多像
#ifdef
这样的预编译指令。例如,在
s
k_buff
结构
的最后,你可以找到:
struct sk_buff {
... ... ...
#ifdef
CONFIG_NET_SCHED
_ _u32
tc_index;
#ifdef CONFIG_NET_CLS_ACT
_ _u32 tc_verd;
_ _u32 tc_classid;
#endif
#endif
}
它表明,
tc_index
只有在编译时定义了
CONFIG_NET_SCHED
符号才有效。这个符号可以通过选择特
定的编译选项来定义(例如:
options
QoS
and/or
fair
queueing
)。这些编译选项可以由管理员通过
make
config
来选择,或者通过一些自动安装工具
来选择。
p>
前面的例子有两个嵌套的选项:
CONFIG_NET_CLS_ACT
(包分类器)
只有在选择支持
“QoS
and/or fair
queueing”
时才能生效。
顺便提一下,
QoS
选项不能被编译成
内核模块。原因就是,内核编译之后,由某个选项所控制的数据结构
是不能动态变化的。
一般来说,如果某个选项会修改内核数据结构(比如说,在
sk_buff
里面增加一个项
tc_index
),那么,包含
这个选项的组件就不能被编译成内核模块。
你可能经常需要查找是哪个
make config
编译选项或者变种定义了某个
#ifdef
标记,
以便理解内核中包
含的某段代码。在
2.6
内核中,最快的,查找它们之间关联关系的方法,
就是查
找分布在内核源代码树中
的
kconfig
文件中是否定义了相应的符号(每个目录都有一个这样的文件)。在
2.4
内核中,你需要查看
Documentat
ion/
文件。
2. Layout Fields
有些
sk_buff
成员变量的作用是方便查找或者是连接数据
结构本身。
内核可以把
sk_buff
组织成一个双向
链表。当然,这个链表的结构要比常见的双向链表的结构复杂一点。
p>
就像任何一个双向链表一样,
sk_buff
中有两个指针
next
和
prev
,其中,
n
ext
指向下一个节点,而
pre
v
指向上一个节点。但是,这个链表还有另一个需求:每个
sk
_buff
结构都必须能够很快找到链表头
节点。为了满足这个
需求,在第一个节点前面会插入另一个结构
sk_buff_head
< br>,这是一个辅助节点,它
的定义如下:
struct sk_buff_head {
/* These two members must be first.
*/ struct sk_buff *
next;
struct sk_buff * prev;
_ _u32 qlen;
spinlock_t lock;
};
qlen
代表链表元素的个数
。
lock
用于防止对链表的并发访问。
sk_buff
和
sk_buff_head
的前两个元素是一样的:
next
和
prev
指针。
这使得它们可以放到同一个链表
中,尽管
sk_b
uff_head
要比
sk_buff
小得多。另外,相同的函数可以同样应用于
sk_buff
和<
/p>
sk_buff_head
。
为了使这个数据结构更灵活,
每个
sk_buf
f
结构都包含一个指向
sk_buff_head
的指针。
这个指针的名字
是
list
。图
1
会帮助你理解它们之间
的关系。
Figure 1. List of
sk_buff elements
其他有趣的成员变量如下:
struct sock *sk
这是一个指向拥有这个
p>
sk_buff
的
sock
结构的指针。这个指针在网络包由本机发出或者由本机进程接收
时有效,
因为插口相关的信息被
L4(TCP
或
UDP)
或者用户空间程序使用。
如果
sk_buff
只在转发中使用
(
这
意味着,源地址和目的地址都不是本机地址
)<
/p>
,这个指针是
NULL
。
unsigned int len
这是缓冲区中数据部分的长度。它包括主缓冲区中的数据长度
(data<
/p>
指针指向它
)
和分片中的数据长度。它<
/p>
的值在缓冲区从一个层向另一个层传递时改变,因为往上层传递,旧的头部就没有用了,而
往下层传递,
需要添加本层的头部。
len
同样包含了协议头的长度。
unsigned int data_len
和
len
不同,
data_len
< br>只计算分片中数据的长度。
unsigned int mac_len
这是
mac
头的长度。
atomic_t users
这是一个引用计数,用于计算
有多少实体引用了这个
sk_buff
缓冲区。它的主要用途是
防止释放
sk_buff
后,还有其他实体引用这个
sk_buff
。因此,每个引用这个缓冲区的实体都必须在适当的时候增
加或减小
这个变量。这个计数器只保护
sk_buff
结构本身,而缓冲区的数据部分由类似的计数器
(dataref)
p>
来保护
.
有时可以用
atomic_inc
和
atomic_dec
函数来直接增加或减小
users
,
但是,
通常还是使用函数
skb_get
和
kfree_skb
来操作这个变量。
unsigned int
truesize
这是缓冲区的总长度,包括
sk_buff
结构和数据部分。如果申请一个
len
字节的缓冲区,
alloc_skb
函数
会把它初始化成
len+sizeof(sk_buff)
。
struct sk_buff
*alloc_skb(unsigned int size,int gfp_mask)
{
... ... ...
skb->truesize = size +
sizeof(struct sk_buff);
... ...
...
}
当
skb->len
p>
变化时,这个变量也会变化。
unsigned char *head
unsigned
char *end
unsigned char *data
unsigned char *tail
它们表示缓冲区和
数据部分的边界。
在每一层申请缓冲区时,
它会分配比协议头或
协议数据大的空间。
head
和
end
指向缓冲区的头部和尾部,
而
data
和
tail
指向实际数据的头部和尾部
,
参见图
2
。
每一层会在
head
和
data
之间填充协议头,或者在
tail
和
end
之间添加新的协议数据。图
2
中右边数据部分会在尾部包含
一个附加的头部。
Figure 2. head/end versus
data/tail pointers
void (*destructor)(...)
这个函数指针可以初始化成一个在缓冲区释放时完成某些动作的函数。
如果缓冲区不属于一个
socket
,
这
个函数指针通常是不会被赋值的。如果缓冲区属于一个
soc
ket
,
这个函数指针会被赋值为<
/p>
sock_rfree
或
sock_wf
ree(
分别由
skb_set_owner_r
或
skb_set_owner_w
函数初始化
p>
)
。这两个
sock_xxx
函
数用于更新
socket
的队列中的内存容量。
3. General
Fields
本节描述
sk_buff
的主要成员变量,这些成员变量与特定的内核功能无关:
struct timeval stamp
这个变量只对接
收到的包有意义。它代表包接收时的时间戳,或者有时代表包准备发出时的时间戳。它在
netif_rx
里面由函数
net_timestamp
p>
设置,而
netif_rx
是设备驱动收到
一个包后调用的函数。
struct net_device
*dev
这个变量的类型是
net_device
,
net_device
它代表一个网络设备。<
/p>
dev
的作用与这个包是准备发出的包
还
是刚接收的包有关。
当收到一个包时,
设备驱动会把
sk_buff
的
dev
指针指向收到这个包的设备的数据
结构,就像下面的
vorte
x_rx
里的一段代码所做的一样,这个函数属于
3c59x<
/p>
系列以太网卡驱动,用于
接收一个帧。
(
drivers/net/3c59x.c)
:
static int vortex_rx(struct
net_device *dev)
{
... ... ...
skb->dev = dev;
... ... ...
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb); /* Pass the
packet to the higher layer
*/
... ... ...
} <
/p>
当一个包被发送时,这个变量代表将要发送这个包的设备。在发送网络包时设置这个值的代
码
要比接收网络包时设置这个值的代码复杂。有些网络功能可以把多个网络设备组成一个
虚拟的
网络设备
(
也就是说,这些设备
没有和物理设备直接关联
)
,并由一个虚拟网络设备驱动管理。
当虚拟设备被使用时,
dev
指针指向
虚拟设备的
net_device
结构。而虚拟设备驱动会在一
组
设备中选择一个设备并把
dev
指针
修改为这个设备的
net_device
结构。
因此,
在某些情况下,
指向传输设备的指针会在包处理过程中被改变。
struct
net_device *input_dev
这是收到包的网络设备的指针。如果包
是本地生成的,这个值为
NULL
。对以太网设备来说,这个值
由
eth_type_trans
初始化
,
它主要被流量控制代码使用。
struct net_device *real_dev
这个变量只对虚拟设备有意义,它代表与虚拟设备关联的真实设备。例如,
Bonding
和
VLAN
设备都使用
它来指向收到包的真实设备。
union {...} h
union {...} nh
union {...}
mac
这些是指向
TCP/IP
各层
协议头的指针:
h
指向
L4
,
nh
指向
L3
,
mac
指向
L2
p>
。每个指针的
类型都是一个联合,
包含多个
数据结构,
每一个数据结构都表示内核在这一层可以解析的协议。
例如,
h
是一个包含内核所能解析的
L4
协议的数据结构的联合。每一个联合都有一个
raw
变
量用于初始化,后续的访问都是通过协议相关的变量进行的。
当接收一个包时,处理
< br>n
层协议头的函数从
n-1
层收
到一个缓冲区,它的
skb->data
指向
< br>n
层协议的头。
处理
n
层协议的函数把本层的指针
(
例如,
L3
对应的是
skb->nh
指针
)
初始化为
skb->dat
a
,
因为这个指针的值会在处理下一层协议时改变
(
skb->data
将被初始化成缓冲区
里的其他地址
)
。在处理
n
层协议的函数结束时,在把包传递给
n+1
层的处理函数前,它会把
skb->data
指针指向
n
层协议头的末尾,这正好是
n+1<
/p>
层协议的协议头
(
参见图
3)
。
< br>发送包的过程与此相反,但是由于要为每一层添加新的协议头,这个过程要比接收包的过程复
杂。
Figure
3. Header's pointer initializations while moving
from layer
two to layer
three
struct dst_entry dst
这个变量在路由子系统中使用。
char cb[40]
这是一个
“control buffer”
< br>,或者说是一个私有信息的存储空间,由每一层自己维护并使用。它在分配
sk_
buff
结构时分配
(
它目前的大小是
40
字节,已经足够为每一层存储必要的私有信息了
)
。在每一层中,
访问这个变量的代码通常用宏实
现以增强代码的可读性。例如,
TCP
用这个变量存储
tcp_skb_cb
结构,
这个结构在
include/net/tcp.h
中定义:
struct tcp_skb_cb {
... ... ...
_
_u32
seq; /*
Starting
sequence
number
*/
_
_u32
end_seq; /*
SEQ
+
FIN
+
SYN
+
datalen*/
_ _u32 when; /* used to compute
rtt's */
_ _u8
flags; /* TCP header
flags.
*/
... ... ...
};
下面这个宏被
TCP
代码用来访问
cb
变量。在这个宏里面,有一个简单的类型转换:
#define TCP_SKB_CB(_ _skb) ((struct
tcp_skb_cb *)&((_
_skb)->cb[0]))
下面的例子是
TCP
子系统在收到一个分段时填充相
关数据结构的代码:
int
tcp_v4_rcv(struct sk_buff *skb)
{
... ... ...
th = skb->;
TCP_SKB_CB(skb)->seq = ntohl(th->seq);
TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq +
th->syn + th->fin +
skb->len - th->doff * 4);
TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
TCP_SKB_CB(skb)->when = 0;
TCP_SKB_CB(skb)->flags =
skb->->tos;
TCP_SKB_CB(skb)->sacked = 0;
... ... ...
}
如果想要了解
< br>cb
中的参数是如何被取出的,可以查看
net/ipv
4/tcp_output.c
中的
tcp_transmit
_skb
函数。这个函数被
TCP
用于
向
IP
层发送一个分段。
unsigned int csum
unsigned
char ip_summed
表示校验和以及相关状态标记。
unsigned char cloned
一个布尔标记,
当被设置时,表示这个结构是另一个
sk_buff
的克隆。在
“
克隆和拷贝缓冲区
”
一节中有描
述。
unsigned char pkt_type
这个变量表
示帧的类型,
分类是由
L2
的目的地址
来决定的。
可能的取值都在
include/linux/if
_packet.h
中定义。对以太网设备来说,这个变量由
e
th_type_trans
函数初始化。
类型的可能取值如下:
PACKET_HOST
包的目的地址与收到它的网络设备的
L2
地址相等。换句话说,这个包是发给本机的。
.PACKET_MULTICAST
包的目的地址是一个多播地址,而这个多播地址是收到这个包的网络设备所注册的多播地址。<
/p>
PACKET_BROADCAST
包的目的地址是一个广播地址,而这个广播地址也是收到这个包的网络设备的广播地址。
PACKET_OTHERHOST
包的目的地址与收到它的
网络设备的地址完全不同
(
不管是单播,多播还是广播
)
,因此,如果
本机的转发功能没有启用,这个
包会被丢弃。
PACKET_OUTGOING
这个包将被发出。用到这个
标记的功能包括
Decnet
协议,或者是为每个网络
tap
都复制一份
发出包的函数。
PACKET_LOOPBACK
这个包发向
loopback
设备。由于有这个标
记,在处理
loopback
设备时,内核可以跳过一些
真实设备才需要的操作。
PACKET_FASTROUTE
这个包由快速路由代码查
找路由。快速路由功能在
2.6
内核中已经去掉了。
_ _u32
priority
这个变量描述发送或转发包的
QoS
类别。如果包是本地生成的,
socket
层
会设置
priority
变量。如果包
是将要被转发的,
rt_tos2priority
函数会根据
ip
头中的
Tos
域来计算赋给这个变量的值。这个变量的值
与
DSCP(D
iffServ CodePoint)
没有任何关系。
unsigned short protocol
这个变量
是高层协议从二层设备的角度所看到的协议。典型的协议包括
IP
,
IPV6
和
ARP
。完整的列表在
include/linux/if_ether.h
中。由于每个协议都有自己的协议处理函数来处理接收到的包,因此,这个域
被设备驱动用于通知上层调用哪个协议处理函数。
每个网络驱动都调用
netif_rx
来通知上层网络协议的协
议处理函
数,因此
protocol
变量必须在这些协议处理函数调用之
前初始化。
unsigned
short security
这是包的安全级别。这个变量最初由
< br>IPSec
子系统使用,但现在已经作废了。
4. Feature-Specific Fields
l
inux
内核是模块化的,
你可以选择包含或者删除某些功能。
因此,
sk_buff
结构里面的一些
成员变量只有
在内核选择支持某些功能时才有效,比如防火墙
(
netfilter)
或者
qos
:<
/p>
unsigned long nfmark
_ _u32 nfcache
_ _u32
nfctinfo
struct nf_conntrack *nfct
unsigned int nfdebug
struct
nf_bridge_info *nf_bridge
这些变量被
< br>netfilter
使用
(
防火
墙代码
)
,内核编译选项是
“Devi
c
e Drivers->Networking support->
Networking options-
> Network
packet
filtering”
和两个子选项
“Network
packet filtering
debugging”
和
“Bridged IP/ARP packets filtering”
union {...} private
这个联合结构被高性能并行接口
(HIPPI)
使用。
相应的内核编译选项是
“Device
->Drivers ->Networking
support
->Network device support -
>HIPPI driver
support”
_ _u32 tc_index
_ _u32 tc_verd
_ _u32
tc_classid
这些变量被流量控制代码使用,内核编译选项是
“Device
Drivers
->Networking->support
->Networking options -
>QoS
and/or fair queueing”
和它的子选项
“
Packetclassifier API”
struct sec_path *sp
这个变量被
IPSec
协议用于跟踪传输的信息。
5. Management Functions
有很多函
数,通常都比较短小而且简单,内核用这些函数操作
sk_buff
的成员变量或者
sk_buff
链表。图
4
会帮助我们理解其中几个重要的函数。我们首先来看分配和释放缓冲区的函数
,然后是一些通
过移动指针在缓冲区的头部或尾部预留空间的函数。
如果你看过
include/
linux/skbuff.h
和
net/core/skbu
ff.c
中的函数,
你会发现,
基本上
每个函数都有两
个版本,名字分别是
do_something
和
__do_something
。通
常第一种函数是一个包装函数,它会在第
二种函数的基础上增加合法性检查或者锁。一般
来说,类似
__do_something
的函数不能被直接调
用
(
除
非满足特定的条件,比如说锁<
/p>
)
。那些违反这条规则而直接引用这些函数的不良代码会最终被更
正。
Figure 4.
Before and after: (a)skb_put, (b)skb_push,
(c)skb_pull, and
(d)skb_reserve
-
-
-
-
-
-
-
-
-
上一篇:接口设计规范V1.0___参考
下一篇:UI设计尺寸规范-最新最全UI设计规范标准