Linux内核网络协议栈套接字缓冲区原理

news/2024/4/25 4:49:15/文章来源:https://blog.csdn.net/m0_50662680/article/details/129209628

概念

Linux网络协议栈是内核中最大的组件之一,由于网络部分应用的范围很广,也相对较热,该部分现有的资料很多,学起来也比较容易。首先,我们看看贯穿网络协议栈各层的一个最关键数据结构——套接字缓冲区(sk_buff结构)。

一个封包就存储在这个数据结构中。所有网络分层都会使用这个结构来存储其报头、有关数据的信息,以及用来协调工作的其他内部信息。在内核的进化历程中,这个结构经历多次变动,本文及后面的文章都是基于2.6.20版本,在2.6.32中该结构又变化了很多。该结构字段可粗略划分为集中类型:布局、通用、专用、可选(可用宏开关)。

SKB在不同网络层之间传递,可用于不同的网络协议。协议栈中的每一层往下一层传递SKB之前,首先就是调用skb_reserve函数在数据缓存区头部预留出的一定空间以保证每一层都能把本层的协议首部添加到数据缓冲区中。如果是向上层协议传递skb,则下层协议层的首部信息就没有用了,内核实现上用指针改变指向来实现。

下面看看该结构体中的字段,大部分都给了注释,后面的方法与实现中我们将看到他的各个字段的应用与实际意义。

可选功能字段

在网络模块中同时也提供了很多有用的功能,虽然这些功能都不是必须的,但对现在的应用来讲是不可缺少的一部分,例如,防火墙、组播等。为了支持这些功能,一般都需要在内核数据结构sk_buff中添加相应的成员变量。因此,sk_buff结构中包含很多想#ifdef这样的预编译指令。如下面的两个宏定义。

struct sk_buff {
……
/*在数据结构中定义的宏不能编译成模块;原因在于内核编译之后,开启该选项所得的多数结果为不可
逆的,一般而言,任何引起内核数据结构改变的选项,都不适合编译成一个模块,编译选项和特定的
#ifdef符号相配,以了解一个代码区块什么时候会包含到内核中,这种关联在源码树的Kconfig文件中*/
#ifdef CONFIG_NET_SCHED__u16                          tc_index;  /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT__u16                          tc_verd;    /* traffic control verdict */
#endif……
}

我们打开内核文件夹net->sched下面的Kconfig文件,发现有下面文字:

menu "QoS and/or fair queueing"config NET_SCHEDbool "QoS and/or fair queueing"
……
config NET_CLS_ACTbool "Actions"select NET_ESTIMATOR---help---
……
endif # NET_SCHEDendmenu

与上面数据结构中的宏对应就显然了,如果需要了解内核配置选项与对应的宏,查看对应的Kconfig文件就可以了。需要指出的是,内核编译之后,由某些选项所控制的数据结构是固定的而不是动态变化的。一般来说,如果某些选项修改了内核数据结构,则包含该选项的组件就不能被编译成内核模块。

数据定位与操作

head,end,data,tail四个字段用来指向线性数据缓存区及数据部分的边界。Head和end分别指向缓存区的头与尾;而data和tail则分别指向数据的头与尾。在发送时,每一层协议会在head与data之间填充协议首部,还可能在tail和end之间添加数据。

Skb初始化

网络模块中,有两个用来分配SKB描述符的高速缓存,在SKB模块初始化函数skb_init中被创建

void __init skb_init(void)
{/*一般情况下,SKB都是从该高速缓存中分配的*/skbuff_head_cache = kmem_cache_create("skbuff_head_cache",sizeof(struct sk_buff),0,SLAB_HWCACHE_ALIGN|SLAB_PANIC,NULL, NULL);/*如果在分配SKB时就知道会被克隆,那么应该从这个高速缓存中分配空间*/skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",(2*sizeof(struct sk_buff)) +sizeof(atomic_t),0,SLAB_HWCACHE_ALIGN|SLAB_PANIC,NULL, NULL);
}

分配skb

Alloc_skb()用来分配SKB,数据缓存区描述符是两个不同的实体,这就意味着,在分配一个SKB时,需要分配两块内存,一块是数据缓存区,一块是SKB描述符。

