Epoll 到底是什么?“不” 简单的网络I/O模型?

news2024/11/15 21:39:10

1 简介

Epoll 是个很老的知识点,是后端工程师的经典必修课。这种知识具备的特点就是研究的人多,所以研究的趋势就会越来越深。当然分享的人也多,由于分享者水平参差不齐,也产生的大量错误理解。

今天我再次分享 epoll,肯定不会列个表格,对比一下差异,那就太无聊了。我将从线程阻塞的原理,中断优化,网卡处理数据过程出发,深入的介绍 epoll 背后的原理。相信无论你是否已经熟悉 epoll,本文都会对你有价值。

2 引言

正文开始前,先问大家几个问题。

1、epoll 性能到底有多高。很多文章介绍 epoll 可以轻松处理几十万个连接。而传统 IO 只能处理几百个连接 是不是说 epoll 的性能就是传统 IO 的千倍呢?

2、很多文章把网络 IO 划分为阻塞,非阻塞,同步,异步。并表示:非阻塞的性能比阻塞性能好,异步的性能比同步性能好。

  • 如果说阻塞导致性能低,那传统 IO 为什么要阻塞呢?

  • epoll 是否需要阻塞呢?

  • Java 的 NIO 和 AIO 底层都是 epoll 实现的,这又怎么理解同步和异步的区别?

3、都是 IO 多路复用。

  • 既生瑜何生亮,为什么会有 select,poll 和 epoll 呢?

  • 为什么 epoll 比 select 性能高?

PS:

本文共包含三大部分:初识 epoll、epoll 背后的原理 、Diss 环节。

本文的重点是介绍原理,建议读者的关注点尽量放在:“为什么”。

Linux 下进程和线程的区别其实并不大,尤其是在讨论原理和性能问题时,因此本文中“进程”和“线程”两个词是混用的。

3 初识 epoll

epoll 是 Linux 内核的可扩展 I/O 事件通知机制,其最大的特点就是性能优异。下图是 libevent(一个知名的异步事件处理软件库)对 select,poll,epoll ,kqueue 这几个 I/O 多路复用技术做的性能测试。

​很多文章在描述 epoll 性能时都引用了这个基准测试,但少有文章能够清晰的解释这个测试结果。

这是一个限制了100个活跃连接的基准测试,每个连接发生1000次读写操作为止。纵轴是请求的响应时间,横轴是持有的 socket 句柄数量。随着句柄数量的增加,epoll 和 kqueue 响应时间几乎无变化,而 poll 和 select 的响应时间却增长了非常多。

可以看出来,epoll 性能是很高的,并且随着监听的文件描述符的增加,epoll 的优势更加明显。

不过,这里限制的100个连接很重要。epoll 在应对大量网络连接时,只有活跃连接很少的情况下才能表现的性能优异。换句话说,epoll 在处理大量非活跃的连接时性能才会表现的优异。如果15000个 socket 都是活跃的,epoll 和 select 其实差不了太多。

为什么 epoll 的高性能有这样的局限性?

问题好像越来越多了,看来我们需要更深入的研究了。

4 epoll背后的原理

4.1 阻塞

4.1.1 为什么阻塞

我们以网卡接收数据举例,回顾一下之前我分享过的网卡接收数据的过程。

​为了方便理解,我尽量简化技术细节,可以把接收数据的过程分为4步:

  1. NIC(网卡) 接收到数据,通过 DMA 方式写入内存(Ring Buffer 和 sk_buff)。

  2. NIC 发出中断请求(IRQ),告诉内核有新的数据过来了。

  3. Linux 内核响应中断,系统切换为内核态,处理 Interrupt Handler,从RingBuffer 拿出一个 Packet, 并处理协议栈,填充 Socket 并交给用户进程。

  4. 系统切换为用户态,用户进程处理数据内容。

网卡何时接收到数据是依赖发送方和传输路径的,这个延迟通常都很高,是毫秒(ms)级别的。而应用程序处理数据是纳秒(ns)级别的。也就是说整个过程中,内核态等待数据,处理协议栈是个相对很慢的过程。这么长的时间里,用户态的进程是无事可做的,因此用到了“阻塞(挂起)”。

相关视频推荐

linux多线程之epoll原理剖析与reactor原理及应用

全网最详细epoll讲解,6种epoll的设计,让你吊打面试官

手写一个epoll组件,为tcp并发实现epoll

免费学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

​4.1.2 阻塞不占用 cpu

阻塞是进程调度的关键一环,指的是进程在等待某事件发生之前的等待状态。请看下表,在 Linux 中,进程状态大致有7种(在 include/linux/sched.h 中有更多状态):

