C 语言网络编程 — 内核协议栈收包/发包流程

目录

文章目录

  • 目录
  • 关键技术
    • DMA
    • sk_buff 结构体
    • Net driver Rx/Tx Ring Buffer
    • Buffer Descriptor Table
    • NAPI 收包机制
    • 网卡多队列
  • 内核协议栈收包/发包流程概览
  • 内核协议栈收包流程详解
    • 驱动程序层(数据链路层)
      • VLAN 协议族
      • Linux Bridge 子系统
    • 网络协议层(L3 子系统)
      • ARP 子系统
      • IP 子系统
    • 网络协议层(L4 子系统)
      • TCP 子系统
    • 协议接口层(BSD Socket 层)
  • 内核协议栈发包流程详解

关键技术

DMA

DMA(Direct Memory Access,直接内存访问)是一种硬件实现的外设 I/O 技术。

在 DMA 技术出现之前,NIC 和 CPU 之间的 Frames(二层数据帧)收发依赖 CPU 先从 NIC Rx/Tx queue 逐个 Copy 到 Kernel space,然后再从 Kernel space 中 Copy 到 User space,单向两次 CPU Copy 的方式,非常消耗资源。

DMA 技术出现后,NIC 增加了 DMA Control 功能,并将 NIC Rx/Tx queue 与 Main Memory 中的 ZONE_DMA 建立直接映射关系。当 Frames 进入 NIC Rx/Tx queue 之后,就会直接被 DMA Controller Copy 到 ZONE_DMA,这一次 Copy 完全不需要 CPU 的参与。并且由于 ZONE_DMA 是一块物理映射区,所以 Kernel space 也可以直接访问。

DMA 技术在单向的外设 I/O 的流程中,减少了一次 CPU Copy 的工作,也以此减轻了 CPU 的工作负载。

在 32bit Linux 中,ZONE_DMA 默认只有 16MB;而在 64bit Linux 中,ZONE_DMA 默认可以有 4GB,得到了非常大的提升。

在这里插入图片描述

DMA Controller 的功能

  • 向 CPU 发出 HOLD(保持)信号,提出 Bus(总线)接管请求。
  • 当 CPU 发出允许接管信号后,负责对 Bus 的控制,进入 DMA I/O 模式。
  • 通过对 Main Memory 进行寻址以及修改地址指针,实现对 Memory 的读写操作。
  • 向 CPU 发出 DMA 结束信号,CPU 恢复正常工作模式。

DMA 信号类型

  • DREQ(外设请求信号):I/O 外设向 DMA Controller 发起请求。
  • DACK(DMA 响应信号):DMA Controller 向 I/O 外设的响应信号
  • HRQ/HOLD(DMA 请求信号):DMA Controller 向 CPU 发出,要求接管 Bus。
  • HLDA(CPU 响应信号):CPU 响应允许 DMA Controller 接管 Bus。

在这里插入图片描述

sk_buff 结构体

sk_buff 结构体是 Kernel 定义的一个用于描述 Frame 的数据结构。

Net driver 的初始化流程中包括对 DMA 空间进行初始化,主要的工作就是在 ZONE_DMA 中分配好用于存储 sk_buff 的内存空间。

当 Frame 到达 NIC 后,DMA Controller 就会将 Frame 的数据 Copy 到 sk_buff 结构体中,以此来完成 Frame => sk_buff 数据格式的封装。在后续的流程中,sk_buff 还会从 ZONE_DMA Copy 到 Kernel Socket Receive Buffer 中,等待 Application 接收。

struct igb_ring {
...
	union {
...
		/* RX */
		struct {
			struct sk_buff *skb;
			struct igb_rx_queue_stats rx_stats;
			struct u64_stats_sync rx_syncp;
		};
...
}

sk_buff 的结构体定义如下图所示,它包含了一个 Frame 的 Interface(网络接口)、Protocol(协议类型)、Headers(协议头)指针、Data(业务数据)指针等信息。

在这里插入图片描述

值得留意的是,在 Kernel 中,一个 Frame 的 Headers 和 Payload 可能是分开存放到不同内存块种的。有以下几点原因:

