Linux 网络发包流程

news2024/11/24 18:48:31

哈喽大家好,我是咸鱼

之前咸鱼在《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 内存

在这里插入图片描述

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

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

相关文章

跨境外贸业务,选择动态IP还是静态IP?

在跨境业务中,代理IP是一个关键工具。它们提供了匿名的盾牌,有助于克服网络服务器针对数据提取设置的限制。无论你是需要经营管理跨境电商店铺、社交平台广告投放,还是独立站SEO优化,代理IP都可以让你的业务程度更加丝滑&#xff…

神经网络基础-神经网络补充概念-54-softmax回归

概念 Softmax回归(Softmax Regression)是一种用于多分类任务的机器学习算法,特别是在神经网络中常用于输出层来进行分类。它是Logistic回归在多分类问题上的推广。 原理 Softmax回归的主要思想是将原始的线性分数(得分&#xf…

【学习日记】【FreeRTOS】任务调度时如何考虑任务优先级——任务的自动切换

写在前面 本文开始为 RTOS 加入考虑任务优先级的自动调度算法,代码大部分参考野火。 本文主要是一篇学习笔记,加入了笔者自己对野火代码的梳理和理解。 一、基本思路 首先我们要知道,在 RTOS 中,优先级越高、越需要被先执行的的…

小程序商品如何指定人员

一般而言,商家小程序中有很多商品,不同商品可能由不同的供应商提供。当客户购买商品时,如何直接将订单发给不同的供应商呢?下面就来具体介绍一下。 1. 设置订单分发模式。在 订单管理->待处理订单 后面点击设置按钮&#xff0…

cve-2016-7193:wwlib 模块堆数据结构溢出

简介 漏洞编号:cve-2016-7193漏洞类型:堆溢出软件名称:Office模块名称:wwlib历史漏洞:较多影响的版本 攻击利用:APT 攻击利器-Word 漏洞 CVE-2016-7193 原理揭秘 操作环境 系统:Win10 1607软…

编译器过程

编译器过程 如果这个框架对应LLVM,为什么这么说LLVM是个框架呢?是因为它提供了中间表示的定义,即前端输出的文本格式定义. 那么 "前端" 可以是两者其一 : Clang 或者 LLVM-GCC "通用优化" 和 "x86后端" 是 LLVM 提供的. // LLVM 也提供 riscv后…

网络机顶盒什么牌子好?自费5000+测评整理网络机顶盒排行榜

在挑选网络机顶盒的时候很多人贪便宜选山寨杂牌,买回家问题频发,我做数码测评几年来身边的朋友们总会问我网络机顶盒什么牌子好,我自费购入了将近二十款网络机顶盒,通过软硬件的全方位对比后整理了网络机顶盒排行榜TOP5&#xff1…

CentOS7配置yum清华源、阿里源

CentOS7配置yum清华源、阿里源 本文为自己安装记录回顾用 下面的是Centos7 更换yum清华源、阿里源 Centos7默认的服务器是在国外,连接很慢。 更换成国内的镜像源,使用yum清华源、阿里源,连接就会快一点 下面介绍更换方法 前提:打…

SSD202D-logo分区添加dtb

SSD202D-kernel-uimage后面加入dtb_旋风旋风的博客-CSDN博客 1.由于内核的uimage老是压缩解压缩,拿到压缩包里面dtb实在困难; 2.把dtb烧在后面又有安全隐患;而且还会有打包升级方法ota之类的很多;又毙掉了, 3.最后直接把dtb放在logo的包里,但是logo包要想添加好,也要深刻的理…

【小梦C嘎嘎——启航篇】string常用接口的模拟实现

【小梦C嘎嘎——启航篇】string常用接口的模拟实现😎 前言🙌string 模拟实现1、iterator 迭代器相关使用函数实现2、构造函数接口实现3、 传统写法——拷贝构造函数接口实现4、 现代写法——拷贝构造函数接口实现5、析构函数接口实现6、传统写法—— 赋…

开源网盘空间本地挂载神器,挂载百度、阿里云盘、OneDrive等云盘到本地工具-AList

开源网盘空间本地挂载神器,挂载百度、阿里云盘、OneDrive等云盘到本地工具-AList 什么是Alist 一个支持多种存储,支持网页浏览和 WebDAV 的文件列表程序,由 gin 和 Solidjs 驱动。 AList 是一款免费开源支持多存储的自建网盘程序 (文件列表…

吃肉原创——使用PYQT设计的yolov8目标检测GUI界面

需要快速编写一个GUI图形界面 pip install pyqt5 pip install pyqt5-tools然后去conda环境中查找启动程序 F:\APP\miniconda\envs\yolov8gui\Lib\site-packages\qt5_applications\Qt\bin\designer.exe双击可以启动,我们可以把它发送到桌面快捷方式 准备设计图&am…

最通俗易懂的 - Tomcat 核心源码仿写 第二版代码

– 更新信息 – 第一版代码实现了基本的交互功能,但只实现了单线程,此次迭代修改多线程,并升级为Maven项目,同时优化代码排版,提高代码可读性 第一版代码介绍博客地址:最通俗易懂的 - Tomcat 核心源码仿写…

odoo-035 Pycharm git commit 提交提示 No changes detected

文章目录 问题查找解决其他? 问题 在 gitee 上面新建的 git 项目,dowanload 下来,在 Pycharm 中修改后发现改完就变成白色到了,不是绿色或蓝色的,然后 git commit 的时候提示 No changes detected。 查找 上面是在 …

spring bean创建总览 1

1 开始 这是一个总图 下边慢慢看 我们最基础的写的方式就是xml的方式去写 像这样, 而我们会通过applicationContext的方式去获得我们的bean ,我其中一篇博客就写到了applicationContext他的父类就是beanFactory 但是中间的是怎么样处理的呢&#xff1f…

springboot、java实现调用企业微信接口向指定用户发送消息

因为项目的业务逻辑需要向指定用户发送企业微信消息&#xff0c;所以在这里记录一下 目录 引入相关依赖创建配置工具类创建发送消息类测试类最终效果 引入相关依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-…

从 Ansible Galaxy 使用角色

从 Ansible Galaxy 使用角色 根据下列要求&#xff0c;创建一个名为 /home/curtis/ansible/roles.yml 的 playbook &#xff1a; playbook 中包含一个 play&#xff0c; 该 play 在 balancers 主机组中的主机上运行并将使用 balancer 角色。 此角色配置一项服务&#xff0c;以…

代码随想录算法训练营第63天|单调栈part02|503.下一个更大元素II、 42. 接雨水

代码随想录算法训练营第63天&#xff5c;单调栈part02&#xff5c;503.下一个更大元素II、 42. 接雨水 503.下一个更大元素II 503.下一个更大元素II 思路&#xff1a; 如何处理循环数组 相信不少同学看到这道题&#xff0c;就想那我直接把两个数组拼接在一起&#xff0c;然…

银河麒麟服务器v10 sp1 .Net6.0 上传文件错误

上一篇&#xff1a;银河麒麟服务器v10 sp1 部署.Net6.0 http https_csdn_aspnet的博客-CSDN博客 .NET 6之前&#xff0c;在Linux服务器上安装 libgdiplus 即可解决&#xff0c;libgdiplus是System.Drawing.Common原生端跨平台实现的主要提供者&#xff0c;是开源mono项目。地址…

小红书美妆护肤种草推广:深度剖析与实战策略

在这个平台上&#xff0c;用户可以分享自己的购物心得和产品评价&#xff0c;为其他消费者提供购买参考。这种基于用户真实体验的分享&#xff0c;更容易赢得消费者的信任&#xff0c;从而提高产品的购买转化率。 小红书俨然成为了美妆护肤品牌种草推广的主要战场&#xff0c;…