Linux sk_buff四大指针与相关操作

嵌入式技术

1372人已加入

描述

在以上文章中,没有分析过Linux内核网络关键的数据结构-套接字数据缓存struct sk_buff,本文将第一次分享到sk_buff,但鉴于其在内核网络中一些复杂情况,本次只简单介绍sk_buff内存空间布局情况与相关操作。

套接字数据缓存(socket buffer)在Linux内核中表示为:struct sk_buff,是Linux内核中数据包管理的基本单元,主要包含两个部分,其一:管理数据,即数据包的管理信息;其二:报文数据,保存了实际网络中传输的数据,在内核协议栈起承上启下的作用,也有很多值得关注的sk_buff操作。

1、sk_buff四大指针与相关操作

分配初始化:

struct sk_buff中四个指针都指向数据区,分别是head、data、tail、end,刚刚分配出来的sk_buff会立马进行四大指针的初始操作。分配sk_buff如下所示:

内核网络

sk_stream_alloc_skb最终调用__alloc_skb函数进行内存分配,分配skb后,进行四大指针的初始化操作:

内核网络

其中skb_reset_tail_pointer(skb):

内核网络

最终四大指针初始化为以下图所示:

内核网络

此时head、data、tail三个指针指向一起,end指向数据缓冲区的尾部。预留协议头空间:在sk_stream_alloc_skb调用__alloc_skb函数进行内存分配后,下一步就会预留协议头空间,使得head、tail、data指针分离:

内核网络

skb_reserve如下,

内核网络

操作后skb_buff的指针如下所示:

内核网络

skb_reserve作用就是预留空间,而且是尽最大的空间预留,但它并没有把数据放到该空间,只是简单更新指针,预留空间!

内核网络

因为很多头都会有可选项,那么不知道头部可选项是多大,所以只能按照最大的分配,同时也要明白一点,预留的空间headroom不一定使用完,可能还有剩余。当我们要增加协议头信息的时候,data指针向上移动,当增加数据的时候tail指针向下移动,完成数据包的封装。此时还没有数据,data和tail指向相同。

操作tailroom中用户数据块区域:skb_put用于修改指向数据区末尾的指针tail:

内核网络

可以看到tail指针的移动是扩大数据区域,即数据区向下扩大len字节,并更新数据区长度len。

增加headroom区域的协议头:skb_push函数用于移动data指针,增加头部协议,与skb_reserve()类似,也并没有真正向数据缓存区中添加数据,而只是移动数据缓存区的头指针data。数据由其他函数复制到数据缓存区中。函数如下:

内核网络

如下两张图分别是由传输层、网络层,数据包向下传递时data指针移动,进行头部协议的封装。

TCP层添加TCP首部。

SKB传递到IP层,IP层为数据包添加IP首部。

SKB传递到链路层,链路层为数据包添加链路层首部。

内核网络

可以看到在数据包封装的过程中,每一层移动data指针进行数据报头的封装。

数据报文解封装,解除协议头:skb_pull通过将data指针向下移动,进行数据报文的解封装,函数如下所示:

内核网络

如下图所示,在收包流程上,向上层协议,如下网络层向传输层传送的时候,调用skb_pull进行数据包的解封装。

内核网络

以上就是struct sk_buff的四大指针的相关操作,通过分析可得:

head指向缓冲区的首地址,作为上边界

end指向缓冲区的尾地址,作为下边界

data指针在数据包头部封装和解封装的过程中移动,指向各层的协议头,skb_push函数将data的指向,向低地址移动(向上),完成协议头空间的占据,skb_pull函数将data的指向,向高地址移动(向下),完成协议头的解封装。

tail指针在增加应用层用户缓冲数据时移动,skb_put函数将该指针向高地址移动(向上),完成用户数据空间的占据。

2、非线性区域

在1、中,可以看到每张sk_buff的图:在end指针紧挨着一个非线性区域;

在struct sk_buff中没有指向skb_shared_info结构的指针,利用end指针,可以用skb_shinfo宏来访问:

内核网络

其中skb_end_pointer函数如下,返回end指针

内核网络

其中skb_frag_t如下:

内核网络

nr_frags,frags,frag_list与IP分片存储有关。

frag_list的用法:

用于在接收分组后链接多个分片,组成一个完整的IP数据报

在UDP数据报输出中,将待分片的SKB链接到第一个SKB中,然后在输出过程中能够快速的分片

用于存放FRAGLIST类型的聚合分散I/O数据包

判断是否存在非线性缓冲区:

先说明struct sk_buff中关于长度的两个字段

len字段:无分片的报文,数据报文的大小

data_len字段:存在分散报文,data_len表示分片的部分大小

如下所示,没有开启分片的报文len = x,data_len = 0:

内核网络

如下所示在Linux内核中,使用skb_is_nonlinear函数判断是否存在分片,即通过判断data_len的大小是否为0:

内核网络

在没有开启分片的报文中,数据包长度在struct sk_buff中为len字段的大小,即data到tail的长度,nf_frags为0,frag_list为NULL。

普通聚合分散I/O的报文:

采用聚合分散I/O的报文,

frag_list为 NULL,nf_frags不等于0,说明这不是一个普通的分片,而是聚合分散I/O的报文。如下所示:nr_frags为2,而frag_list为NULL,说明这不是普通的分片,而是聚合分散I/O分片,数量为2,这两个分片指向同一物理分页,各自在分页中的偏移和长度分别是0/S1和S1/S2。

内核网络

FRAGLIST类型的分散聚合I/O的报文:

采用FRAGLIST类型的分散聚合I/O报文,frag_list不为NULL,nf_frags等于0,数据长度len为x+S1,data_len为S1。

内核网络

以上从struct sk_buff的四大指针以及操作、非线性区域对套接字缓存(socket buffer)进行分析,更多sk_buff的分析、实操等将在以后的文章中梳理。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分