linux网络协议栈2--网络包接收发送流程

news2024/11/14 20:57:28

上文我们讲了报文格式,应该对数据传输格式有了一定了解,这篇文章主要讲述的是网络包接收和发送的流程,主要是大方面来介绍。

网络包接收流程

  • 当网络数据帧通过网络传输到达网卡时,网卡会将网络数据帧通过DMA的方式放到环形缓冲区RingBuffer中。
    RingBuffer是网卡在启动的时候分配和初始化的环形缓冲队列。当RingBuffer满的时候,新来的数据包就会被丢弃。我们可以通过ifconfig命令查看网卡收发数据包的情况。其中overruns数据项表示当RingBuffer满时,被丢弃的数据包。如果发现出现丢包情况,可以通过ethtool命令来增大RingBuffer长度。

  • 当DMA操作完成时,网卡会向CPU发起一个硬中断,告诉CPU有网络数据到达。CPU调用网卡驱动注册的硬中断响应程序。网卡硬中断响应程序会为网络数据帧创建内核数据结构sk_buffer,并将网络数据帧拷贝到sk_buffer中。然后发起软中断请求,通知内核有新的网络数据帧到达。
    sk_buff缓冲区,是一个维护网络帧结构的双向链表,链表中的每一个元素都是一个网络帧。虽然 TCP/IP 协议栈分了好几层,但上下不同层之间的传递,实际上只需要操作这个数据结构中的指针,而无需进行数据复制

  • 内核线程ksoftirqd发现有软中断请求到来,随后调用网卡驱动注册的poll函数,poll函数将sk_buffer中的网络数据包送到内核协议栈中注册的ip_rcv函数中。
    每个CPU会绑定一个ksoftirqd内核线程专门用来处理软中断响应。2个 CPU 时,就会有 ksoftirqd/0 和 ksoftirqd/1这两个内核线程。

    这里有个事情需要注意下: 网卡接收到数据后,当DMA拷贝完成时,向CPU发出硬中断,这时哪个CPU上响应了这个硬中断,那么在网卡硬中断响应程序中发出的软中断请求也会在这个CPU绑定的ksoftirqd线程中响应。所以如果发现Linux软中断,CPU消耗都集中在一个核上的话,那么就需要调整硬中断的CPU亲和性,来将硬中断打散到不通的CPU核上去。

  • 在ip_rcv函数中也就是上图中的网络层,取出数据包的IP头,判断该数据包下一跳的走向,如果数据包是发送给本机的,则取出传输层的协议类型(TCP或者UDP),并去掉数据包的IP头,将数据包交给上图中得传输层处理。
    传输层的处理函数:TCP协议对应内核协议栈中注册的tcp_rcv函数,UDP协议对应内核协议栈中注册的udp_rcv函数。

  • 当我们采用的是TCP协议时,数据包到达传输层时,会在内核协议栈中的tcp_rcv函数处理,在tcp_rcv函数中去掉TCP头,根据四元组(源IP,源端口,目的IP,目的端口)查找对应的Socket,如果找到对应的Socket则将网络数据包中的传输数据拷贝到Socket中的接收缓冲区中。如果没有找到,则发送一个目标不可达的icmp包。

  • 内核在接收网络数据包时所做的工作我们就介绍完了,现在我们把视角放到应用层,当我们程序通过系统调用read读取Socket接收缓冲区中的数据时,如果接收缓冲区中没有数据,那么应用程序就会在系统调用上阻塞,直到Socket接收缓冲区有数据,然后CPU将内核空间(Socket接收缓冲区)的数据拷贝到用户空间,最后系统调用read返回,应用程序读取数据。