​从说明中其实就可以发现,“可运行状态”会占用 CPU 资源,另外创建和销毁进程也需要占用 CPU 资源(内核)。重点是,当进程被"阻塞/挂起"时,是不会占用 CPU 资源的。

换个角度来讲。为了支持多任务,Linux 实现了进程调度的功能(CPU 时间片的调度)。而这个时间片的切换,只会在“可运行状态”的进程间进行。因此“阻塞/挂起”的进程是不占用 CPU 资源的。

另外讲个知识点,为了方便时间片的调度,所有“可运行状态”状态的进程,会组成一个队列,就叫“工作队列”。

4.1.3 阻塞的恢复

内核当然可以很容易的修改一个进程的状态,问题是网络 IO 中,内核该修改那个进程的状态。

​socket 结构体,包含了两个重要数据:进程 ID 和端口号。进程 ID 存放的就是执行 connect,send,read 函数,被挂起的进程。在 socket 创建之初,端口号就被确定了下来,操作系统会维护一个端口号到 socket 的数据结构。

当网卡接收到数据时,数据中一定会带着端口号,内核就可以找到对应的 socket,并从中取得“挂起”进程的 ID。将进程的状态修改为“可运行状态”(加入到工作队列)。此时内核代码执行完毕,将控制权交还给用户态。通过正常的“CPU 时间片的调度”,用户进程得以处理数据。

4.1.4 进程模型

上面介绍的整个过程,基本就是 BIO(阻塞 IO)的基本原理了。用户进程都是独立的处理自己的业务,这其实是一种符合进程模型的处理方式。

4.2 上下文切换的优化

上面介绍的过程中,有两个地方会造成频繁的上下文切换,效率可能会很低。

  1. 如果频繁的收到数据包,NIC 可能频繁发出中断请求(IRQ)。CPU 也许在用户态,也许在内核态,也许还在处理上一条数据的协议栈。但无论如何,CPU 都要尽快的响应中断。这么做实际上非常低效,造成了大量的上下文切换,也可能导致用户进程长时间无法获得数据。(即使是多核,每次协议栈都没有处理完,自然无法交给用户进程)

  2. 每个 Packet 对应一个 socket,每个 socket 对应一个用户态的进程。这些用户态进程转为“可运行状态”,必然要引起进程间的上下文切换。

4.2.1 网卡驱动的 NAPI 机制

在 NIC 上,解决频繁 IRQ 的技术叫做 New API(NAPI) 。原理其实特别简单,把 Interrupt Handler 分为两部分。

  1. 函数名为 napi_schedule,专门快速响应 IRQ,只记录必要信息,并在合适的时机发出软中断 softirq。

  2. 函数名为 netrxaction,在另一个进程中执行,专门响应 napi_schedule 发出的软中断,批量的处理 RingBuffer 中的数据。

所以使用了 NAPI 的驱动,接收数据过程可以简化描述为:

  1. NIC 接收到数据,通过 DMA 方式写入内存(Ring Buffer 和 sk_buff)。

  2. NIC 发出中断请求(IRQ),告诉内核有新的数据过来了。

  3. driver 的 napi_schedule 函数响应 IRQ,并在合适的时机发出软中断(NET_RX_SOFTIRQ)

  4. driver 的 net_rx_action 函数响应软中断,从 Ring Buffer 中批量拉取收到的数据。并处理协议栈,填充 Socket 并交给用户进程。

  5. 系统切换为用户态,多个用户进程切换为“可运行状态”,按 CPU 时间片调度,处理数据内容。

一句话概括就是:等着收到一批数据,再一次批量的处理数据。

4.2.2 单线程的 IO 多路复用

内核优化“进程间上下文切换”的技术叫的“IO 多路复用”,思路和 NAPI 是很接近的。

每个 socket 不再阻塞读写它的进程,而是用一个专门的线程,批量的处理用户态数据,这样就减少了线程间的上下文切换。

​作为 IO 多路复用的一个实现,select 的原理也很简单。所有的 socket 统一保存执行 select 函数的(监视进程)进程 ID。任何一个 socket 接收了数据,都会唤醒“监视进程”。内核只要告诉“监视进程”,那些 socket 已经就绪,监视进程就可以批量处理了。

4.3 IO 多路复用的进化

4.3.1 对比 epoll 与 select

select,poll 和 epoll 都是“IO 多路复用”,那为什么还会有性能差距呢?篇幅限制,这里我们只简单对比 select 和 epoll 的基本原理差异。

对于内核,同时处理的 socket 可能有很多,监视进程也可能有多个。所以监视进程每次“批量处理数据”,都需要告诉内核它“关心的 socket”。内核在唤醒监视进程时,就可以把“关心的 socket”中,就绪的 socket 传给监视进程。

换句话说,在执行系统调用 select 或 epoll_create 时,入参是“关心的 socket”,出参是“就绪的 socket”。