资料直通车:最新Linux内核源码资料文档+视频资料

内核学习地址:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,int fclone, int node)
{struct kmem_cache *cache;struct skb_shared_info *shinfo;struct sk_buff *skb;u8 *data;/*根据参数选着从那个高速缓存中分配空间*/cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;/* Get the HEAD *//*分配空间,从cache中*/skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);if (!skb)goto out;/* Get the DATA. Size must match skb_add_mtu(). *//*对其到size*/size = SKB_DATA_ALIGN(size);/*分配数据缓冲区,其长度为size大小加上skb_shared_info结构大小*/data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info),gfp_mask, node);if (!data)goto nodata;memset(skb, 0, offsetof(struct sk_buff, truesize));/*设置truesize大小为size+sizeof(struct sk_buff)*/skb->truesize = size + sizeof(struct sk_buff);atomic_set(&skb->users, 1);/*设置引用计数为1*/skb->head = data;skb->data = data;skb->tail = data;/*设置end为data+size大小*/skb->end  = data + size;/* make sure we initialize shinfo sequentially *//*skb_shared_info处在skb->end开始处*/shinfo = skb_shinfo(skb);/*初始化该结构*/atomic_set(&shinfo->dataref, 1);shinfo->nr_frags  = 0;shinfo->gso_size = 0;shinfo->gso_segs = 0;shinfo->gso_type = 0;shinfo->ip6_frag_id = 0;shinfo->frag_list = NULL;if (fclone) {/*如果设置了克隆标志*/struct sk_buff *child = skb + 1;/*获得下一个skb结构*//*获得引用计数*/atomic_t *fclone_ref = (atomic_t *) (child + 1);/*设置克隆标志*/skb->fclone = SKB_FCLONE_ORIG;atomic_set(fclone_ref, 1);/*克隆引用标志为1*/child->fclone = SKB_FCLONE_UNAVAILABLE;}
out:return skb;
nodata:kmem_cache_free(cache, skb);skb = NULL;goto out;
}

调用该函数后生成的图如下所示:

对于skb数据结构的其他操作主要放在skbuff.h文件中,主要有skb_reserve()、skb_put()、skb_push()、skb_pull()、skb_trim()等等,都是对skb的head、data、tail、end、len等字段进行操作。代码不难,都能看懂,后面涉及到具体的协议再来看这些。

链表管理

在对skb链表的操作中,为了防止被其他异步操作打断,在操作前都必须现获取SKB头节点中(sk_buff_head结构)的自旋锁,然后才能访问队列中的元素。该链表头结构如下:

struct sk_buff_head {/* These two members must be first. */struct sk_buff   *next;struct sk_buff   *prev;/*链表中的节点数,即队列长度*/__u32                 qlen;/*用来控制SKB链表并发操作的自旋锁*/spinlock_t         lock;
};

对链表操作也增加了很多函数,包括初始化、入队列、出队列等等,也在skbuff.h中。

Skb_shared_info结构

在alloc_skb()看到,其中中分配数据部分分配了一个该结构,在数据缓存区的末尾,保存了数据块的附加信息。如下:

#define skb_shinfo(SKB)           ((struct skb_shared_info *)((SKB)->end))

该结构定义如下:

struct skb_shared_info {/*引用计数,当一个数据缓存区被多个SKB的描述符引用时
就会设置相应的计数*/atomic_t   dataref;/*ip分片的存储有关,片段数*/unsigned short nr_frags;/*生成GSO段时的MSS,因为GSO段的长度是与发送该段的套接口中
合适MSS的整数倍*/unsigned short gso_size;/* Warning: this field is not always filled in (UFO)! */
/*GSO段的长度是gso_size的整数倍,即用gso_size来分割大段时
产生的段数*/unsigned short gso_segs;/*该SKB中的数据支持的GSO类型*/unsigned short  gso_type;__be32          ip6_frag_id;/*ip分片的存储有关,使用情况如下:
1,用于在接收分片组后连接多核分片,组成一个完整的IP数据报;
2,在UDP数据报的传输中,将待分片的SKB连接到第一个SKB中,然后在
传输过程中能够快速的分片;
3,用于存放FRAGLIST类型的聚合分散I/O的数据包,如果输出网络设备支持
FRAGLIST类型的聚合分散I/O,则可以直接输出*/struct sk_buff   *frag_list;/*ip分片的存储有关,片段以关联的方式存储在该数组中*/skb_frag_t        frags[MAX_SKB_FRAGS];
};
其中skb_frag_t类型如下:
struct skb_frag_struct {/*指向文件系统缓存页的指针*/struct page *page;/*数据起始地址在文件系统缓存页中的偏移*/__u16 page_offset;/*数据在文件系统缓存页面中使用的长度*/__u16 size;
};
struct sk_buff {/* These two members must be first. *//*链表,放在结构头,用于强制转化得到,链表头为skb_buff_head*/struct sk_buff            *next;struct sk_buff            *prev;/*指向拥有此缓冲区的sock数据结构,当数据在本地产生或者正由本地进程接收时,就需要这个指针因为该数据以及套接字相关的信息会由L4以及用户应用程序使用。当缓冲去只是被转发时,该指针为NULL*/struct sock                  *sk;/*通常只对一个以及接收的封包才有意义这是一个时间戳记,用于表示封包何时被接收,或者有时用于表示封包预定传输时间*/struct skb_timeval     tstamp;/*此字段描述一个网络设备,该字段的作用与该SKB是发送包还是接受包有关*/struct net_device      *dev;/*接收报文的原始网络设备,主要用于流量控制*/struct net_device      *input_dev;union {struct tcphdr     *th;struct udphdr    *uh;struct icmphdr  *icmph;struct igmphdr  *igmph;struct iphdr       *ipiph;struct ipv6hdr   *ipv6h;unsigned char   *raw;} h;/*四层协议首部*/union {struct iphdr       *iph;struct ipv6hdr   *ipv6h;struct arphdr     *arph;unsigned char   *raw;} nh;/*三层协议首部*/union {unsigned char *raw;} mac;/*二层协议首部*//*目的路由缓存项*/struct  dst_entry      *dst;/*IPSec协议用来跟踪传输的信息*/struct        sec_path  *sp;/** This is the control buffer. It is free to use for every* layer. Please put your private variables there. If you* want to keep them across layers you have to do a skb_clone()* first. This is owned by whoever has the skb queued ATM.*//*SKB信息控制块,是每层协议的私有信息存储空间,由每层协议
自己使用并维护,并只有在本层有效*/char                    cb[48];/*数据总长度,包括线性缓冲区数据长度(data指向),SG类型的聚合分散I/O的数据以及FRAGLIST类型的聚合分散I/O的数据长度,也包括协议首部的长度*/unsigned int               len,/*SG类型和FRAGLIST类型聚合分散I/O存储区中的数据长度*/data_len,/*二层首部长度。实际长度与网络介质有关,在以太网中为以太网桢首部的长度*/mac_len;union {__wsum             csum;__u32                 csum_offset;};/*发送或转发QOS类别。*/__u32                          priority;/*表示此skb在本地允许分片*/__u8                            local_df:1,/*标记所属SKB是否已经克隆*/cloned:1,/*标记传输层校验和的状态*/ip_summed:2,/*标识payload是否被单独引用,不存在协议首部*/      nohdr:1,/*防火墙使用*/nfctinfo:3;/*桢类型,分类是由二层目的地址来决定的,对于以太网设备来说,该字段由eth_type_trans()初始化*/__u8                            pkt_type:3,/*当前克隆状态*/fclone:2,ipvs_property:1;/*从二层设备角度看到的上层协议,即链路层承载的三层协议类型*/__be16                        protocol;/*skb析构函数指针,在释放skb时被调用,完成某些必要的工作*/void                    (*destructor)(struct sk_buff *skb);/*在数据结构中定义的宏不能编译成模块;
原因在于内核编译之后,开启该选项所得
的多数结果为不可逆的,一般而言,任何
引起内核数据结构改变的选项,都不适合
编译成一个模块,编译选项和特定的#ifdef
符号相配,以了解一个代码区块什么时候
会包含到内核中,这种关联在源码树的
Kconfig文件中*/
#ifdef CONFIG_NETFILTER/*防火墙使用*/struct nf_conntrack  *nfct;
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)struct sk_buff            *nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER/*防火墙使用*/struct nf_bridge_info         *nf_bridge;
#endif
#endif /* CONFIG_NETFILTER */
#ifdef CONFIG_NET_SCHED
/*用于流量控制*/__u16                          tc_index;  /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT/*用于流量控制*/__u16                          tc_verd;    /* traffic control verdict */
#endif
#endif
#ifdef CONFIG_NET_DMAdma_cookie_t            dma_cookie;
#endif
#ifdef CONFIG_NETWORK_SECMARK__u32                          secmark;
#endif__u32                          mark;/* These elements must be at the end, see alloc_skb() for details.  *//*全部数据的长度+该结构的长度,如果申请了一个len字节的缓冲区该字段为len+sizeof(sk_buff)*/unsigned int               truesize;/*引用计数,使用这个缓冲区的实例的数目*/atomic_t             users;/*head和end指向数据在内存中的起始和结束位置,即已经分配的缓冲区空间的开端和尾端data和tail指向数据区域的起始和结束位置,即实际数据的开端和尾端*/unsigned char            *head,*data,*tail,*end;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_73882.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python-pycharm爬虫工程(一)-依赖包下载部分