网络包发送流程

  • 当我们在应用程序中调用send系统调用发送数据时,由于是系统调用所以线程会发生一次用户态到内核态的转换,在内核中首先根据fd将真正的Socket找出,这个Socket对象中记录着各种协议栈的函数地址,然后构造struct msghdr对象,将用户需要发送的数据全部封装在这个struct msghdr结构体中。

  • 调用内核协议栈函数inet_sendmsg,发送流程进入内核协议栈处理。在进入到内核协议栈之后,内核会找到Socket上的具体协议的发送函数。

    比如:我们使用的是TCP协议,对应的TCP协议发送函数是tcp_sendmsg,如果是UDP协议的话,对应的发送函数为udp_sendmsg。

  • 在TCP协议的发送函数tcp_sendmsg中,创建内核数据结构sk_buffer,将
    struct msghdr结构体中的发送数据拷贝到sk_buffer中。调用tcp_write_queue_tail函数获取Socket发送队列中的队尾元素,将新创建的sk_buffer添加到Socket发送队列的尾部。
    Socket的发送队列是由sk_buffer组成的一个双向链表。

    发送流程走到这里,用户要发送的数据总算是从用户空间拷贝到了内核中,这时虽然发送数据已经拷贝到了内核Socket中的发送队列中,但并不代表内核会开始发送,因为TCP协议的流量控制和拥塞控制,用户要发送的数据包并不一定会立马被发送出去,需要符合TCP协议的发送条件。如果没有达到发送条件,那么本次send系统调用就会直接返回。

  • 如果符合发送条件,则开始调用tcp_write_xmit内核函数。在这个函数中,会循环获取Socket发送队列中待发送的sk_buffer,然后进行拥塞控制以及滑动窗口的管理。

  • 将从Socket发送队列中获取到的sk_buffer重新拷贝一份,设置sk_buffer副本中的TCP HEADER。

    sk_buffer 内部其实包含了网络协议中所有的 header。在设置 TCP HEADER的时候,只是把指针指向 sk_buffer的合适位置。后面再设置 IP HEADER的时候,在把指针移动一下就行,避免频繁的内存申请和拷贝,效率很高。

    为什么不直接使用Socket发送队列中的sk_buffer而是需要拷贝一份呢?
    因为TCP协议是支持丢包重传的,在没有收到对端的ACK之前,这个sk_buffer是不能删除的。内核每次调用网卡发送数据的时候,实际上传递的是sk_buffer的拷贝副本,当网卡把数据发送出去后,sk_buffer拷贝副本会被释放。当收到对端的ACK之后,Socket发送队列中的sk_buffer才会被真正删除。

  • 当设置完TCP头后,内核协议栈传输层的事情就做完了,下面通过调用ip_queue_xmit内核函数,正式来到内核协议栈网络层的处理。

    1. 检查Socket中是否有缓存路由表,如果没有的话,则查找路由项,并缓存到Socket中。接着在把路由表设置到sk_buffer中。
      通过route命令可以查看本机路由配置。

    2. 将sk_buffer中的指针移动到IP头位置上,设置IP头。

    3. 执行netfilters过滤。过滤通过之后,如果数据大于 MTU的话,则执行分片。

    如果你使用 iptables配置了一些规则,那么这里将检测是否命中规则。 如果你设置了非常复杂的 netfilter 规则,在这个函数里将会导致你的线程 CPU 开销会极大增加。

  • 内核协议栈网络层的事情处理完后,现在发送流程进入了到了邻居子系统,邻居子系统位于内核协议栈中的网络层和网络接口层之间,用于发送ARP请求获取MAC地址,然后将sk_buffer中的指针移动到MAC头位置,填充MAC头。

  • 经过邻居子系统的处理,现在sk_buffer中已经封装了一个完整的数据帧,随后内核将sk_buffer交给网络设备子系统进行处理。网络设备子系统主要做以下几项事情:

选择发送队列(RingBuffer)。因为网卡拥有多个发送队列,所以在发送前需要选择一个发送队列。
将sk_buffer添加到发送队列中。
循环从发送队列(RingBuffer)中取出sk_buffer,调用内核函数sch_direct_xmit发送数据,其中会调用网卡驱动程序来发送数据。
以上过程全部是用户线程的内核态在执行,占用的CPU时间是系统态时间(sy),当分配给用户线程的CPU quota用完的时候,会触发NET_TX_SOFTIRQ类型的软中断,内核线程ksoftirqd会响应这个软中断,并执行NET_TX_SOFTIRQ类型的软中断注册的回调函数net_tx_action,在回调函数中会执行到驱动程序函数 dev_hard_start_xmit来发送数据。

注意:当触发NET_TX_SOFTIRQ软中断来发送数据时,后边消耗的 CPU 就都显示在 si这里了,不会消耗用户进程的系统态时间(sy)了。

从这里可以看到网络包的发送过程和接受过程是不同的,在介绍网络包的接受过程时,我们提到是通过触发NET_RX_SOFTIRQ类型的软中断在内核线程ksoftirqd中执行内核网络协议栈接受数据。而在网络数据包的发送过程中是用户线程的内核态在执行内核网络协议栈,只有当线程的CPU quota用尽时,才触发NET_TX_SOFTIRQ软中断来发送数据。