而 select 与 epoll 的区别在于:

  • select (一次O(n)查找)

  1. 每次传给内核一个用户空间分配的 fd_set 用于表示“关心的 socket”。其结构(相当于 bitset)限制了只能保存1024个 socket。

  2. 每次 socket 状态变化,内核利用 fd_set 查询O(1),就能知道监视进程是否关心这个 socket。

  3. 内核是复用了 fd_set 作为出参,返还给监视进程(所以每次 select 入参需要重置)。

然而监视进程必须遍历一遍 socket 数组O(n),才知道哪些 socket 就绪了。

  • epoll (全是O(1)查找)

  1. 每次传给内核一个实例句柄。这个句柄是在内核分配的红黑树 rbr+双向链表 rdllist。只要句柄不变,内核就能复用上次计算的结果。

  2. 每次 socket 状态变化,内核就可以快速从 rbr 查询O(1),监视进程是否关心这个 socket。同时修改 rdllist,所以 rdllist 实际上是“就绪的 socket”的一个缓存。

  3. 内核复制 rdllist 的一部分或者全部(LT 和 ET),到专门的 epoll_event 作为出参。

所以监视进程,可以直接一个个处理数据,无需再遍历确认。

Select 示例代码

Epoll 示例代码

另外,epoll_create 底层实现,到底是不是红黑树,其实也不太重要(完全可以换成 hashtable)。重要的是 efd 是个指针,其数据结构完全可以对外透明的修改成任意其他数据结构。

4.3.2 API 发布的时间线

另外,我们再来看看网络 IO 中,各个 api 的发布时间线。就可以得到两个有意思的结论。

1983,socket 发布在 Unix(4.2 BSD) 1983,select 发布在 Unix(4.2 BSD) 1994,Linux的1.0,已经支持socket和select 1997,poll 发布在 Linux 2.1.23 2002,epoll发布在 Linux 2.5.44

1、socket 和 select 是同时发布的。这说明了,select 不是用来代替传统 IO 的。这是两种不同的用法(或模型),适用于不同的场景。

2、select、poll 和 epoll,这三个“IO 多路复用 API”是相继发布的。这说明了,它们是 IO 多路复用的3个进化版本。因为 API 设计缺陷,无法在不改变 API 的前提下优化内部逻辑。所以用 poll 替代 select,再用 epoll 替代 poll。

5 总结

我们花了三个章节,阐述 Epoll 背后的原理,现在用三句话总结一下。

  1. 基于数据收发的基本原理,系统利用阻塞提高了 CPU 利用率。

  2. 为了优化上线文切换,设计了“IO 多路复用”(和 NAPI)。

  3. 为了优化“内核与监视进程的交互”,设计了三个版本的 API(select,poll,epoll)。

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

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

相关文章

USB转GSM模块发送中英文测试

目录 使用模块前注意事项模块测试发送英文短信发送中文短信手机收到短信页面常见问题 总结 使用模块前注意事项 使用USB转GSM模块要 注意 两点: 1.所在地要有2G基站,因为这是2G信号产品。 2.最好使用移动卡,有些地方电信和联通卡无法使用。 模块测试 这里介绍模块…

在安卓手机搭建kali环境,手机变成便携式渗透神器

