浅谈linux 内核网络 sk_buff 之克隆与复制

news2024/12/30 3:15:50

【推荐阅读】

需要多久才能看完linux内核源码?

概述Linux内核驱动之GPIO子系统API接口

一文了解Linux内核的Oops

一篇长文叙述Linux内核虚拟地址空间的基本概括

纯干货,linux内存管理——内存管理架构(建议收藏)

1 skb_clone() 函数

该函数就是单单克隆下 sk_buff 结构体,对 sk_buff 结构的数据区、分片结构体 skb_shared_info、分片结构体数据区等结果进行共享。

  • skb:将要被克隆的sk_buff结构体(后面用父skb来称);
  • gfp_mask:向内核申请分配内存的方式;
  • 返回:新克隆出来的skb结构体(后面称其为子skb);
struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)
{
	struct sk_buff *n;
 
	if (skb_orphan_frags(skb, gfp_mask))
		return NULL;
 
	n = skb + 1;//取克隆的子skb指针
	/*1.判断skb是否从skbuff_fclone_cache中分配
	  2.子skb尚未被克隆过
	*/
	if (skb->fclone == SKB_FCLONE_ORIG &&
	    n->fclone == SKB_FCLONE_UNAVAILABLE) {
		atomic_t *fclone_ref = (atomic_t *) (n + 1);//满足克隆条件修改继续
		n->fclone = SKB_FCLONE_CLONE;
		atomic_inc(fclone_ref);
	} else {//不满足条件从skbuff_head_cache中申请skb
		if (skb_pfmemalloc(skb))
			gfp_mask |= __GFP_MEMALLOC;
 
		n = kmem_cache_alloc(skbuff_head_cache, gfp_mask);
		if (!n)
			return NULL;
 
		kmemcheck_annotate_bitfield(n, flags1);
		kmemcheck_annotate_bitfield(n, flags2);
		n->fclone = SKB_FCLONE_UNAVAILABLE;
	}
 
	return __skb_clone(n, skb);//调用克隆接口,赋值结构体成员变量
}
 
static struct sk_buff *__skb_clone(struct sk_buff *n, struct sk_buff *skb)
{
#define C(x) n->x = skb->x
 
	n->next = n->prev = NULL;
	n->sk = NULL;
	__copy_skb_header(n, skb);
 
	C(len);
	C(data_len);
	C(mac_len);
	n->hdr_len = skb->nohdr ? skb_headroom(skb) : skb->hdr_len;
	n->cloned = 1;
	n->nohdr = 0;
	n->destructor = NULL;
	C(tail);
	C(end);
	C(head);
	C(head_frag);
	C(data);
	C(truesize);
	
	/*来设置子skb的引用计数器为1,表明还有另外一个skb(其实就是父skb),
	 *防止子skb释放时连同共享数据区也一起释放掉。	
	*/
	atomic_set(&n->users, 1);
	
	/*
	 *这个简单的说就是,因为开始也讲过sk_buff的数据区和分片结构是一体的,连内存申请和释放都是一起的。
	 *而dataref是分片结构skb_shared_info中的一个 表示sk_buff的数据区和分片结构被多少skb共享的成员字段。
	 *这里调用atomic_inc()函数让该引用计数器自增,表明克隆skb对sk_buff数据区和分片结构的共享引用。
	*/
	atomic_inc(&(skb_shinfo(skb)->dataref));
	
	skb->cloned = 1;//表明这是个克隆的skb结构体
 
	return n;
#undef C
}

skb_clone() 函数功能就是简单的克隆一个 skb,然后共享其他数据。虽然可以提高效率,但是存在一个很大的缺陷,就是当有克隆 skb 指向共享数据区是,那么共享数据区的数据就不能被修改了。所以说如果只是让多个 skb 查看共享数据区内容,则可以用 skb_clone() 函数来克隆这几个 skb 出来,提高效率。

【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

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

2 pskb_copy() 函数

pskb_copy()函数:不仅拷贝sk_buff结构体,还拷贝sk_buff结构体指针data所指向的数据区(当然这个数据区包括了分片结构体,因为内存分配时,这两个结构体都是一起分配的,现在如果要重新为数据区分配内存的话,那自然也是一起分配了),但是分片结构体中所指的数据区是共享的。

  • skb:将要被复制的 skb
  • gfp_mask:内核内存申请时,内存分配的方式
  • 返回:复制好的子 skb 结构
static inline struct sk_buff *pskb_copy(struct sk_buff *skb,
					gfp_t gfp_mask)
{
	return __pskb_copy(skb, skb_headroom(skb), gfp_mask);
}
 