  1. 两者具有不同的特征和用途:Headers 包含了网络协议的元数据信息,而 Payload 包含了 Application 的业务信息。
  2. 两者具有不同的大小和格式:Headers 的格式通常是标准的,而且数据量比 Payload 小的多。
  3. 两者具有不同的处理逻辑:Headers 需要被快速识别、分析和校验,而 Payload 则需要被快速的传输和存储。

分开存储和处理的方式,可以有效提高网络传输的效率和可靠性。

同时,sk_buff 是一个双向链表数据结构,支持链表操作。

在这里插入图片描述

Net driver Rx/Tx Ring Buffer

Net driver 实现了 2 个 Ring Buffer 用于数据报文的收发处理。

Ring queue 是高性能数据包处理场景中常见的数据结构,它将 Buffer 内存空间设计成一个首尾相连的环。当 Buffer 空间溢满后,新来的 Frames 会从头开始存放,而不是为其分配新的内存空间。相较于传统的 FIFO queue 数据结构,Ring queue 可以避免频繁的内存分配和数据复制,从而提高传输效率。此外还具有缓存友好、易于并行处理等优势。

值得注意的是,Rx/Tx Ring Buffer 中存储的是 sk_buff 的 Descriptor,而不是 sk_buff 本身,本质是一个指针,也称为 Packet Descriptor。

在这里插入图片描述

Packet Descriptor 有 Ready 和 Used 这 2 种状态。初始时 Descriptor 指向一个预先分配好且是空的 sk_buff 空间,处在 Ready 状态。当有 Frame 到达时,DMA Controller 从 Rx Ring Buffer 中按顺序找到下一个 Ready 的 Descriptor,将 Frame 的数据 Copy 到该 Descriptor 指向的 sk_buff 空间中,最后标记为 Used 状态。

这样设计的原因是 Rx/Tx Ring Buffer 作为 I/O 控制单元,不应该持有太多数据量。数据传输由 DMA 实现会非常快,而 Ring Buffer 也只需要记录相应的指针即可。

在这里插入图片描述

Buffer Descriptor Table

Rx/Tx Ring Buffer 的具体实现为一张 Buffer Descriptor Table(BDT)。

BDT 是一个 Table 数据结构,拥有多个 Entries,每条 Entry 都对应了 Ring Buffer 中的一个 Rx/Tx Desc,它们记录了存放 sk_buff 的入口地址、长度以及状态标志位。

在这里插入图片描述

  • 收包时:DMA Controller 搜索 Rx BDT,取出空闲的 DB Entry,将 Frame 存放到 Entry 指向的 sk_buff,修改 Entry 为 Ready。然后 DBT 指针下移一项。
  • 发包时:DMA Controller 搜索 Tx BDT,取出状态为 Ready 的 DB Entry 所指向的 sk_buff 并转化为 Frame 发送出去。然后 DBT 指针下移一项。

在这里插入图片描述

NAPI 收包机制

NAPI(New API)是一种 “中断 + 轮训” 收包机制,相较于传统的单一中断(硬中断 + 软中断)收包的方式效率更高。

在这里插入图片描述

NAPI 的工作流程如下:

  1. Net driver 初始化流程中,注册 NAPI 收包机制所必须的 poll() 函数到 ksoftirqd(软中断处理)内核线程。
  2. Frame 到达 NIC;
  3. DMA Controller 写入 sk_buff;
  4. NIC Controller 发起硬中断,进入 Net driver 的 napi_schedule() 硬中断处理函数,然后将一个 napi_struct 结构体加入到 poll_queue(NAPI 软中断队列)中。此时 NIC Controller 立即禁用了硬中断,开始切换到 NAPI “轮训“ 收包工作模式。
  5. 再进入 raise_softirq_irqoff(NET_RX_SOFTIRQ) 软中断处理程序,唤醒 NAPI 子系统,新建 ksoftirqd 内核线程。
  6. ksoftirqd 线程调用 Net driver 注册的 poll() 函数。
  7. poll() 调用 netif_receive_skb() 开始从 sk_buff 空间中收包,并将它传递给网络协议栈进行处理。
  8. 直到一段时间内没有 Frame 到达为止,关闭 ksoftirqd 内核线程,NIC Controller 重新切换到 NAPI “中断” 收包工作模式。

在具体的实现中,poll() 会轮训检查 BDT Entries 的状态,如果发现当前 BDT 指针指向的 Entry Ready,则将该 Entry 对应的 sk_buff 取出来,并恢复该 Entry 的空闲状态。

可见,和传统方式相比,NAPI 一次中断就可以接收多个包,因此可以减少硬件中断的数量。

在这里插入图片描述

网卡多队列

在以往,一张 NIC 只会提供一组 HW Rx/Tx Ring queue,对应一个 CPU 来进行处理。在多核时代,NIC 也相应的提供了 Multi-Queue 功能,可以将多个 Queue 通过硬中断绑定到不同的 CPU Cores 上处理。

以 Intel 82575 为例。

