Linux内核网络协议栈之套接字缓冲区(纯干货~)
Linux网络协议栈是内核中最大的组件之一,由于网络部分应用的范围很广,也相对较热,该部分现有的资料很多,学起来也比较容易。首先,我们看看贯穿网络协议栈各层的一个最关键数据结构——套接字缓冲区(sk_buff结构)。
一个封包就存储在这个数据结构中。所有网络分层都会使用这个结构来存储其报头、有关数据的信息,以及用来协调工作的其他内部信息。在内核的进化历程中,这个结构经历多次变动,本文及后面的文章都是基于2.6.20版本,在2.6.32中该结构又变化了很多。该结构字段可粗略划分为集中类型:布局、通用、专用、可选(可用宏开关)。
SKB在不同网络层之间传递,可用于不同的网络协议。协议栈中的每一层往下一层传递SKB之前,首先就是调用skb_reserve函数在数据缓存区头部预留出的一定空间以保证每一层都能把本层的协议首部添加到数据缓冲区中。如果是向上层协议传递skb,则下层协议层的首部信息就没有用了,内核实现上用指针改变指向来实现。
下面看看该结构体中的字段,大部分都给了注释,后面的方法与实现中我们将看到他的各个字段的应用与实际意义。
【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)


可选功能字段
在网络模块中同时也提供了很多有用的功能,虽然这些功能都不是必须的,但对现在的应用来讲是不可缺少的一部分,例如,防火墙、组播等。为了支持这些功能,一般都需要在内核数据结构sk_buff中添加相应的成员变量。因此,sk_buff结构中包含很多想#ifdef这样的预编译指令。如下面的两个宏定义。
我们打开内核文件夹net->sched下面的Kconfig文件,发现有下面文字:
与上面数据结构中的宏对应就显然了,如果需要了解内核配置选项与对应的宏,查看对应的Kconfig文件就可以了。需要指出的是,内核编译之后,由某些选项所控制的数据结构是固定的而不是动态变化的。一般来说,如果某些选项修改了内核数据结构,则包含该选项的组件就不能被编译成内核模块。
数据定位与操作
head,end,data,tail四个字段用来指向线性数据缓存区及数据部分的边界。Head和end分别指向缓存区的头与尾;而data和tail则分别指向数据的头与尾。在发送时,每一层协议会在head与data之间填充协议首部,还可能在tail和end之间添加数据。

Skb初始化
网络模块中,有两个用来分配SKB描述符的高速缓存,在SKB模块初始化函数skb_init中被创建
分配skb
Alloc_skb()用来分配SKB,数据缓存区描述符是两个不同的实体,这就意味着,在分配一个SKB时,需要分配两块内存,一块是数据缓存区,一块是SKB描述符。
调用该函数后生成的图如下所示:

对于skb数据结构的其他操作主要放在skbuff.h文件中,主要有skb_reserve()、skb_put()、skb_push()、skb_pull()、skb_trim()等等,都是对skb的head、data、tail、end、len等字段进行操作。代码不难,都能看懂,后面涉及到具体的协议再来看这些。
链表管理
在对skb链表的操作中,为了防止被其他异步操作打断,在操作前都必须现获取SKB头节点中(sk_buff_head结构)的自旋锁,然后才能访问队列中的元素。该链表头结构如下:

对链表操作也增加了很多函数,包括初始化、入队列、出队列等等,也在skbuff.h中。
Skb_shared_info结构
在alloc_skb()看到,其中中分配数据部分分配了一个该结构,在数据缓存区的末尾,保存了数据块的附加信息。如下:
该结构定义如下:
关于该结构的操作在后面的协议分析中碰到后进行阅读。接下来依着协议栈的层次进行分析和学习,从驱动一直到传输层。只会涉及最基本的几个协议(TCP、IP、UDP、ICMP以及ARP等)。
