Linux 网络:skb 数据管理

news2025/3/12 18:12:31

文章目录

  • 1. 前言
  • 2. skb 数据管理
    • 2.1 初始化
    • 2.2 数据的插入
      • 2.2.1 在头部插入数据
      • 2.2.2 在尾部插入数据
    • 2.2 数据的移除
  • 3. 小结

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. skb 数据管理

数据结构:

/* include/linux/skbuff.h */

struct sk_buff {
	...
	/*
	 * @len: sk_buff 包含的数据长度(sk_buff::tail - sk_buff::data). 
	 *       当在协议层间移动时, @len 会改变: 添加(往下层)或移除(往上层)
	 *		 操作接口 skb_reserve(), skb_put(), skb_push(), skb_pull()
	 */
	unsigned int		len,
				data_len/* 仅用于分片场景下分片(fragment)数据的长度 */;
	__u16			mac_len/* MAC 头部长度 */,
				hdr_len;
	...
	__be16			protocol; /* ETH_P_IP, ... */
	__u16			transport_header; /* 传输层数据偏移 */
	__u16			network_header; /* 网络层数据偏移 */
	__u16			mac_header; /* MAC/Linker 层数据偏移 */
	...
	/* These elements must be at the end, see alloc_skb() for details.  */
	/*
	 * head --> -------------------
	 *         | reserved headroom |
	 * data -->|-------------------|
	 *         |///|
	 *         |///|
	 * tail -->|-------------------|
	 *         |      tailroom     |
	 * end  -->|-------------------|
	 *         |  skb_shared_info  |
	 *         |-------------------|
	 *         |       PAD         |
	 *          -------------------
	 */
	sk_buff_data_t		tail; /* 偏移位置: 指向当前数据的尾部,随数据的添加、移除而改变 */
	sk_buff_data_t		end; /* 偏移位置: 可用数据空间的尾部。end 后还跟有 skb_shared_info */
	unsigned char		*head, /* 数据指针: 指向可用数据空间数据头部 */
						*data; /* 数据指针: 指向当前数据开始位置 */
	unsigned int		truesize; /* sizeof(sk_buff) + size(数据空间大小) + sizeof(skb_shared_info), 由 alloc_skb() 初始化 */
	refcount_t			users; /* sk_buff 引用计数. skb_get(), kfree_skb() 接口影响 */
};

2.1 初始化

创建 skb 时的初始化:

struct sk_buff *skb;
int frame_len; /* 网卡接收的数据帧长度 */

frame_len = ...;
skb = netdev_alloc_skb_ip_align(priv->dev, frame_len);
/* include/linux/skbuff.h */

static inline struct sk_buff *netdev_alloc_skb_ip_align(struct net_device *dev,
		unsigned int length)
{
	return __netdev_alloc_skb_ip_align(dev, length, GFP_ATOMIC);
}

static inline struct sk_buff *__netdev_alloc_skb_ip_align(struct net_device *dev,
		unsigned int length, gfp_t gfp)
{
	struct sk_buff *skb = __netdev_alloc_skb(dev, length + NET_IP_ALIGN, gfp);

	if (NET_IP_ALIGN && skb)
		skb_reserve(skb, NET_IP_ALIGN); /* 在数据头部保留部分空间 */
	return skb;
}
/* net/core/skbuff.c */

struct sk_buff *__netdev_alloc_skb(struct net_device *dev, unsigned int len,
				   gfp_t gfp_mask)
{
	struct page_frag_cache *nc;
	unsigned long flags;
	struct sk_buff *skb;
	bool pfmemalloc;
	void *data;

	/*
	 * 数据长度对齐:
	 * 没有特别定义 NET_SKB_PAD 时,是对齐到 cache 行。
	 */
	len += NET_SKB_PAD;

	...

	/* skb_shared_info 包含在 skb 的数据(长度)内 */
	len += SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); 
	len = SKB_DATA_ALIGN(len);

	...
	data = page_frag_alloc(nc, len, gfp_mask); /* 分配 @len 数据空间 */
	...

	skb = __build_skb(data, len); /* 用 @len 长度数据 @data 构建 skb */
	...