  • 在硬件层面:它拥有 4 组硬件队列,它们的硬中断分别绑定到 4 个 Core 上,并通过 RSS(Receive Side Scaling)技术实现负载均衡。RSS 技术通过 HASH Packet Header IP 4-tuple(srcIP、srcPort、dstIP、dstPort),将同一条 Flow 总是送到相同的队列,从而避免了报文乱序问题。
    在这里插入图片描述

  • 在软件层面:Linux Kernel v2.6.21 开始支持网卡多队列特性。在 Net driver 初始化流程中,Kernel 获悉 Net device 所支持的硬件队列数量。然后结合 CPU Cores 的数量,通过 Sum=Min(NIC queue, CPU core) 公式计算得出应该被激活 Sum 个硬件队列,并申请 Sum 个中断号,分配给激活的每个队列。

在这里插入图片描述

如上图所示,当某个硬件队列收到 Frames 时,就触发相应的硬中断,收到中断的 CPU Core 就中断处理任务下发给该 Core 的 NET_RX_SOFTIRQ 实例处理(每个 Core 都有一个 NET_RX_SOFTIRQ 实例)。

在 NET_RX_SOFTIRQ 中调用 NAPI 的收包接口,将 Frames 收到具有多个 netdev_queue 的 net_device 结构体中。

在这里插入图片描述

查看网卡是否支持多队列。

$ lspci -vvv

查看 Ethernet controller 的条目内容,如果有 MSI-X: Enable+(MSI-X 指 MSI 数组,Enable+ 指使能),则该网卡是多队列网卡。

在这里插入图片描述

网卡设备有 3 种常见的硬中断类型:

  1. MSI-X
  2. MSI
  3. legacy IRQ

MSI(Message Signaled Interrupts)是 PCI 规范的一个实现,可以突破 CPU 256 条 interrupt 的限制,使每个设备具有多个中断,多队列网卡驱动给每个硬件队列都申请了 MSI。

MSI-X 中断是比较推荐的方式,尤其是对于支持多队列的网卡。因为每个 RX 队列有独立的 MSI-X 中断,因此可以被不同的 CPU 处理。处理中断的 CPU 也是随后处理这个包的 CPU。这样的话,从网卡硬件中断的层面就可以设置让收到的包被不同的 CPU 处理。

内核协议栈收包/发包流程概览

  • 收包流程
    在这里插入图片描述

  • 发包流程
    在这里插入图片描述

  • 收包流程
    在这里插入图片描述

  • 发包流程
    在这里插入图片描述

内核协议栈收包流程详解

驱动程序层(数据链路层)

在这里插入图片描述

  1. NIC Controller 接收到高低电信号,表示 Frame 到达。PHY 芯片首先将电信号转换为比特流,然后 MAC 芯片再将比特流转换为 Frame 格式。

  2. DMA Controller 将 Frame Copy 到 Rx Ring Buffer 中的一个 Rx Desc 指向的 sk_buff 空间。

  3. DMA Controller 更新相应的 BD Entry 状态为 Ready,并将 BDT 指针下移一项。