struct sk_buff *__pskb_copy(struct sk_buff *skb, int headroom, gfp_t gfp_mask)
{
	unsigned int size = skb_headlen(skb) + headroom;
	struct sk_buff *n = __alloc_skb(size, gfp_mask,
					skb_alloc_rx_flag(skb), NUMA_NO_NODE);
 
	if (!n)
		goto out;
 
	/* Set the data pointer */
	/*是让分配到的子skb中的data指针和tail指针指向同一个位置,
	 *为了后面改变data指针和tail指针来存放协议信息。
	*/
	skb_reserve(n, headroom);
	
	
	/* Set the tail pointer and length */
	/*使tail指针向高地址偏移(len - data_len)即是协议信息和应用层数据的长度和。
	 *以便存储各层协议和应用层数据。*/
	skb_put(n, skb_headlen(skb));
 
	/*Copy the bytes
	 *这是个内存拷贝的封装函数,就是从被拷贝的skb结构中的data指针指向的地方开始,
	 *偏移len(因为len = (data - tail) + data_len;所以这里本应该写成(data - tail)的,
	 *但考虑到此时分片结构数据区还没有数据,data_len为零。即是len = data-tail)个字节
	 *内容都拷贝到新复制到的skb结构体中去。即是:用被拷贝的skb中的数据区内容来为新拷贝
	 *的skb结构的数据区填充。
	*/
	skb_copy_from_linear_data(skb, n->data, n->len);
 
	n->truesize += skb->data_len;
	n->data_len  = skb->data_len;
	n->len	     = skb->len;
 
	if (skb_shinfo(skb)->nr_frags) {
		int i;
 
		if (skb_orphan_frags(skb, gfp_mask)) {
			kfree_skb(n);
			n = NULL;
			goto out;
		}
		/*新的skb结构中的分片结构体指针指向 开始被拷贝的skb结构中分片结构体指针,
		 *就是让新的skb结构中的分片结构指针指向共享的分片结构数据区。
		 *skb_shinfo(n)->nr_frags = i;则是为新的skb结构体中的分片结构
		 *nr_frags成员字段(表示有多少个分片结构数据区)赋值;
		*/
		for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
			skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i];
			skb_frag_ref(skb, i);
		}
		skb_shinfo(n)->nr_frags = i;
	}
 
	if (skb_has_frag_list(skb)) {//链表类型的分片数据
		skb_shinfo(n)->frag_list = skb_shinfo(skb)->frag_list;
		skb_clone_fraglist(n);
	}
 
	copy_skb_header(n, skb);
out:
	return n;
}

pskb_copy()函数实现其实不难,主要是分配skb及数据区内存----》对数据区拷贝赋值----》处理分片结构数据区内存----》为其他成员变量拷贝赋值。下面是pskb_copy()函数的原理图:

其实上面的skb_clone()函数和pskb_copy()函数就像高富帅和屌丝男一样:skb_clone()函数就是那种富二代、官二代、星二代(当然了,虽然有些人不是什么二代,但人家有干爹),想要什么他老爸早在开始的时候就已经准备好了(skb_clone()函数是使用父skb内存申请时准备好的skb内存空间);而pskb_copy()函数就不一样了,什么东西都必须自己去挣、去争(pskb_copy()函数是自己去调用skb_alloc()函数来申请)。如果挣不到或者争不到,那没办法直接game over。但是唯一值得高兴的是,pskb_copy()函数申请内存时,其实是可以选择:alloc_skb_fclone()函数来申请的,可以让自己孩子成为某二代嘛(因为skb_clone()函数这个二代用的空间就是从alloc_skb_fclone()函数申请时返回的子skb),当然了,这个得自己去封装了。内核选择的还是低调做法,用alloc_skb()函数去申请,自给自足,不让自己孩子成为某二代。

3 skb_copy() 函数

上面的pskb_copy()函数和skb_clone()函数类似:skb_clone()函数克隆出来的skb结构不能修改其共享数据区的数据,而pskb_copy()函数也是一样的,克隆出来的skb及数据区不能修改共享的分片结构数据区内容。所以如果想要修改分片结构数据区的内容,则必须要用skb_copy()函数来克隆skb结构体。skb_copy()函数是对skb结构体真正的完全复制拷贝。不仅是sk_buff结构体还有data指针指向的数据区(包括分片结构)以及分片结构中指针指向的数据区,都各自复制拷贝一份。

struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)
{
	int headerlen = skb_headroom(skb);
	unsigned int size = skb_end_offset(skb) + skb->data_len;
	struct sk_buff *n = __alloc_skb(size, gfp_mask,
					skb_alloc_rx_flag(skb), NUMA_NO_NODE);
 
	if (!n)
		return NULL;
 
	/* Set the data pointer */
	skb_reserve(n, headerlen);
	/* Set the tail pointer and length */
	skb_put(n, skb->len);
	
	//拷贝数据区、数组分片区、链表分片区
	if (skb_copy_bits(skb, -headerlen, n->head, headerlen + skb->len))
		BUG();
 
	copy_skb_header(n, skb);
	return n;
}