1,创建一个工程所需的python依赖包 2,依赖包下载慢或者无法下载解决 3,国内对应的镜像有哪些 1,创建一个工程所需的python依赖包 python新工程创建新的python依赖虚拟环境 File-->Settings-->Project:pc 其中pc是我的工程名 点击ok之后得到新的虚拟python依赖包…

【GlobalMapper精品教程】054:标签(标注)功能案例详解

同ArcGIS标注一样,globalmapper提供了动态标注的功能,称为标签,本文详解标签的使用方法。 文章目录 一、标签配置二、创建标签图层三、标签图层选项1. 标签字段2. 标签样式3. 标签格式4. 标签语言5. 标签优先级一、标签配置 在配置页面的【矢量显示】→标签选项卡下,有标签…

Springboot 整合Flowable工作流框架搭建

我们在开发自动化办公软件时经常会遇到各种审批流程功能,这个使用就需要使用到工作流引擎。目前主流的工作流引擎有Activiti、Flowable、camunda,其中Flowable是在Activiti的基础上开发出来的,基于BPMN2.0协议,它包括 BPMN&#x…

大型旋转设备滑动轴承X、Y测点振动值说明(转载的)

滑动轴承支撑的大型旋转设备,绝大部分的故障都表现为不平衡引起的1倍频振动,诊断故障原因要根据振动随转速、负荷、温度、时间的变化情况来具体判断。滑动轴承设备的诊断主要依据电涡流传感器测量轴和轴瓦间的相对振动,判断转子相关的各种问题…

Linux 脚本(sh)之 定时清理悬空、指定镜像,自动增长版本号

定时任务(images_clean): 位置:/mydata/hostmachine_jenkins/images_clean.sh 作用:Jenkins发布之后,遗留下来的老版镜像以及悬空镜像进行定时清理 注意:如果你需要发布新的服务,那么你需要进入当前目录…

快到金3银4了,准备跳槽的可以看看

前两天跟朋友感慨,今年的铜九铁十、裁员、疫情导致好多人都没拿到offer!现在已经12月了,具体明年的金三银四只剩下两个月。 对于想跳槽的职场人来说,绝对要从现在开始做准备了。这时候,很多高薪技术岗、管理岗的缺口和市场需求也…

高品质运动耳机哪款更好用、运动耳机最好的牌子推荐

在运动的时候大家都会选择戴上耳机,用音乐来”调味“,让跑步的过程不那么枯燥乏味。说到运动耳机,除了老生常谈的音质以外,耳机的材质、耳机的工艺,耳机的佩戴稳固性等,也都在影响着用户的体验,…

未来土地利用模拟FLUS模型

未来土地利用模拟(FutureLand-Use Simulation, FLUS)模型1 模型简介1.1 基于ANN 的适宜性概率计算1.2 基于自适应惯性机制的元胞自动机1.3 模拟精度评价参考流域 径流变化是 自然因素和 人为因素共同作用的结果,其中人为因素最为直接的方式就…