skb_success:
	skb_reserve(skb, NET_SKB_PAD); /* 在数据头部保留部分空间 */
	skb->dev = dev; /* 设定 skb 的 所属的 网卡设备对象 */

skb_fail:
	return skb;
}

struct sk_buff *__build_skb(void *data, unsigned int frag_size)
{
	struct skb_shared_info *shinfo;
	struct sk_buff *skb;
	unsigned int size = frag_size ? : ksize(data);

	/* 创建 skb 对象 */
	skb = kmem_cache_alloc(skbuff_head_cache, GFP_ATOMIC);
	...

	size -= SKB_DATA_ALIGN(sizeof(struct skb_shared_info));

	memset(skb, 0, offsetof(struct sk_buff, tail)); /* skb->tail 之前的所有成员清 0 */
	skb->truesize = SKB_TRUESIZE(size);
	refcount_set(&skb->users, 1);
	skb->head = data;
	skb->data = data;
	skb_reset_tail_pointer(skb); /* 初始化 tail: 指向数据开始的偏移位置,即 skb->data 所在的偏移位置 */
	skb->end = skb->tail + size;
	skb->mac_header = (typeof(skb->mac_header))~0U;
	skb->transport_header = (typeof(skb->transport_header))~0U;

	/* make sure we initialize shinfo sequentially */
	shinfo = skb_shinfo(skb);
	memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
	atomic_set(&shinfo->dataref, 1);

	return skb;
}

我们用一张图来描述 skb 的初始状态:

在这里插入图片描述

skb 数据管理,主要依靠 tail, end, head, data 这几个成员。从图中可见,在初始时:

head: 指向数据的开始位置。
data: 指向可用数据的开始位置,跳过了头部保留空间。
tail: 当前已填充数据的结尾位置偏移。
end: 数据可用空间结尾位置偏移。

2.2 数据的插入

数据插入位置可以是 头部尾部

2.2.1 在头部插入数据

头部插入数据,首先通过 *skb_push() 系列 API 按插入数据长度移动数据位置指针,然后执行数据拷贝。

如进行 IP 数据分片时:

int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
		   int (*output)(struct net *, struct sock *, struct sk_buff *))
{
	...
	skb_reset_transport_header(frag); /* 设置 传输层 数据偏移位置 */
	__skb_push(frag, hlen); /* 往后移动数据指针,在头部为 网络层协议头(L3) 划分一部分空间 */
	skb_reset_network_header(frag); /* 设置 网络层 数据偏移位置 */
	memcpy(skb_network_header(frag), iph, hlen); /* 在 头部 插入 网络层 数据 */
	...
}
/* include/linux/skbuff.h */

/* 设置 传输层(L4) TCP/UDP 头部数据偏移位置 */
static inline void skb_reset_transport_header(struct sk_buff *skb)
{
	skb->transport_header = skb->data - skb->head;
}

/*
 * 从头部增加 @len 长度数据后调用: 
 * . skb->data 后退 @len 个位置 (skb->data -= len)
 * . skb->len 增大 @len
 */
void *skb_push(struct sk_buff *skb, unsigned int len);
static inline void *__skb_push(struct sk_buff *skb, unsigned int len)
{
	skb->data -= len;
	skb->len  += len;
	return skb->data;
}

/* 设置 网络层(L3) IP 头部数据偏移位置 */
static inline void skb_reset_network_header(struct sk_buff *skb)
{
	skb->network_header = skb->data - skb->head;
}

/* 返回 网络层 (L3) IP 头部位置 (struct ip_header *) */
static inline unsigned char *skb_network_header(const struct sk_buff *skb)
{
	return skb->head + skb->network_header;
}

从这里的示例代码可以看到,在头部插入数据,分为两步:

1. 通过 __skb_push() 移动数据指针
2. 然后再将数据拷贝到指定位置

可见,*skb_push() 系列 API 只会移动数据指针,并不会做数据拷贝操作。

2.2.2 在尾部插入数据