  4. NIC Controller 给 CPU 的相关引脚上触发一个电压变化,硬中断 CPU。每个硬中断都对应一个中断号,CPU 开始收包硬中断处理程序。硬中断处理程序由 Kernel 回调 Net driver 具体实现的注册函数,根据是否开启了 NAPI 有两条不同的执行路径。

  5. 以 NAPI 模式为例,Net driver 执行 napi_schedule() 硬中断处理函数,然后将一个 napi 结构体加入到 poll_queue(NAPI 软中断队列)中。此时 NIC Controller 立即禁用了硬中断,开始切换到 “NAPI 轮训“ 收包工作模式。

  6. 再进入 raise_softirq_irqoff() 软中断处理程序,唤醒 NAPI 子系统,新建 ksoftirqd 内核线程。

  7. ksoftirqd 线程调用 Net driver 注册的 poll() 函数。

// linux/drivers/net/ethernet/intel/igb/igb_main.c

/**
 *  igb_poll - NAPI Rx polling callback
 *  @napi: napi polling structure
 *  @budget: count of how many packets we should handle
 **/
static int igb_poll(struct napi_struct *napi, int budget)
{
...
	if (q_vector->tx.ring)
		clean_complete = igb_clean_tx_irq(q_vector, budget);

	if (q_vector->rx.ring) {
		int cleaned = igb_clean_rx_irq(q_vector, budget);
...
}
  1. poll() 调用 netif_receive_skb() 开始从 sk_buff 空间中收包,并将它传递给 TCP/IP 网络协议栈进行处理。
// linux/net/core/dev.c

/**
 *	netif_receive_skb - process receive buffer from network
 *	@skb: buffer to process
 *
 *	netif_receive_skb() is the main receive data processing function.
 *	It always succeeds. The buffer may be dropped during processing
 *	for congestion control or by the protocol layers.
 *
 *	This function may only be called from softirq context and interrupts
 *	should be enabled.
 *
 *	Return values (usually ignored):
 *	NET_RX_SUCCESS: no congestion
 *	NET_RX_DROP: packet was dropped
 */
int netif_receive_skb(struct sk_buff *skb)
{
	int ret;

	trace_netif_receive_skb_entry(skb);

	ret = netif_receive_skb_internal(skb);
	trace_netif_receive_skb_exit(ret);

	return ret;
}
  1. 直到一段时间内没有 Frame 到达为止,关闭 ksoftirqd 内核线程,NIC Controller 重新切换到硬中断收包工作模式。

在这里插入图片描述

VLAN 协议族

在这里插入图片描述

Linux Bridge 子系统

在这里插入图片描述

网络协议层(L3 子系统)

在这里插入图片描述

  1. Net driver 调用 netif_receive_skb() 将 sk_buff 从 ZONE_DMA Ring Buffer 中取出并交给 TCP/IP 协议栈处理的过程中,首先会根据 sk_buff 内层 Header 的 Protocol Type 选择相应的处理函数,如果是 IP 协议,则调用 ip_rcv() 进行处理。

在这里插入图片描述

  1. ip_rcv() 首先会解析 IP Header,根据 srcIP 和 dstIP 进入路由子系统处理流程。如果 dstIP 是本机地址,则根据内层 Header 的 Protocol Type 选择相应的传输层处理函数。UDP 对应 udp_rcv()、TCP 对应 tcp_rcv()、ICMP 对应 icmp_rcv()、IGMP 对应 igmp_rcv()。

ARP 子系统

在这里插入图片描述

IP 子系统

在这里插入图片描述

网络协议层(L4 子系统)

在这里插入图片描述

  1. 在使用 socket() 创建一个 TCP Socket 之后,Socket 对应的 Sock 结构体会被注册到一个 tcp_prot 全局变量中,并以 tcp_port 作为 Index。

  2. 当 tcp_rcv() 收到 sk_buff 之后,根据 TCP Header 中的 dstPort 字段索引到相应的 Sock。

  3. 然后将 sk_buff 加入到该 Sock 的 receive_queue 成员所指向的 Socket Receive Buffer 缓冲队列中,等待 Application 通过 read() 等 SCI 来进行读取。

TCP 子系统

在这里插入图片描述

协议接口层(BSD Socket 层)

当 Application 调用 read() 来进行读取时:

  1. 首先会根据 socket fd 查询 file inode,并从中得到相应的 Sock 结构体;
  2. 然后从 Sock 结构体成员 recieve_queue 指向的 Socket Receive Buffer 发起一次 CPU Copy;
  3. CPU 从用户模式转为内核模式,将数据包从 Kernel space Copy 到 User space。

内核协议栈发包流程详解

在这里插入图片描述

以 UDP 数据报为例:

  1. 协议接口层:BSD socket 层的 sock_write() 会调用 INET socket 层的 inet_wirte()。INET socket 层会调用具体传输层协议的 write 函数,该函数是通过调用本层的 inet_send() 来实现的,inet_send() 的 UDP 协议对应的函数为 udp_write()。

  2. L4 子系统:udp_write() 调用本层的 udp_sendto() 完成功能。udp_sendto() 完成 sk_buff 结构体相应的设置和 Header 的填写后会调用 udp_send() 来发送数据。而在 udp_send() 中,最后会调用 ip_queue_xmit() 将数据包下放的网络层。

  3. L3 子系统:函数 ip_queue_xmit() 的功能是将数据包进行一系列复杂的操作,比如是检查数据包是否需要分片,是否是多播等一系列检查,最后调用 dev_queue_xmit() 发送数据。

  4. 驱动程序层:函数调用会调用具体设备提供的发送函数来发送数据包,e.g. hard_start_xmit(skb, dev)。具体设备的发送函数在协议栈初始化的时候已经设置了。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PCB模块化设计01——USB接口详解知识要点

目录PCB模块化设计01——USB接口详解知识要点一、定义二、USB分类:三、传输协议四、USB接口布局布线要求PCB模块化设计01——USB接口详解知识要点 一、定义 USB是通用串行总线(Universal Serial Bus),分为HOST/DEVICE两个角色,所有的数据传…

【C++学习】日积月累——继承详解(1)

一、继承的概念及定义 1.1 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称该类为派生类。…

JavaSE思维导图——总结篇

👨‍💻作者简介:学习时长两年半的java博主 🎟️个人主页:君临๑ ps:点赞是免费的,却可以让写博客的作者开心好几天😎 进入正题。关于Java专栏的规划如下 写作计划:大概一…

【微服务 从0开始 】Spring Cloud 配置文件

🔎这里是【秒懂云原生】,关注我学习云原生不迷路 👍如果对你有帮助,给博主一个免费的点赞以示鼓励 欢迎各位🔎点赞👍评论收藏⭐️ 👀专栏介绍 【秒懂云原生】 目前主要更新微服务,…

抖音本地商家怎么做短视频运营?

抖音作为一款以短视频为核心的本地化社交平台,对于实体店的短视频运营来说,需要注重产品定位、目标人群、短视频制作、发布、私信评论维护和同行客户挖掘等方面。   一、做好产品定位   实体店在进行短视频运营时,首先需要做好产品定位。…

2021蓝桥杯真题图像模糊 C语言/C++

题目描述 小蓝有一张黑白图像,nm 个像素组成,其中从上到下共 n 行,每行从左到右 m 列。每个像素由一个 0 到 255 之间的灰度值表示。 现在,小蓝准备对图像进行模糊操作,操作的方法为: 对于每个像素&#…

首屏加载优化

最近沉迷逛某蓝色软件,收益良多!万分感谢博主 海阔_天空,写的太棒了👍🎉 下面是原文链接,我在原文的基础上浅做个笔记,方便个人快速复习 前端性能优化——首页资源压缩63%、白屏时间缩短86% -…

溯源(五)之攻击源的获取

溯源(一)之溯源的概念与意义 溯源(二)之 windows-还原攻击路径 溯源(三)之Linux-入侵排查 溯源(四)之流量分析-Wireshark使用 溯源整体流程的思维导图 攻击源的获取 1、获取哪些数…

Spring Data JPA

1. Spring Data环境搭建 Spring Data提供了一套统一的基于Spring的数据访问模型,它可以轻松的实现数据库访问,包括各种关系型、非关系型数据库、Map-Reduce框架、云数据服务等。 Spring Data 包含多个子项目: • Commons - 提供共享的基础框架…

ExtScreen,为智能电视和VR设备打造的快应用引擎

和手机相比,智能电视端的生态一直都不怎么行,具体来讲有以下这几个问题: 电视芯片运算能力差,配置普遍不如手机;电视交互基于遥控器,完全不同于触摸屏操作的手机;电视的生态比较封闭&#xff0…

【JavaWeb】Cookie和Session

目录 Cookie Cookie定义 Cookie数据的来源 Cookie数据的存储 Cookie数据的使用 使用Cookie原因 Session Session定义 如何存储数据 Cookie和Session的区别 使用Cookie和Session简单实现登录页面 Cookie Cookie定义 Cookie是浏览器提供持久化存储数据的机制。 Cook…

这么方便吗?用ChatGPT生成Excel(详解步骤)

文章目录前言使用过 ChatGPT 的人都知道,提示占据非常重要的位置。而 Word,Excel、PPT 这办公三大件中,当属 Excel 最难搞,想要熟练掌握它,需要记住很多公式。但是使用提示就简单多了,和 ChatGPT 聊聊天就能…

【vue3】基础概念的介绍

⭐【前言】 首先,恭喜你打开了一个系统化的学习专栏,在这个vue专栏中,大家可以根据博主发布文章的时间顺序进行一个学习。博主vue专栏指南在这:vue专栏的学习指南 🥳博主:初映CY的前说(前端领域) &#x1f…

【音视频】zlmediakit总结一

推拉流理论 推流:将直播的内容推送至服务器的过程。 拉流:指服务器已有直播内容,用指定地址进行拉取的过程。 拉流,即是指服务器里面有流媒体视频文件; 但zlmediakit里也有个广义的拉流概念如下。对于用户而言&#xf…

面试官灵魂拷问[二]:SQL 语句中 where 条件后写上 1=1 是什么意思?

面试官灵魂拷问系列又来更新啦! “SQL 语句中 where 条件后写上 11 是什么意思?” 这玩意就跟很多新语言支持尾部逗号的原理一样的。 比如 Kotlin 支持数组写成 [1, 2, 3, 4, ] ,注意4后边那个逗号,为什么呢?因为当你增加一个项…

医院LIS系统源码,云LIS系统源码,独立实验室LIS源码

实验室云LIS系统源码 LIS系统源码 LIS源码 基于B/S架构的实验室管理系统云LIS,整个系统的运行基于WEB层面,只需要在对应的工作台安装一个浏览器软件有外网即可访问。 私信了解更多源码内容! 技术架构:Asp.NET CORE 3.1 MVC SQ…

MySQL表设计思路(一对多、多对多...)

要开始单独负责需求了,捋一捋表设计的思路。 文章目录一、MySQL中的数据类型二、一对一的关系设计二、一对多的关系设计三、多对多的关系设计四、经验总结一、MySQL中的数据类型 字符串类型 varchar:即variable char ,可边长度的字符串&#…

Tomcat启动JSP项目,搞起来了

虽然有点复古,但是还是有很多小伙伴在使用的,小编来一篇保姆级教程 1、用idea打开jsp项目 2、添加tomcat配置 3、点击后会出现配置框,这里画框的地方都选上,版本选择1.8,其他的信息内容默认后,点击确认 4、点击…

FITC-PEG-Biotin,荧光素-聚乙二醇-生物素的相关检测

FITC-PEG-Biotin 荧光素聚乙二醇生物素 英文名称:Fluorescein (polyethylene glycol) Biotin 中文名称:荧光素聚乙二醇生物素 激光/发射波长:515nm~520 nm 分子量:2000、3400、5000其他分子量可制定 溶剂&#xff…

【C++】异常

文章目录C传统处理错误方式C异常概念异常使用1.异常的抛出和捕获2.异常的重新抛出异常安全异常规范自定义异常体系C标准库的异常体系异常的优缺点C传统处理错误方式 C语言传统的错误处理机制: 1. 终止程序,如assert,缺陷:用户难以…
最新文章