流感来了,这类人最容易感染!

最近有学校因多名学生发热停课,浙江多地疾控也提醒大家现在是进入了甲流高发期。今天就来讲一讲甲流该如何防护。首先甲流与普通感冒不同,感冒病原体是鼻病毒、冠状病毒、副流感病毒等。流感病毒是正粘病毒科,根据核蛋白和基质蛋白M1抗原性的…

Fabric.js使用说明Part 2

目录一、Fabric.js使用说明Part 1Fabric.js简介 开始方法事件canvas常用属性对象属性图层层级操作复制和粘贴二、Fabric.js使用说明Part 2锁定拖拽和缩放画布分组动画图像滤镜渐变右键菜单删除三、Fabric.js使用说明Part 3自由绘画绘制背景图片绘制文本绘制线和路径一、锁定Fab…

FSM——squirrel状态机使用

FSM——squirrel状态机使用 1 FSM介绍 1.1 概念 FSM(finite state machine):有限状态机 是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。核心内容:有限个状态、通过外部操作引起状态的转移。用来对状态的流转进行解耦&a…

高等工程数学张韵华版第二章课后题

答案仅供参考 本章内容 第 2 章 线性空间 2.1 向量的相关性 2.1.1 线性组合和线性表示 2.1.2 线性相关与线性无关 2.2 秩 2.2.1 向量组的秩 2.2.2 矩阵的秩 2.2.3 相抵标准形 2.3 线性空间 2.3.1 线性空间的定义 2.3.2 线性子空间 2.4 维、基、坐标 2.4.1 维、基、坐标的定义…

复杂场景的接口测试

测试场景一:被测业务操作是由多个API调用协作完成 背景:一个单一的前端操作可能会触发后端一系列的API调用,此时API的测试用例就不再是简单的单个API调用,而是一系列API的调用 存在的情况:存在后一个API需要使用前一个…

springboot+vue软件bug项目测试过程管理系统

config:主要用来存储配置文件,以及其他不怎么动用的信息 controller:项目的主要控制文件 dao: 主要用来操作数据库 entity: 实体,用来放与数据库表里对应的实体类,表中的字段对应类中的属性值,并…

视觉SLAM数据集(一):TUM DataSet

首先给出数据集下载地址:TUM Dataset Download。 如果你是第一次做实验,建议下载xyz的数据集,因为它的动作相对很小,只包含桌面上的一小部分。一旦成功测试,就可以试试desk数据集,它包含四张桌子和几个闭环…

C语言的学习小结——数组

一、一维数组的创建与初始化 1、格式: type_t arr_name[const_n];//type_t 是指数组的元素类型 //const_n 是一个常量表达式,用来指定数组的大小 注: 数组是使用下标来访问的,下标从0开始。 数组的大小可以通过计算得到&…

电商平台商品详情接口的应用场景

API接口的定义价格、库存量、发货地点等。此外,它还可以提供商品的详细信息,包括商品的图片、详细描述、规格参数、售后服务等。这些信息可以帮助用户更好地了解商品,从而更好地选择商品。其次,电商平台商品详情接口的实现原理是基…

使用Chemistry Development Kit (CDK) 来进行化学SMILES子结构匹配

摘要 SMILES是一种用于描述化合物结构的字符串表示法,其中子结构搜索是在大规模化合物数据库中查找特定的结构。然而,这种搜索方法存在一个误解,即将化合物的子结构视为一个独立的实体进行搜索,而忽略了它们在更大的化合物中的上…

极光笔记 | 埋点体系建设与实施方法论

PART 01 前 言随着网络技术的发展,从粗犷型到精细化运营型,再到现在的数字化运营,数据变得越来越细分和重要,不仅可以进行策略调整,还可以实现自动化的精细化运营。而数据价值的起点就是埋点,只有合理地埋点…

lammps教程:Ovito选择特定晶粒的方法

大家好,我是小马老师。 本文介绍如何使用ovito提取特定的晶粒。 在多晶的lammps模拟中,可能会对某一个特定晶粒的变形情况进行分析,此时,需要找到这个晶粒,并进行单独分析。 ovito有专用的晶粒识别命令,…