尾部插入数据,首先执行数据拷贝,然后通过 *skb_put() 系列 API 按拷贝的数据长度移动 skb 数据位置指针。

如网卡接收数据时:

static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
{
	...
	/* 分配 skb 缓冲(细节见前面分析) */     
	skb = netdev_alloc_skb_ip_align(priv->dev, frame_len);
	/* 拷贝接收的数据(RING BUFFE 中的数据)到 skb 缓冲 */
	skb_copy_to_linear_data(skb,
							rx_q->rx_skbuff[entry]->data,
							frame_len);
	skb_put(skb, frame_len); /* 移动数据指针 */
	...
}
/* include/linux/skbuff.h */

static inline void skb_copy_to_linear_data(struct sk_buff *skb,
					   const void *from,
					   const unsigned int len)
{
	memcpy(skb->data, from, len);
}

#ifdef NET_SKBUFF_DATA_USES_OFFSET
static inline unsigned char *skb_tail_pointer(const struct sk_buff *skb)
{
	return skb->head + skb->tail;
}

static inline void skb_reset_tail_pointer(struct sk_buff *skb)
{
	skb->tail = skb->data - skb->head;
}

static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset)
{
	skb_reset_tail_pointer(skb);
	skb->tail += offset;
}

#else /* NET_SKBUFF_DATA_USES_OFFSET */
static inline unsigned char *skb_tail_pointer(const struct sk_buff *skb)
{
	return skb->tail;
}

static inline void skb_reset_tail_pointer(struct sk_buff *skb)
{
	skb->tail = skb->data;
}

static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset)
{
	skb->tail = skb->data + offset;
}

#endif /* NET_SKBUFF_DATA_USES_OFFSET */
/* net/core/skbuff.c */

/*
 * 增加数据长度,在【尾部】增加数据时使用: 
 * . 前进数据【尾部偏移】 skb->tail += len
 * . 增大数据长度 skb->len += len
 * 返回: 数据旧的尾部位置数据指针。
 */
void *skb_put(struct sk_buff *skb, unsigned int len)
{
	void *tmp = skb_tail_pointer(skb);
	SKB_LINEAR_ASSERT(skb);
	skb->tail += len;
	skb->len  += len;
	if (unlikely(skb->tail > skb->end)) /* 超出了数据尾部空间 */
		skb_over_panic(skb, len, __builtin_return_address(0));
	return tmp;
}
EXPORT_SYMBOL(skb_put);

2.2 数据的移除

处于效率的考虑,skb 数据的移除,并不是真的移除,而是仅仅移动数据指针位置。通过 *skb_pull() 系列 API 执行数据的移除:

/* net/core/skbuff.c */

void *skb_pull(struct sk_buff *skb, unsigned int len)
{
	return skb_pull_inline(skb, len);
}
EXPORT_SYMBOL(skb_pull);
/* include/linux/skbuff.h */

/*
 * 从头部拉取 @len 长度数据后调用: 
 * . skb->data 前进 @len 个位置 (skb->data += len)
 * . skb->len 减小 @len
 */
static inline void *skb_pull_inline(struct sk_buff *skb, unsigned int len)
{
	return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
}

/*
 * 从头部拉取 @len 长度数据后调用: 
 * . skb->data 前进 @len 个位置 (skb->data += len)
 * . skb->len 减小 @len
 */
void *skb_pull(struct sk_buff *skb, unsigned int len);
static inline void *__skb_pull(struct sk_buff *skb, unsigned int len)
{
	skb->len -= len;
	BUG_ON(skb->len < skb->data_len);
	return skb->data += len;
}

3. 小结

本文简答的对 skb 数据管理的最基本情形 - 线性数据的插入删除 - 做了描述,事实上,skb 的数据的管理,远比这个要更复杂,譬如 IP 分片的非线性数据管理skb_shared_info 的管理等等,以后有机会再和大家一起探讨。

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

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

相关文章

wireguard搭配udp2raw部署内网