在整个网络包的发送和接受过程中,NET_TX_SOFTIRQ类型的软中断只会在发送网络包时并且当用户线程的CPU quota用尽时,才会触发。剩下的接受过程中触发的软中断类型以及发送完数据触发的软中断类型均为NET_RX_SOFTIRQ。
所以这就是你在服务器上查看 /proc/softirqs,一般 NET_RX都要比 NET_TX大很多的的原因。

  • 现在发送流程终于到了网卡真实发送数据的阶段,前边我们讲到无论是用户线程的内核态还是触发NET_TX_SOFTIRQ类型的软中断在发送数据的时候最终会调用到网卡的驱动程序函数dev_hard_start_xmit来发送数据。在网卡驱动程序函数dev_hard_start_xmit中会将sk_buffer映射到网卡可访问的内存 DMA 区域,最终网卡驱动程序通过DMA的方式将数据帧通过物理网卡发送出去。

  • 当数据发送完毕后,还有最后一项重要的工作,就是清理工作。数据发送完毕后,网卡设备会向CPU发送一个硬中断,CPU调用网卡驱动程序注册的硬中断响应程序,在硬中断响应中触发NET_RX_SOFTIRQ类型的软中断,在软中断的回调函数igb_poll中清理释放 sk_buffer,清理网卡发送队列(RingBuffer),解除 DMA 映射。

无论硬中断是因为有数据要接收,还是说发送完成通知,从硬中断触发的软中断都是 NET_RX_SOFTIRQ。

这里释放清理的只是sk_buffer的副本,真正的sk_buffer现在还是存放在Socket的发送队列中。前面在传输层处理的时候我们提到过,因为传输层需要保证可靠性,所以 sk_buffer其实还没有删除。它得等收到对方的 ACK 之后才会真正删除。

总结

大家有兴趣的话可以细看文字段落,不感兴趣的话看看两张图片,想想其中的流程即可

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

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

相关文章

Flutter轮播图Banner

使用插件:flutter_swiper 实现轮播图 pubspec.yaml 增加 :flutter_swiper : ^lastest_version 在项目文件夹下打开命令行执行:flutter packages get 安装插件 home_page.dart中使用swiper 程序运行:先启动虚拟设备后,执行命令f…

【C语言】linux内核ipoib模块 - ipoib_send

