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强制为竖屏,忽略传感器和系统…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...

免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...
Linux安全加固:从攻防视角构建系统免疫
Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...