前言 上一篇写了使用 wireguard 可以非常轻松的进行组网部署&#xff0c;但是如果服务器厂商屏蔽了 udp 端口&#xff0c;那就没法了 针对 udp 被服务器厂商屏蔽的情况&#xff0c;需要使用一款 udp2raw 或 socat 类似的工具&#xff0c;来将 udp 打包成 tcp 进行通信 这里以…

Qwen/QwQ-32B 基础模型上构建agent实现ppt自动生成

关心Qwen/QwQ-32B 性能测试结果可以参考下 https://zhuanlan.zhihu.com/p/28600079208https://zhuanlan.zhihu.com/p/28600079208 官方宣传上是该模型性能比肩满血版 DeepSeek-R1&#xff08;671B&#xff09;&#xff01; 我们实现一个 使用Qwen/QwQ-32B 自动生成 PowerPoi…

PostgreSQL17(最新版)安装部署

PostgreSQL 17已与2024年9月26日正式发布&#xff01;&#xff01;&#xff01; 一、Postgres概述 官网地址&#xff1a;PostgreSQL: The world’s most advanced open source database Postgres作为最先进的开源数据库&#xff08; the latest version of the world’s most…

【AI大模型智能应用】Deepseek生成测试用例

在软件开发过程中&#xff0c;测试用例的设计和编写是确保软件质量的关键。 然而&#xff0c;软件系统的复杂性不断增加&#xff0c;手动编写测试用例的工作量变得异常庞大&#xff0c;且容易出错。 DeepSeek基于人工智能和机器学习&#xff0c;它能够依据软件的需求和设计文…

【高级篇】大疆Pocket 3加ENC编码器实现无线RTMP转HDMI进导播台

【高级篇】大疆Pocket 3加ENC编码器实现无线RTMP转HDMI进导播台 文章目录 准备工作连接设备RTMP概念ENCSHV2推流地址设置大疆Pocket 3直播设置总结 老铁们好&#xff01; 很久没写软文了&#xff0c;今天给大家带了一个干货&#xff0c;如上图&#xff0c;大疆Pocket 3加ENC编…

机器人交互系统 部署构建

环境要求 Ubuntu 20.04 或更高版本ROS Noetic 或兼容版本Python 3.8 安装步骤 1. 安装ROS环境&#xff08;如未安装&#xff09; sudo apt update sudo apt install ros-noetic-desktop-full source /opt/ros/noetic/setup.bash2. 创建工作空间并克隆代码 mkdir -p ~/code…

创建模式-工厂方法模式(Factory Method Pattern)

江城子乙卯正月二十日夜记梦 目的动机简单工厂示例代码 目的 定义一个创建对象的接口&#xff0c;该接口的子类具体负责创建具体的对象。工厂方法模式将对象的实例化延迟到子类。简单工厂是直接在创建方法中负责所有的产品的生成&#xff0c;造成该方法臃肿&#xff0c;并且当…

【eNSP实战】交换机配置端口隔离

交换机端口隔离可以实现在同一个VLAN内对端口进行逻辑隔离&#xff0c;端口隔离分为L2层隔离和L3层隔离&#xff0c;这里只进行L2层隔离演示。 拓扑图 路由器AR1配置GE 0/0/1配置IP&#xff0c;其余PC主机各自配置IP和网关。 现将PC1到PC4四个主机全部进行L2层隔离&#xff0c…

人脸识别之数据集中 PI20 和 CFMT 之间关联的模型预测贝叶斯(Python+论文代码实现)

代码文件&#xff08;联系作者点击这里末尾&#xff09; 代码文件描述如下&#xff1a; subjective_objective.ipynb和&#xff1a;这分别是实际的笔记本和 Web 浏览器友好的只读版本。此笔记本读取数据&#xff0c;执行一些预处理&#xff0c;并包含论文中使用的模型规范。它…

静态时序分析:无法满足的生成时钟(TIM-255警告、UITE-461或PTE-075错误)

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 在阅读本文前&#xff0c;强烈建议首先阅读介绍生成时钟的文章&#xff0c;尤其是其中关于时钟极性和反相的相关内容。 静态时序分析&#xff1a;SDC约束命令cr…