一、ipoib_send函数定义 int ipoib_send(struct net_device *dev, struct sk_buff *skb,struct ib_ah *address, u32 dqpn) {struct ipoib_dev_priv *priv ipoib_priv(dev);struct ipoib_tx_buf *tx_req;int hlen, rc;void *phead;unsigned int usable_sge priv->max_sen…

redis数据安全(一)数据持久化

一、Redis数据安全措施: 1、将数据持久化至硬盘 2、将数据复制至其他机器; 复制是在数据持久化的基础上进行的。 二、将数据持久化至硬盘 1、介绍:Redis是一个基于内存的数据库,它的数据是存放在内存中,内存有个问题就是关闭…

大模型 RAG 面试篇

1.LLMs 存在模型幻觉问题,请问如何处理? 检索LLM。 先用问题在领域数据库里检索到候选答案,再用LLM对答案进行加工。 2.基于LLM向量库的文档对话 思路是怎么样? 加载文件读取文本文本分割文本向量化问句向量化在文本向量中匹配…

构建高效外卖系统:技术实践与代码示例

外卖系统在现代社会中扮演着重要的角色,为用户提供了便捷的用餐解决方案。在这篇文章中,我们将探讨构建高效外卖系统的技术实践,同时提供一些基础的代码示例,帮助开发者更好地理解和应用这些技术。 1. 技术栈选择 构建外卖系统…

Python爬虫从入门到入狱系列合集

我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈 入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈 虚 拟 环 境 搭 建 :👉&…

【Qt5】QString的成员函数chop

2024年1月19日&#xff0c;周五下午 QString 的 chop 方法用于从字符串的末尾移除指定数量的字符。这个方法会修改原始字符串&#xff0c;并返回 void。 下面是一个简单的示例&#xff1a; #include <QString> #include <QDebug>int main() {QString originalStr…

python使用jupyter记笔记

目录 一、安装 二、运行jupyter 三、使用 四、记笔记 Jupyter Notebook&#xff08;此前被称为 IPython notebook&#xff09;是一个交互式笔记本&#xff0c;支持运行 40 多种编程语言。 Jupyter Notebook 的本质是一个 Web 应用程序&#xff0c;便于创建和共享程序文档&a…

接口的返回值中所需信息作为其他接口入参使用(postman与jmeter的使用)

一、背景&#xff1a; 偶尔会用到一个场景&#xff0c;两个接口之前的调用有依赖关系&#xff0c;将其中一个的返回参数中的部分信息取出来作为入参在第二个接口中使用&#xff0c;代码内是比较好实现&#xff0c;只要定义一个变量&#xff0c;用于参数传递。 如果是测试过程中…

java小项目:简单的收入明细记事本,超级简单(不涉及数据库,通过字符串来记录)

一、效果 二、代码 2.1 Acount类 package com.demo1;public class Acount {public static void main(String[] args) {String details "收支\t账户金额\t收支金额\t说 明\n"; //通过字符串来记录收入明细int balance 10000;boolean loopFlag true;//控制循…

关于datagrip的一个错误。Unexpected update count received (Actual: 3, Expected: 1).

这一行原本的值是<null><null><null>,现在我们把它修改为1,114&#xff0c;无名氏&#xff0c;但却报错。 这是对应的sql语句&#xff0c;原因在于有三行全为 <null><null><null>&#xff0c;where无法指定是哪一行&#xff0c;所以看起来…

电子学会C/C++编程等级考试2023年12月(七级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:迷宫 一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由n * n的格点组成,每个格点只有2种状态,.和#,前者表示可以通行后者表示不能通行。同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下…

【RabbitMQ】RabbitMQ安装与使用详解以及Spring集成

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《RabbitMQ实战》。&#x1f3af;&#x1f3af; &am…

GPT应用开发:编写插件获取实时天气信息

欢迎阅读本系列文章&#xff01;我将带你一起探索如何利用OpenAI API开发GPT应用。无论你是编程新手还是资深开发者&#xff0c;都能在这里获得灵感和收获。 本文&#xff0c;我们将继续展示聊天API中插件的使用方法&#xff0c;让你能够轻松驾驭这个强大的工具。 插件运行效…

母婴品牌找小红书达人卖货怎么做?

小红书母婴种草推广&#xff0c;就是品牌方找小红书上的达人进行产品体验和内容分享&#xff0c;从而达到卖货的目的&#xff0c;软广形式更容易被大众所接受&#xff0c;而且小红书平台上的宝妈用户群体都是有一定消费水平的一二线城市用户&#xff0c;所以这样就会形成一套完…

git提权

实验环境——vulnhub-dc2靶场 git提权 前提&#xff1a;用户可以使用sudo中git权限 查看sudo权限 sudo -l可以发现git命令存在sudo提权 基于此进行权限提升 方式&#xff1a; sudo git help config #在末行命令模式输入 !/bin/bash 或 !sh #完成提权 sudo git -p help…

2023年12月 C/C++(七级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++等级考试(1~8级)全部真题・点这里 第1题:迷宫 一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由n * n的格点组成,每个格点只有2种状态,.和#,前者表示可以通行后者表示不能通行。同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下…

【大坑】MyBatisPlus使用updateById莫名将数据四舍五入了

问题描述 我目前在为本地的一所高中开发一个成绩分析的网站&#xff0c;后端使用的是SpringBootMyBatisPlus&#xff0c;业务逻辑是用户在前端上传EXCEL文件&#xff0c;后端从文件中读取成绩存到数据库用于分析。但是奇怪的是&#xff1a;在后端&#xff0c;进入数据库之前的…

Flink实时数仓同步:拉链表实战详解

一、背景 在大数据领域&#xff0c;业务数据通常最初存储在关系型数据库&#xff0c;例如MySQL。然而&#xff0c;为了满足日常分析和报表等需求&#xff0c;大数据平台会采用多种不同的存储方式来容纳这些业务数据。这些存储方式包括离线仓库、实时仓库等&#xff0c;根据不同…

75.网游逆向分析与插件开发-背包的获取-背包结构与指针的逆向分析

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;物品名称与物品编号的映射关系分析-CSDN博客 通过这个内容以及可以通过物品的id得到一个名字&#xff0c;知道了它的算法&#xff0c;它的算法自己封装好了&#xff0c;我们直接用就好&#xff0c;接…