简介 kali是著名的黑客专用系统,一般都是直接装在物理机或者虚拟机上,我们可以尝试把kali安装在手机上,把手机打造成一个便携式渗透神器。 我们需要下载以下3款软件: (1).Termux(终端模拟器) (2).AnLinux(里边有各种安装liunx…

GPT时代,寻找讯飞星火大模型的算力支点

作者 | 辰纹 来源 | 洞见新研社 大模型的“涌现”还在持续。 5月底举行的中关村论坛上,有专家披露,中国10亿级参数规模以上的大模型已经发布了79个,刚刚结束的世界人工智能大会上,又有一批大模型批量发布。 大模型的热度居高不…

华大HC32F460 TCP Server实验

目录 1.实验目标 2.实验准备 3.主流程图 4.驱动代码 5.实验步骤 1.实验目标 本实验使用W5500服务器功能,通过串口实现与本地客户端透传数据。 2.实验准备 硬件搭建:ZW-HC32F460-BZ标准版开发板1套 软件搭建:MDK5.22 3.主流程图 4.驱动…

css实现按钮圆角渐变样式

最终成果图: 背景: 最近项目数据大屏这个样式给我卡住了,起因是UI设计想要按钮边框渐圆角背景透明渐变,我查找了好多资料也在问答里提问,都没有实现初始样式。原因是如果想用渐变边框就会使用到属性border-image&#…

在Microsoft Excel中带单位的数字如何求和

使用 Excel 中的 SUM 函数对一系列单元格、整列或非连续单元格求和。要创建出色的 SUM 公式,请将 SUM 函数与其他 Excel 函数结合使用,然而 SUM 函数不能直接对带单位的数字进行求和。 当直接相加带单位的几个数字会出现如下错误: 错误的原因…

811. 交换数值

链接: https://www.acwing.com/problem/content/813/ 题目: 输入两个整数 xx 和 yy,请你编写一个函数, 交换两个整数的数值并输出交换后的 xx 和 yy。 C中的格式为:void swap(int &x, int &y)。 Java中的格式为&#xff1…

【软件测试】Git 将项目本地推送至GitHub与Gitee(详细)

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 Gitee拉取Github仓…

KeyShot 2023 Pro 对 Mac 和 Windows 系统的要求

KeyShot 2023 Pro 是一款功能强大的计算机辅助设计(CAD)渲染软件,它为用户提供了高质量的视觉效果和逼真渲染。 KeyShot 2023 Pro for Mac可以与各种主流 CAD 软件进行集成,包括 SolidWorks、AutoCAD、Rhinoceros、Fusion 360 等…

simulink matlab function

目录 改形参类型​编辑 多输出值 排序 peisistent 关键字,静态相当于static function添加trigger 输出调用function call() 改形参类型 多输出值 function [y1,y2] myfcn(u1,u2)y1 u1u2; y2 u1-u2; 排序 peisistent 关键字,静态相当于static func…

MIT 6.S081 -- Virtual memory for applications

MIT 6.S081 -- Virtual memory for applications 引言应用程序使用虚拟内存所需要的特性支持应用程序使用虚拟内存的系统调用虚拟内存系统如何支持用户应用程序构建大的缓存表Bakers Real-Time Copying Garbage Collector使用虚拟内存特性的GC使用虚拟内存特性的GC代码展示 引言…

【Linux后端服务器开发】基础IO与文件系统

目录 一、基础IO 1. C语言文件读写 2. 标志位传参 3. C语言与系统调用关系 二、文件系统 1. 文件描述符 2. 输入输出重定向 一、基础IO 文件调用 库函数接口: fopen、fclose、fwrite、fread、fseek系统调用接口:open、close、write、read、lsee…

利用 kube-vip 实现 K3s 高可用部署

作者简介 王海龙,Rancher 中国社区技术经理,Linux Foundation APAC Evangelist,负责 Rancher 中国技术社区的维护和运营。拥有 9 年的云计算领域经验,经历了 OpenStack 到 Kubernetes 的技术变革,无论底层操作系统 Lin…

腾讯音乐娱乐集团2023校园招聘技术类岗位编程题合集

字符串操作 题解:先变为没出现过的字符,然后在正常的变换 class Solution { public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** 返回满足题意的最小操作数* param str string字符串 给定…

Python绘制直方图

文章目录 初步参数绘图类型多组数据直方图对比 初步 对于大量样本来说,如果想快速获知其分布特征,最方便的可视化方案就是直方图,即统计落入不同区间中的样本个数。 以正态分布为例 import numpy as np import matplotlib.pyplot as pltxs…

用c++实现大小端序互转(字符串方法)

今天在逆一个RC4加密算法&#xff0c;忘了IDA如何自动将大小端序互转的按键&#xff0c;索性自己写了一个&#xff0c;因为是用字符串方法&#xff0c;所以理论上长度是管够的 #include <iostream> #include <string> using namespace std; int main() {string m_…

macOS 14 Sonoma Beta 测试版体验,不影响主力系统

今年的 WWDC 上 Apple 依旧保持往年的发布节奏&#xff0c;公布了今年的 macOS 新版本&#xff1a;macOS Sonoma&#xff0c;不过和以往有些不太一样的是&#xff0c;这一次 Apple 将测试版系统直接下发给了普通开发者账户&#xff0c;对于一般用户简单注册就可以直接获得 macO…

Windows VScode如何配置与使用git?

当我们在VScode中编写代码后&#xff0c;需要提交到git仓库时&#xff0c;但是我们又不想切换到git的命令行窗口&#xff0c;我们可以在VScode中配置git&#xff0c;然后就可以很方便快捷的把代码提交到仓库中。 1. 官网下载安装Git命令行工具 根据自己的电脑系统&#xff0c…

分布式ELK日志文件分析系统(曾经沧海难为水,除却巫山不是云)

文章目录 一、ELK 概述1. 为什么要使用 ELK2. 完整日志系统基本特征3. ELK 简介3.1 ElasticSearch&#xff08;ES&#xff09;3.2 Kiabana3.3 Logstash3.4 其它组件Filebeat缓存/消息队列Fluentd 4. ELK 的工作原理5. Linux 系统内核日志消息的优先级别 二、 部署 ELK 集群服务…