VSTO(C#)Excel开发2:Excel对象模型和基本操作

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

途游游戏25届AI算法岗内推

熟悉常用的编程语言&#xff0c;如Python、R等&#xff0c;具有良好的编码和调试能力&#xff1b;对常用的机器学习算法和深度学习框架&#xff08;如TensorFlow、PyTorch等&#xff09;有深入理解&#xff0c;对大型语言模型有一定了解&#xff0c;具备模型部署和微调的实践经…

【数据分析大屏】基于Django+Vue汽车销售数据分析可视化大屏(完整系统源码+数据库+开发笔记+详细部署教程+虚拟机分布式启动教程)✅

目录 一、项目背景 二、项目创新点 三、项目功能 四、开发技术介绍 五、项目功能展示 六、权威视频链接 一、项目背景 汽车行业数字化转型加速&#xff0c;销售数据多维分析需求激增。本项目针对传统报表系统交互性弱、实时性差等痛点&#xff0c;基于DjangoVue架构构建…

OpenCV应用:三种图像风格化案例

OpenCV 本身主要用于计算机视觉任务&#xff0c;例如图像处理、边缘检测、物体识别等&#xff0c;虽然它并不直接提供像 Photoshop 或其他艺术设计软件那样的 "风格化" 功能&#xff0c;但你可以通过一些图像处理技术在 OpenCV 中实现不同风格化效果。 1. 卡通化效果…

【Axure资料】110套优质可视化大屏模板+图表组件+科技感元件等

本作品集包含110套高保真可视化大屏模板&#xff0c;以及丰富的图表组件和科技感元件&#xff0c;旨在满足各行业对数据可视化展示的需求。所有模板和组件均基于Axure RP 9软件设计&#xff0c;确保高质量的交互体验和视觉效果。 作品类型&#xff1a;Axure原型模板 兼容版本&…

VanillaVueSvelteReactSolidAngularPreact前端框架/库的简要介绍及其优势

VanillaVueSvelteReactSolidAngularPreact前端框架/库的简要介绍及其优势。以下是这些前端框架/库的简要介绍及其优势&#xff1a; 1. Vanilla 定义&#xff1a;Vanilla 并不是一个框架&#xff0c;而是指 原生 JavaScript&#xff08;即不使用任何框架或库&#xff09;。优势…

Oracle 字符类型对比

本文以 Oracle12c 为例 1.主要区别对比 类型存储方式最大长度字符集支持适用场景备注​CHAR(M)固定长度空格填充2000 字节&#xff0c;M 代表字节长度默认字符集固定长度编码实际存储长度固定为定义长度&#xff08;如 CHAR(10) 始终占 10 字节&#xff09;​VARCHAR2(M)可变长…

阿里云操作系统控制台实战评测:提升云资源管理与监控效率

文章目录 前言产品介绍操作系统控制台体验阿里云操作系统开通 帮助与总结建议 前言 随着云计算和虚拟化技术的发展&#xff0c;操作系统控制台作为运维管理的核心工具之一&#xff0c;在现代IT环境中发挥着越来越重要的作用。它提供了一种更加直观、高效的方式来管理操作系统&…

Linux本地部署deepseek及SpringBoot集成deepseek

一、ollama安装 本文以linux安装为例(服务器主机ip&#xff1a;192.168.109.210) 1、自动安装(方式一) curl -fsSL https://ollama.com/install.sh | sh 2、手动安装(方式二) (1)下载二进制文件 curl -L https://ollama.com/download/ollama-linux-amd64.tgz -o ollama-linu…

用物理信息神经网络(PINN)解决实际优化问题:全面解析与实践

摘要 本文系统介绍了物理信息神经网络&#xff08;PINN&#xff09;在解决实际优化问题中的创新应用。通过将物理定律与神经网络深度融合&#xff0c;PINN在摆的倒立控制、最短时间路径规划及航天器借力飞行轨道设计等复杂任务中展现出显著优势。实验表明&#xff0c;PINN相比…