实现机制如下:

4 小结

以上就是有关sk_buff结构的一些克隆拷贝函数了。要知道什么时候该用哪个克隆拷贝函数,就必须知道各个函数的不同点。当然,首先还是来说下sk_buff结构及相关结构体,第一块是sk_buff自身结构体,第二块是sk_buff结构的数据区及分片结构体(他们始终在一起),第三块则是分片结构中的数据区。然后来总结下各个函数的不同点:

  • skb_clone() 函数仅仅是克隆个sk_buff结构体,其他数据都是共享;
  • pskb_copy() 函数克隆复制了sk_buff和其数据区(包括分片结构体),其他数据共享;
  • skb_copy() 函数则是完全的复制拷贝函数了,把sk_buff结构体和其数据区(包括分片结构体)、分片结构的数据区都复制拷贝了一份。

为什么要定义这么多个复制拷贝函数呢? 其真正的原因是:不能修改共享数据。所以如果想要修改共享数据,只能把这份共享数据拷贝一份,因此就有了这几个不同的复制拷贝函数了。 选择使用哪个复制拷贝函数时就根据你所要修改的哪块共享数据区来定。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1X的示波器探头为什么会降低示波器带宽

有些无源示波器探头分为1X和10X两个挡位&#xff0c;比如这个探头&#xff0c;这里有个按钮可以选择1X或者10X&#xff0c; 1X表示测量的信号不在探头衰减&#xff0c;同时示波器的通道选项也不用放大&#xff0c;10X表示测量的信号在探头衰减10倍&#xff0c;同时示波器的通道…

Spring Boot的两种配置文件

⭐️前言⭐️ Spring Boot项目中重要的数据都是在配置文件中配置的&#xff0c;下边我们就来学习SpringBoot中的配置文件的具体详情。 &#x1f349;博客主页&#xff1a; &#x1f341;【如风暖阳】&#x1f341; &#x1f349;精品Java专栏【JavaEE进阶】、【JavaEE初阶】、…

2.5D游戏,角色移动限制方法。不用空气墙。

有一个项目&#xff0c;2.5D视角。角色在设定好的路线上自由移动&#xff0c;不能超出路线。 之前的做法是用空气墙&#xff0c;设定物理碰撞&#xff0c;然后角色移动。 我感觉这种做法性能有点低。手机上体验平均帧时是4ms 于是想用空间换时间&#xff0c;将可能的运算进行预…

chrome 如何下载网站在线预览PDF文件,保存到本地

爱学习的小伙伴肯定遇到过那种只能在线看&#xff0c;但并不提供下载的的PDF文件&#xff01; 但有时候想保存到本地有很费劲。今天准备了一个很简单的方法 以这个在线pdf为例 在线PDF文件 该如何把这个PDF保存到本地呢~ 方法 1.以chrome浏览器为例&#xff0c;打开准备好的示…

iOS运行时Runtime在OC中的应用场景

本篇将会总结Rutime的具体应用实例&#xff0c;结合其动态特性&#xff0c;Runtime在开发中的应用大致分为以下几个方面&#xff1a; 一、动态方法交换&#xff1a;Method Swizzling 实现动态方法交换(Method Swizzling )是Runtime中最具盛名的应用场景&#xff0c;其原理是&a…

SpringBoot2.0中MVC和WebFlux控制层Controller对比

本篇文章是SpringBoot2.0关于Controller控制层的对比&#xff0c;相信很多开发最好奇的也是这块。那么小编就带着大家一起先来看一下尝尝鲜,本篇文章比较短小精悍,只讲如何使用,至于原理剖析,后面会讲。阅读时间大概3分钟,现在开始! 文章目录一、演示目录结构二、演示启动类定义…

昨天阅读量900多

今日阅读量还不错的样子&#xff0c;也有900多了&#xff0c;

【C语言】函数递归详解

&#x1f680;write in front&#x1f680; &#x1f4dd;个人主页&#xff1a;认真写博客的夏目浅石. &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​ &#x1f4e3;系列专栏&#xff1a;鹏哥带我学c带我飞 &#x1f4ac;总结&#xff1a;希望你看…

解析davinci快捷键配置文件

davinci resolve 是一款非线性影片剪辑软件&#xff0c;mac 下快捷键配置文件位于&#xff1a; lucaslucasdeMacBook-Pro DaVinci Resolve % pwd /Users/lucas/Library/Preferences/Blackmagic Design/DaVinci Resolve lucaslucasdeMacBook-Pro DaVinci Resolve % ll | grep k…

