Linux 网络发包流程
哈喽大家好,我是咸鱼
之前咸鱼在《Linux 网络收包流程》一文中介绍了 Linux 是如何实现网络接收数据包的
简单回顾一下:
- 数据到达网卡之后,网卡通过 DMA 将数据放到内存分配好的一块
ring buffer
中,然后触发硬中断 - CPU 收到硬中断之后简单的处理了一下(分配
skb_buffer
),然后触发软中断 - 软中断进程
ksoftirqd
执行一系列操作(例如把数据帧从ring ruffer
上取下来)然后将数据送到三层协议栈中 - 在三层协议栈中数据被进一步处理发送到四层协议栈
- 在四层协议栈中,数据会从内核拷贝到用户空间,供应用程序读取
- 最后被处在应用层的应用程序去读取
当 Linux 要发送一个数据包的时候,这个包是怎么从应用程序再到 Linux 的内核最后由网卡发送出去的呢?
那么今天咸鱼将会为大家介绍 Linux 是如何实现网络发送数据包
发包流程
假设我们的网卡已经启动好(分配和初始化 RingBuffer) 且 server 和 client 已经建立好 socket
这里需要注意的是,网卡在启动过程中申请分配的 RingBuffer 是有两个:
igb_tx_buffer
数组:这个数组是内核使用的,用于存储要发送的数据包描述信息,通过vzalloc
申请的e1000_adv_tx_desc
数组:这个数组是网卡硬件使用的,用于存储要发送的数据包,网卡硬件可以通过 DMA 直接访问这块内存,通过dma_alloc_coherent
分配
igb_tx_buffer
数组中的每个元素都有一个指针指向e1000_adv_tx_desc
这样内核就可以把要发送的数据填充到
e1000_adv_tx_desc
数组上然后网卡硬件会直接从
e1000_adv_tx_desc
数组中读取实际数据,并将数据发送到网络上
拷贝到内核
- socket 系统调用将数据拷贝到内核
应用程序首先通过 socket 提供的接口实现系统调用
我们在用户态使用的 send
函数和 sendto
函数其实都是 sendto
系统调用实现的
send/sendto
函数 只是为了用户方便,封装出来的一个更易于调用的方式而已
/* sendto 系统调用 省略了一些代码 */
SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,unsigned int, flags, struct sockaddr __user *, addr,int, addr_len)
{...sock = sockfd_lookup_light(fd, &err, &fput_needed);...err = sock_sendmsg(sock, &msg, len);...
}
在 sendto
系统调用内部,首先 sockfd_lookup_light
函数会查找与给定文件描述符(fd)关联的 socket
接着调用 sock_sendmsg
函数(sock_sendmsg ==> __sock_sendmsg ==> __sock_sendmsg_nosec
)
其中 sock->ops->sendmsg
函数实际执行的是 inet_sendmsg
协议栈函数
/*
__sock_sendmsg_nosec 函数iocb:指向与 I/O 操作相关的结构体 kiocb
sock: 指向要执行发送操作的套接字结构体
msg: 指向存储要发送数据的消息头结构体 msghdr
size: 要发送的数据大小*/
static inline int __sock_sendmsg_nosec(struct kiocb *iocb, struct socket *sock,struct msghdr *msg, size_t size)
{...return sock->ops->sendmsg(iocb, sock, msg, size);
}
这时候内核会去找 socket 上对应的具体协议发送函数
以 TCP 为例,具体协议发送函数为 tcp_sendmsg
tcp_sendmsg
会去申请一个内核态内存 skb(sk_buff)
,然后挂到发送队列上(发送队列是由 skb 组成的一个链表)
接着把用户待发送的数据拷贝到 skb 中,拷贝之后会触发【发送】操作
这里说的发送是指在当前上下文中,待发送数据从 socket 层发送到传输层
需要注意的是,这时候不一定开始真正发送,因为还要进行一些条件判断(比如说发送队列中的数据已经超过了窗口大小的一半)
只有满足了条件才能够发送,如果没有满足条件这次系统调用就可能直接返回了
网络协议栈处理
- 传输层处理
接着数据来到了传输层
传输层主要看 tcp_write_xmit
函数,这个函数处理了传输层的拥塞控制、滑动窗口相关的工作
该函数会根据发送窗口和最大段大小等因素计算出本次发送的数据大小,然后将数据封装成 TCP 段并发送出去
如果满足窗口要求,设置 TCP 头然后将数据传到更低的网络层进行处理
在传输层中,内核主要做了两件事:
- 复制一份数据(skb)
为什么要复制一份出来呢?因为网卡发送完成之后,skb 会被释放掉,但 TCP 协议是支持丢失重传的
所以在收到对方的 ACK 之前必须要备份一个 skb 去为重传做准备
实际上一开始发送的是 skb 的拷贝版,收到了对方的 ACK 之后系统才会把真正的 skb 删除掉
- 封装 TCP 头
系统会根据实际情况添加 TCP 头封装成 TCP 段
这里需要知道的是:每个 skb 内部包含了网络协议中的所有头部信息,例如 MAC 头、IP 头、TCP/UDP 头等
在设置这些头部时,内核会通过调整指针的位置来填充相应的字段,而不是频繁申请和拷贝内存
比如说在设置 TCP 头的时候,只是把指针指向 skb 的合适位置。后面再设置 IP 头的时候,在把指针挪一挪就行
这种方式利用了 skb 数据结构的链表特性可以避免内存分配和数据拷贝所带来的性能开销,从而提高数据传输的效率
- 网络层处理
数据离开了传输层之后,就来到了网络层
网络层主要做下面的事情:
- 路由项查找:
根据目标 IP 地址查找路由表,确定数据包的下一跳( ip_queue_xmit
函数)
- IP 头设置:
根据路由表查找的结果,设置 IP 头中的源和目标 IP 地址、TTL(生存时间)、IP 协议等字段
- netfilter 过滤:
netfilter 是 Linux 内核中的一个框架,用于实现数据包的过滤和修改
在网络层,netfilter 可以用于对数据包进行过滤、NAT(网络地址转换)等操作
- skb 切分:
如果数据包的大小超过了 MTU(最大传输单元),需要将数据包进行切分成多个片段,以适应网络传输,每个片段会被封装成单独的 skb
- 数据链路层处理
当数据来到了数据链路层之后,会有两个子系统协同工作,确保数据包在发送和接收过程中能够正确地对数据进行封装、解析和传输
- 邻居子系统
管理和维护主机或路由器与其它设备之间的邻居关系
邻居子系统里会发送 arp 请求找邻居,然后把邻居信息存在邻居缓存表里,用于存储目标主机的 MAC 地址
当需要发送数据包到某个目标主机时,数据链路层会首先查询邻居缓存表,以获取目标主机的 MAC 地址,从而正确地封装数据包(封装 MAC 头)
- 网络设备子系统
网络设备子系统负责处理与物理网络接口相关的操作,包括数据包的封装和发送,以及从物理接口接收数据包并进行解析
网络设备子系统不但处理数据包的格式转换,如在以太网中添加帧头和帧尾,以及从帧中提取数据
还负责处理硬件相关的操作,如发送和接收数据包的时钟同步、物理层错误检测等
- 到达网卡发送队列
接着网络设备子系统会选择一个合适的网卡发送队列并把 skb 添加到队列中(绕过软中断处理程序)
然后,内核会调用网卡驱动的入口函数 dev_hard_start_xmit
来触发数据包的发送
在一些情况下,邻居子系统还会将 skb 数据包添加到软中断队列(softnet_data)上,并触发软中断(NET_TX_SOFTIRQ)
这个过程是为了将 skb 数据包交给软中断处理程序进行进一步处理和发送。软中断处理程序会负责实际的数据包发送
这就是为什么一般服务器上查看 /proc/softirqs
,一般 NET_RX 都要比 NET_TX 大的多的原因之一
即对于收包来说,都是要经过 NET_RX 软中断;而对于发包来说,只有某些情况下才触发 NET_TX 软中断
网卡驱动发送
驱动程序从发送队列中读取 skb 的描述信息,将其挂到 RingBuffer 上(前面提到的igb_tx_buffer
数组)
接着将 skb 的描述信息映射到网卡可访问的内存 DMA 区域中(前面提到的e1000_adv_tx_desc
数组)
网卡会直接从 e1000_adv_tx_desc
数组中根据描述信息读取实际数据并将数据发送到网络。这样就完成了数据包的发送过程
收尾工作
当数据发送完成后,网卡设备会触发一个硬件中断(NET_RX_SOFTIRQ),这个硬中断通常称为“发送完成中断”或者“发送队列清理中断”
这个硬中断的主要作用是执行发送完成的清理工作,包括释放之前为数据包分配的内存,即释放 skb 内存和 RingBuffer 内存
最后,当收到这个 TCP 报文的 ACK 应答时,传输层就会释放原始的 skb(前面有讲到发送的其实是 skb 的拷贝版)
可以看到,当数据发送完成以后,通过硬中断的方式来通知驱动发送完毕,而这个中断类型是
NET_RX_SOFTIRQ
前面我们讲到过网卡收到一个网络包的时候,会触发
NET_RX_SOFTIRQ
中断去告诉 CPU 有数据要处理也就是说,无论是网卡接收一个网络包还是发送网络包结束之后,触发的都是
NET_RX_SOFTIRQ
总结
最后总结一下在 Linux 系统中发送网络数据包的流程:
- 应用程序通过 socket 提供的接口进行系统调用,将数据从用户态拷贝到内核态的 socket 缓冲区中
- 网络协议栈从 socket 缓冲区中拿取数据,并按照 TCP/IP 协议栈从上到下逐层处理
- 传输层处理:以 TCP 为例,在传输层中会复制一份数据(为了丢失重传),然后为数据封装 TCP 头
- 网络层处理:选取路由(确认下一跳的 IP)、填充 IP 头、netfilter 过滤、对超过 MTU 大小的数据包进行分片等操作
- 邻居子系统和网络设备子系统处理:在这里数据会被进一步处理和封装,然后被添加到网卡的发送队列中
- 驱动程序从发送队列中读取 skb 的描述信息然后挂在 RingBuffer 上,接着将 skb 的描述信息映射到网卡可访问的内存 DMA 区域中
- 网卡将数据发送到网络
- 当数据发送完成后触发硬中断,释放 skb 内存和 RingBuffer 内存
相关文章:

Linux 网络发包流程
哈喽大家好,我是咸鱼 之前咸鱼在《Linux 网络收包流程》一文中介绍了 Linux 是如何实现网络接收数据包的 简单回顾一下: 数据到达网卡之后,网卡通过 DMA 将数据放到内存分配好的一块 ring buffer 中,然后触发硬中断CPU 收到硬中…...

Python web实战之Django的AJAX支持详解
关键词:Web开发、Django、AJAX、前端交互、动态网页 今天和大家分享Django的AJAX支持。AJAX可实现在网页上动态加载内容、无刷新更新数据的需求。 1. AJAX简介 AJAX(Asynchronous JavaScript and XML)是一种在网页上实现异步通信的技术。通过…...

spring boot实现实体类参数自定义校验
安装依赖项 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>1、新建实体类 Data public class UserEntity {private String name;private Integer age;…...

网络安全威胁与防御策略
第一章:引言 随着数字化时代的快速发展,网络已经成为人们生活和工作中不可或缺的一部分。然而,网络的广泛应用也引发了一系列严峻的网络安全威胁。恶意软件、网络攻击、数据泄露等问题层出不穷,给个人和企业带来了巨大的风险。本文…...

C++:哈希表——模拟散列表
模拟散列表 维护一个集合,支持如下几种操作: 1.“I x”,插入一个数x 2.“Q x”,询问数x是否在集合中出现过 现在要进行N次操作,对于每个询问操作输出对应的结果 输入格式 第一行包含整数N,表示操作数量 …...

项目配置中心介绍
目录 什么是配置中心 为什么要有配置中心 配置中心的做法(读取和通知) 配置中心优点: 常用的配置中心中间件 什么是配置中心 配置中心就是用来管理项目当中所有配置的系统,也是微服务系统当中不可或缺的一部分。项目的配置文件不放到本地…...

14-案例:购物车
综合案例-购物车 需求说明: 1. 渲染功能 v-if/v-else v-for :class 2. 删除功能 点击传参 filter过滤覆盖原数组 3. 修改个数 点击传参 find找对象 4. 全选反选 计算属性computed 完整写法 get/set 5. 统计 选中的 总价 和 数量 计算属性conputed reduce条件求和 6. 持久化到本…...

上海市青少年算法2023年2月月赛(丙组)
上海市青少年算法2023年2月月赛(丙组)T1 格式改写 题目描述 给定一个仅由拉丁字符组成字符序列,需要改写一些字符的大小写,使得序列全部变成大写或全部变成小写,请统计最少修改多少个字符才能完成这项任务。 输入格式 一个字符序列:保证仅由拉丁字符构成 输出格式 单个整…...

jetpack5.0.2 已经安装了 cudnn 和 tensorrt
在平台 jetson Xavier NX 中想使用 cudnn 和 tensorrt。然后自己下载了相应包并解压,拷贝,编译 安装 cudnn 1.下载对应包文件,例如:cudnn-linux-sbsa-8.4.1.50_cuda11.6-archive.tar.xz 2.解压,移动到解压目录&#…...

我的编程语言学习笔记
前言 作为一名编程初学者,我深知学习编程需要不断积累和记录。在这篇博客文章中,我将分享一些我在学习C/C编程语言过程中记录的常用代码、特定函数、复杂概念以及特定功能。希望能与大家一起切磋进步! 常用代码: 1. 输入输出操作…...

一个DW的计算
一个DW的计算 1- 题目: 已知一个DW1.1 要求: 从DW中取出指定的位的值1.1.1 分析1.1.2 实现1.1.3 简化实现1.1.4 验证 2- 题目: 已知一个DW2.1 要求: 从DW中的指定的P和S,取出指定的位的值2.1.1 分析2.1.2 实现 1- 题目: 已知一个DW 有图中所示一行信息,表示一个DW(…...

java.net.BindException Address already in use: NET_Bind解决
java.net.BindException Address already in use: NET_Bind 两种解决方法 两种解决方法 (1) kill 占用此端口的线程 查看报错的端口 netstat -ano | findstr 16825tasklist | findstr 1092 如果占用的程序不重要直接kill taskkill /f /pid 16825 (2) 修改启动端口 找一个没…...

JMM内存模型之happens-before阐述
文章目录 一、happens-before的定义二、happens-before的规则1. 程序顺序规则:2. 监视器锁规则:3. volatile变量规则:4. 传递性:5. start()规则:6. join()规则: 一、happens-before的定义 如果一个操作hap…...

大数据课程I2——Kafka的架构
文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握Kafka的架构; ⚪ 掌握Kafka的Topic与Partition; 一、Kafka核心概念及操作 1. producer生产者,可以是一个测试线程,也可以是某种技术框架(比如flume)。 2. producer向kafka生…...

vscode如何汉化
首先我们到vscode官网下载 链接如下: Visual Studio Code - Code Editing. Redefined 根据自己需要的版本下载就好 下载并且安装完毕之后 运行vscode 然后按快捷键 CTRLSHIFTX 打开安装扩展界面 搜索简体中文 安装就可以了 谢谢大家观看...

matlab保存图片
仅作为记录,大佬请跳过。 文章目录 用界面中的“另存为”用saveas 用界面中的“另存为” 即可。 参考 感谢大佬博主文章:传送门 用saveas 必须在编辑器中的plot之后用saveas(也就是不能在命令行中单独使用——比如在编辑器中plot…...

产业园区数字孪生3d可视化全景展示方案
随着数字经济的发展,数字技术给企业发展带来了机遇的同时,也为企业管理带来挑战。比如园区运维,不仅体量大,复杂的运维管理系统,落地难度也较高。那么如何通过数字化手段重塑园区运营,打通园区各业务数据孤…...

centos7 jupyter notebook 安装自动补全插件
激活juoyter notebook的安装环境 conda activate prod执行以下命令安装 pip install jupyter_contrib_nbextensions -i https://pypi.tuna.tsinghua.edu.cn/simple jupyter contrib nbextension install --userpip install jupyter_nbextensions_configurator -i https://py…...

【算法——双指针】LeetCode 202 快乐数
题目描述: 思路:快慢指针 看到循环,我就想起了快慢指针的方法,从题目我们可以看出,我们需要模拟一个过程:不断用当前的数去生成下一个数,生成的规则就是将当前数的各位的平方累加; …...

AndroidManifest清单文件中,Activity的screenOrientation属性详解
screenOrientation用于控制Acivity的屏幕方向,参数有16个。 参数值功能自动旋转打开自动旋转关闭unspecified-1让系统决定Activity的方向,由传感器和系统设置共同决定四个方向不旋转landscape0强制为横屏,忽略传感器和系统设置不旋转不旋转portrait1强制为竖屏,忽略传感器和系统…...

Qt+Pyhton实现麒麟V10系统下word文档读写功能
目录 前言1.C调用python1.1 安装Python开发环境1.2 修改Qt工程配置1.3 初始化Python环境1.4 C 调用Python 函数1.5 常用的Python接口 2.python虚拟环境2.1Python虚拟环境简介2.2 virtualenv 安装及使用2.3 在C程序中配置virtualenv 虚拟环境 3.python-docx库的应用4.总结 前言 …...

TCP/IP 下的计算机网络江湖
〇、引言 在当今数字化时代,计算机网络宛如广袤江湖,涵盖着五大门派:物理层、数据链路层、网络层、传输层和应用层。每个门派独具技能,共同构筑着现代网络的框架。物理层宛如江湖基石,将比特流传输;数据链路层如武林传承,组织数据帧传递;网络层则像导航大师,寻找传送路…...

智能家居(4)---火灾报警线程封装
封装火灾报警线程实现智能家居中的火灾报警功能 mainPro.c(主函数) #include <stdio.h> #include "controlDevice.h" #include "inputCommand.h"#include <pthread.h>struct Devices *pdeviceHead NULL; …...

C#语音播报问题之 无法嵌入互操作类型SpVoiceClass,请改用适用的窗口
C#语音播报问题之 无法嵌入互操作类型SpVoiceClass,请改用适用的窗口 解决办法如下: 只需要将引入的Interop.SpeechLib的属性嵌入互操作类型改为false 改为false 即可解决!...

C语言实例_获取文件MD5值
一、MD5介绍 MD5(Message Digest Algorithm 5)是一种常用的哈希函数算法。将任意长度的数据作为输入,并生成一个唯一的、固定长度(通常是128位)的哈希值,称为MD5值。MD5算法以其高度可靠性和广泛应用而闻名…...

Win11环境下 Unity个人版无法激活
网上教程大多都是在win10环境下运行,win11环境下遇到很多没有碰到的问题,故简单做个记录,也方便同样使用win11的朋友解决问题。 Unity2021无法打开 问题描述:下载Unity2021.3.4f1c1版本(LTS)后࿰…...

C++:模拟实现list及迭代器类模板优化方法
文章目录 迭代器模拟实现 本篇模拟实现简单的list和一些其他注意的点 迭代器 如下所示是利用拷贝构造将一个链表中的数据挪动到另外一个链表中,构造两个相同的链表 list(const list<T>& lt) {emptyinit();for (auto e : lt){push_back(e);} }void test_…...

k8s整合istio配置gateway入口、配置集群内部服务调用管理
一、 istio gateway使用demo kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata:name: ngdemo-gatewaynamespace: ssx spec:selector:istio: ingressgateway # use Istio default gateway implementationservers:- port:numbe…...

工程监测振弦采集仪采集到的数据如何进行分析和处理
工程监测振弦采集仪采集到的数据如何进行分析和处理 振弦采集仪是一个用于测量和记录物体振动的设备。它通过测量物体表面的振动来提取振动信号数据,然后将其转换为数字信号,以便进行分析和处理。在实际应用中,振弦采集仪是广泛应用于机械、建…...

(三)行为模式:2、命令模式(Command Pattern)(C++示例)
目录 1、命令模式(Command Pattern)含义 2、命令模式的UML图学习 3、命令模式的应用场景 4、命令模式的优缺点 5、C实现命令模式的实例 1、命令模式(Command Pattern)含义 命令模式(Command)ÿ…...