一. 前言
Linux内核网络子系统的实现之所以灵活高效,主要是在于管理网络数据包的缓冲器-socket buffer设计得高效合理。在Linux网络子系统中,socket buffer是一个关键的数据结构,它代表一个数据包在内核中处理的整个生命周期。
二. Socket Buffer组成
1. 数据包
存放实际要在网络中传送的数据缓冲区
2. 管理数据结构(struct sk_buff)
当在内核中处理数据包时,内核需要一些其他的数据来管理数据包和操作数据包,例如数据接收/发送的时间,状态等。这部分信息不需要对外发送,所以不存放在数据包中。
数据包和sk_buff的关系好比信件和信封一样,信件是你要传递给他人的信息内容,信封告诉邮局如何将信件传到目的地。
三. socket buffer穿越TCP/IP协议栈
1. 发送网络数据包
当网络应用数据从用户地址空间复制到内核地址空间时,在内核的套接字层会创建相应的socket buffer实例来将负载数据存放在数据包缓冲区中。数据包的地址,长度,状态等信息存放在sk_buff结构中。
在socket buffer由上到下穿越TCP/IP协议栈时,会发生如下事件:
1. 各层协议的头信息会不断的插入到数据包中
2. 在sk_buff结构中描述协议头信息的地址指针会被赋值
创建socket buffer时,需要在数据包缓冲区前留出足够的空间来存放各层协议的头信息
2. 接收网络数据包
网络适配器接收到发给本机的网络数据后,首先产生中断通知内核收到了网络数据帧。接着在网络适配器的中断处理程序中调用dev_alloc_skb函数向系统申请一个socket buffer,将接收到的网络数据帧从设备的硬件缓冲区复制到socket buffer的数据包缓冲区,填写sk_buff结构中的地址,接收时间和协议等信息。此时socket buffer到达了内核地址空间,当socket buffer由下往上穿越TCP/IP协议栈时:
1. 各层协议的头信息会不断从数据包中去掉
2. 相应的在sk_buff结构中描述头信息的地址指针会被复位,并调整sk_buff结构中指向有效数据的sk_buff->data指针。
如上图所示,Socket Buffer的组织方式能有效的避免重复复制数据。在传送数据的情况下只需要复制两次:一次是从应用层的用户地址空间复制到内核地址空间;一次是从内核空间复制到网络适配器的硬件缓冲区。在接收数据的情况下也是复制两次数据,和传送数据过程相反。
四. 总结
1. Socket Buffer包含两个部分:数据包和数据包管理结构(struct sk_buff)。
2. 在数据包接收的时候,各层的协议头是不断从数据包去掉,在发送数据包时候,各层的协议头不断插入到数据包中。
3. Socket Buffer的设计优点是避免了重复拷贝数据,在传送和接收的分别都只有两次,分别是应用层和内核空间之间的拷贝和网络适配器的硬件缓冲区和内核空间之间的拷贝。