“滴灌”代替“漫灌”:“全链路增长”的百度联盟解

作者 | 曾响铃 文 | 响铃说 2021年时&#xff0c;在平台上的日均收益才不足1000元&#xff0c;日活不足1万&#xff1b; 一年时间不到&#xff0c;现在矩阵产品在平台的日均收入已经翻了90倍&#xff0c;日活翻了25倍。 这是一家白牌资讯媒体“早闻天下事”加入百度联盟后发…

跬智信息(Kyligence)荣获浦东新区人工智能创新应用大赛一等奖

近日&#xff0c;2022 浦东新区人工智能创新应用大赛圆满闭幕。经过层层筛选和考核&#xff0c;跬智信息&#xff08;Kyligence&#xff09;从 113 支团队中脱颖而出&#xff0c;参赛项目“Byzer 面向 DataAI 的低代码开源编程语言”在技术创新性、创意性以及项目的可落地性、可…

spi访问fpga

SPI 外设的三线/四线模式及时钟极性相位可以配置&#xff0c;支持主机/从机、全双工/半双工&#xff0c;传送数据格式可灵活配置&#xff0c;并且有发送空接收满 SPI 错误等中断事件功能配合应用使用&#xff0c;更多功能详见本系列芯片手册的相关章节。 SPI四线模式框图&…

轻松玩转树莓派Pico之五、FreeRTOS体验

树莓派Pico开发板片上主芯片为RP2040单片机&#xff0c;双核 Arm Cortex-M0 处理器&#xff0c;工作主频为133MHz&#xff0c;264K片上SRAM&#xff0c;和2MByteFlash。 这么大的RAM和Flash资源&#xff0c;不跑一下RTOS操作系统实在有些可惜&#xff0c;这次就先体验一下Fre…

基于新型战争策略优化算法的光伏模型优化(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f389;作者研究&#xff1a;&#x1f3c5;&#x1f3c5;&#x1f3c5;本科计算机专业&#xff0c;研究生电气学硕…

vscode通过插件一键运行 c++单元测试

gtest使用初探 简介: 本文在 ubuntu18.04 上实现了 googletest 的全局安装&#xff0c;并在一个 demo项目中演示了使用 cmake 引入了该库&#xff0c;实现了在命令行中运行 c 单元测试&#xff0c;包括运行单个 TEST 函数。另外通过 vscode 的插件 C TestMate&#xff0c; 实现…

MATLAB应用2——MATLAB串口采集加速度计数据

串口保存数据为txt格式&#xff1a; clc ; %死机的时候&#xff0c;在命令窗口输入return %getcominstrhwinfo (serial) %寻找串口 delete(instrfindall) %这句话必须有 global xx; global nn; global data1; global s; xx0; nn500; data1(1:nn)0; sserial(COM8); set(s,Bau…

什么?用Python实用脚本也能实现快速卡通画人物头像,这不就是妥妥的QQ秀嘛。

前言 今天我们就利用Python脚本实现天气查询应用吧。直接开整~ 思路分析 从大量照片/卡通数据中习得照片到卡通画的映射。 开发工具 python版本&#xff1a; 3.6 相关模块&#xff1a; pytorch 1.4 tensorflow-gpu 1.14 face-alignment dlib 1.数据准备 训练数据包括…

Linux入门—Shell常用命令之打包压缩

本文由【正厚软件】沙老师提供 打包压缩命令 1. gzip 命令 命令功能&#xff1a;压缩或展开文件 命令格式&#xff1a; ogzip [ -acdfhlLnNrtvV19 ] [-S 后缀] [ 文件名 ... ] &#xff1a;压缩文件 ogunzip [ -acfhlLnNrtvV ] [-S 后缀] [ 文件名 ... ] &#xff1a;解压缩文件…

离线安装harbor容器镜像仓库单机版(harbor-v2.3.5)

记录&#xff1a;358 场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;离线部署harbor容器镜像仓库单机版&#xff0c;使用Redis为外部缓存、使用PostgreSQL为外部数据库、使用docker-ce操作容器、使用docker-compose操作harbor容器镜像仓库。 版本&#xff1a; 操作系统…

OpenAI chatGPT火爆出圈,世界悄悄发生着变化

OpenAI chatGPT火爆出圈&#xff0c;世界悄悄发生着变化一、为什么突然火起来了&#xff1f;二、ChatGPT功能实例2.1 [AI聊天](https://chat.openai.com/chat)2.2 [AI写新闻稿](https://chat.openai.com/chat)2.3 [AI写代码](https://chat.openai.com/chat)2.4 [AI写论文](http…