Linux网络子系统中存在一些很重要的数据结构,贯穿整个子系统,主要有以下两个: struct sk_buff:数据封包结构。所有的网络分层都会使用这个结构来存储其报头、有关用户数据,以及协调其他工作的其他内部信息。
struct net_device:在Linux内核中,每种网络设备都用这个数据结构表示,包括软硬件的配置信息。
一、套接字缓冲区:sk_buff结构
这可能是Linux网络代码中最重要的数据结构,表示数据报文。这个结构定义在头文件中,由巨大的变量堆组成,试图满足所有人的所有需求。
这个结构的字段大致分为以下几个类型:
网络层次
通用字段
功能专用
管理函数
在网络系统的不同网络层都会使用这个结构,而当这个结构从一个分层传到另一个分层时,其不同的字段会随之发生改变。如L4层在传递给L3之前会附加一个报头,通用L3到L2之前也会加上自己的报头。附加报头比把数据从一个分层拷贝到另一个分层更有效率。
由于要在一个缓冲区开端新增空间(也就是修改指向缓冲区头部的指针),内核提供了skb_reserve函数来执行这个操作。所以,当缓冲区往下传递给每个网络层时,每层的协议首先要做的就是调用skb_reserve函数为该协议的报头预留空间。
而在缓冲区向上传递给上层网络时,并没有本层报头从缓冲区中删除,二是将直线有效数据的指针向前移到上层的报头位置。
由于网络代码提供了大量的选项性功能,不一定总是需要,如防火墙、多播、连接跟踪等,这些功能都会在sk_buff结构猪附加上字段。因此,sk_buff结构中有许多由C预处理#ifdef指令附加的字段。一般而言,任何引起内核数据结构改变的选项,都不适合编译成一个模块进行动态加载。
sk_buff中的某些字段是为了组织数据结构本身:
- struct sk_buff {
- /* These two members must be first. */
- struct sk_buff *next;
- struct sk_buff *prev;
同时为了迅速找到整个表的头,在表的开端额外增加一个sk_buff_head结构作为一种哑元元素,sk_buff_head结构是:- 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结构的前两个元素是相同的,所以同样的函数也可用于操作sk_buff和sk_buff_head二者。
sk_buff结构中的list字段指向表头:
sk_buff中的其他字段:
struct sock *sk:指向拥有此缓冲区的套接字的sock数据结构。当数据在本地产生或者正在由本地进程接收时,就需要这个指针,因为该数据以及套接字相关的信息会由L4层(TCP或UDP)以及用户应用程序使用。当缓冲区只是被转发时,该指针就是NULL。
unsigned int len:这是指缓冲区猪数据区块的大小。这个长度包括主要缓冲区(由head所指)的数据以及一些片段(fragment)的数据。当缓冲区从一个网络层传递给下一个网络层时,其值会发生变化。因为在协议栈中往上移动时,报头会被丢弃。但是往下移动时,报头会被添加进来,len会将协议报头长度算在里面。
unsigned int data_len:与len不同,data_len只计算片段中的数据大小
unsigned int mac_len:MAC报头的大小
atomic_t users:引用计数,或者使用这个sk_buff缓冲区的实例的数目。这个参数的的主要用途是避免这个结构仍在使用时,被另一个实例释放掉。users有时直接使用atimic_inc和atomic_dec函数递增和递减,但在大多数时候,采用skb_get和kfree_skb进行处理。
unsigned int truesize:表示此缓冲区的总大小,包括sk_buff结构本身。当此缓冲区得到所分配的len个字节的数据请求空间时,此字段的初始化由alloc_skb函数设置为len+sizeof(sk_buff)。每当skb->len的值增加时,此字段就会得到更新。
sk_buff_data_t tail
sk_buff_data_t end
unsigned char *head
unsigned char *data
这些字段代表缓冲区的边界以及其中的数据。当每一层为其工作而准备缓冲区时,可能会为了一个报头或更多的数据分配更多的空间。head和end指向已分配空间的开端和尾端,而data和tail指向实际数据的开端和尾端。因此,可以再head和data直接填充报文头,在tail和end之间增加新的数据。
其中tail和end根据系统是否使用NET_SKBUFF_DATA_USES_OFFSET来决定使用偏移地址还是指针
- #ifdef NET_SKBUFF_DATA_USES_OFFSET
- typedef unsigned int sk_buff_data_t;
- #else
- typedef unsigned char *sk_buff_data_t;
- #endif
void (*destructor)(struct sk_buff *skb):此函数指针所指的函数在缓冲区被删除时,完成某些工作。当此缓冲区不属于一个套接字时,destructor通常不会被初始化。但若属于一个套接字时,通常被设置为sock_rfree或sock_wfree。这两个函数可用于更新套接字队列猪所持有的内存。
阅读(5127) | 评论(1) | 转发(5) |