软件开发税率是13%还是6,搜索引擎优化的主要工作有,wordpress 最近登录地址,个人备案能做企业网站吗前言
在我的Rust入门及实战系列文章中已经说明#xff0c; Rust是一门内存安全的高性能编程语言#xff0c;从它的这些优秀特性来看#xff0c;就是一门专为系统开发而诞生的语言。至于很多使用Rust来进行web开发的行为#xff0c;不能说它们不好#xff0c;只能说是杀鸡…前言
在我的Rust入门及实战系列文章中已经说明 Rust是一门内存安全的高性能编程语言从它的这些优秀特性来看就是一门专为系统开发而诞生的语言。至于很多使用Rust来进行web开发的行为不能说它们不好只能说是杀鸡焉用牛刀耳。
本系列的文章旨在为大家介绍一个新的专用于开发eBPF程序的Rust框架Aya。Aya 是 Rust 中的一个 eBPFExtended Berkeley Packet Filter库提供了用 Rust 语言编写、加载和运行 eBPF 程序的能力。eBPF 是一种强大的技术用于在 Linux 内核中安全地运行沙盒程序常用于网络编程、性能监控和安全增强。我们在后面的文章中将对eBPF进行更加详细和深入的介绍。
Linux内核网络基础
如果你还不了解什么是eBPF, 那么其实从它的名称中便可见一斑从Packet Filter可以看出这显然是用于包的处理的一门技术。它通过在Linux的内核中的不同挂载点加入一个隔离可控的二进制程序来达到我们想要为内核增加功能处理网络包的目的。既然是在内核的网络处理流程中动手脚那么在开始之前我们有必要对Linux内核原本的网络处理流程有基本的认知 否则如果直接开始eBPF的编写那么我们很可能会变成狗拿刺猬无从下手。
网络模型概览
众所周知网络模型的的划分有不同的方法 最流行的莫过于经典的OSI七层网络模型 TCP/IP四层网络模型还有综合两者而成的五层网络模型。从理解内核网络工作原理的角度出发我们选择五层网络模型进行接下来的探究
注 在Linux系统中内核源码的位置位于/usr/src/目录下的对应内核命名的目录下例如我使用的azure虚拟机上内核代码的目录为/usr/src/linux-headers-5.15.0-1052-azure。 后文提到的内核代码相关的目录都是以此为根的相对路径。
Linux内核在收到一个网络数据包后首先会由网卡驱动程序(相关内核代码位于drivers/net/ethernet中)先进行处理 然后回交由内核中处理协议栈相关的代码进行处理(相关内核代码位于kernel/和net/中)处理完成后的结果再由socket提供接口供用户空间的应用层程序访问。
网络中断处理原理
那么从网卡收到数据包是怎么传递给内核进行处理的呢这里就要谈到中断处理了。 从硬件的角度来看当一个网卡收到数据包时 它会进行以下两件事情
将数据包以DMA(Direct Memory Access)的方式, 将收到的数据帧存放到内存的环形缓冲区中(Buffer Ring);向CPU的引脚施加一个电压变化向CPU表明现在有一个数据来了需要处理。
上述这种通过向CPU引脚施加电压的硬件操作被称为硬中断。那么CPU此时就会对收到的数据包进行处理那么如何处理呢网络包如果一直不停的到来而对网络包的处理往往又是复杂和耗时的如果CPU每收到一个数据包都对它进行处理完成后再干别的工作就会导致CPU的占用率过高而无法对其他的硬中断进行响应了比如鼠标键盘等设备发起的硬中断请求。
软中断注册
因此当CPU收到一个网卡发来的硬中断时它会告诉网卡驱动程序: “你先去内存登记一下待办事项吧”于是网卡驱动程序会在内存中标记一个变量表示这里有一个网络包需要人手来处理了。这个在内存中设置标志的操作就被称为软中断。
上述过程的图示如下
软中断处理
内核驱动程序处理
在我们的Linux启动后内核中会运行一个进程ksoftirqd, 它的职责就是检测内存中是否有软中断需要处理 一旦检测到这是一个网络驱动注册的软中断就会调用网卡驱动中的poll函数从内存的环形缓冲区中将网络数据包收下来并进行处理 这个过程的图示如下 其中 网卡驱动程序中的igb_poll()函数会从内存的环形缓冲区中将完整的网络数据包取出来然后调用igb_clean_rx_irq()函数进行处理这些处理包括
校验收到的数据格式是否是一个合法的网络包将收到的数据包格式化成skb解析timestamp, VLAN id, protocol等字段信息
内核协议栈处理
在驱动程序对数据包进行处理后处理完的数据将被发送到内核的协议栈进行处理在进入协议栈之前内核中存在一个GRO引擎它的作用是把一些小的网络包合成一个大的网络包一次性发给协议栈进行处理目的是减少传送给协议栈的包数量这有助于减少 CPU 的使用量。 如上图所示在数据包进入协议栈后
首先会调用netif_receive_skb()函数其中会辨别数据包的网络层协议根据网络层协议调用不同的函数例如判断得到这个数据包是个IP包则会接下来调用ip_rcv()函数在其中又会判断它的传输层协议根据其是TCP还是UDP而调用不同的函数例如判断得到这是一个TCP包那么将继续调用tcp_rcv()函数对数据进行处理处理完成后的数据可以供用户通过socket访问
小结
网络模块在Linux内核中及其复杂上面的介绍以尽可能简单明了的方式描述了一个数据包从网卡收到它开始如何被内核进行处理的整个过程。其中包含了CPU硬中断ksoftiqrd线程软中断处理网卡驱动对数据包的处理skb的创建 网络协议栈对数据包的处理等过程。
本文没有涉及用户空间应用程序从socket取包的过程这涉及到recvfrom系统调用也是一个比较复杂的话题。本系列文章旨在介绍Rust开发eBPF程序只关注内核对网络包的处理流程因此用户空间取包不在我们的关注范围内。在了解了网络包在内核中的处理流程之后对于后需eBPF程序的挂载点我们应当会有更清晰的认识。