Linux 多路复用之epoll

简介: epoll作为一种基于事件通知的I/O处理模型,广泛用于需要I/O多路处理的场景。epoll API所执行的任务与poll类似:监测多路文件描述符的I/O可用性。

epoll 简析


epoll作为一种基于事件通知的I/O处理模型,广泛用于需要I/O多路处理的场景。epoll API所执行的任务与poll类似:监测多路文件描述符的I/O可用性。


epoll的特性:


  • 触发方式:epoll API能被用于边沿触发或者电平触发方式,这两种触发方式的机制存在较大的不同,下文会做详细讲解。


  • 性能:epoll能够很好地监测大量的文件描述符,对于大规模的I/O多路复用场景,epoll是很好的选择。


epoll实例基本管理


通过epoll_create、epoll_wait、epoll_ctl系统调用完成epoll的创建、I/O事件等待、文件描述符的增加|删除|修改等功能。


  • epoll_create:其创建一个epoll的实例,并且返回一个指向该实例的文件描述符。(epoll_create1扩展了epoll_create的一些功能)。


  • epoll_ctl:通过epoll_ctl将感兴趣的文件描述符添加到epoll实例。当前注册到epoll实例上的文件描述符集合,一般称为epoll set。


  • epoll_wait:等待I/O事件,如果没有I/O事件可用,那么epoll_wait会阻塞调用线程。


触发方式


epoll事件分发界面可以表现为edge-triggered(ET)和level-triggered(LT)触发方式。关于两种机制的区别下面会详细的介绍。现在模拟这样一种场景:


  1. 打开一个管道,并将管道的读端文件描述符(rfd)注册到epoll实例。


  1. 在管道的另一端,向管道写入2kB的数据。


  1. epoll_wait会返回,并且返回可读状态的rfd。


  1. 通过rfd读取1kB的数据。


  1. 调用epoll_wait。


如果rfd在加入到epoll实例时配置了EPOLLET(edge-triggered)属性,那么上面第5步的epoll_wait调用会挂起,尽管在rfd输入缓冲区中仍然存在可用的数据;同时,管道的另一端可能在等待基于这些数据的响应消息。 发生这种情况的原因是,edge-triggered模式只会在文件描述符发生变化时才会递送事件通知。所以,第5步的epoll_wait可能会一直一些数据的到来,但是这些数据已经存在于输入缓冲区。上面例子中,步骤2,向管道写入 2kB数据,步骤3中,epoll_wait以为rfd可读而返回,步骤4,读取了管道中的一部分数据。因为步骤4中没有将全部的数据读取出来,所以,步骤5中的epoll_wait调用可能会一直阻塞下去。


为了避免应用程序在处理多个使用了EPOLLET属性的文件描述时,由于阻塞与读或者写,而导致某个任务出现"饥饿"的现象,我们应该使用非阻塞文件描述符。建议使用EPOLLET模式的epoll方式如下:


  • 使用非阻塞的文件描述符;并且


  • 只有在read或write返回EAGAIN错误后,再调用epoll_wait等待I/O事件。


相反,当使用电平触发方式时(默认是该模式),epoll仅仅是高速版的poll,其语义与poll相同。


即使某些文件描述符使用了边缘触发的epoll,也可以在接收到多个数据块时生成多个事件,因此调用者可以选择指定EPOLLONESHOT标志,告诉epoll在接收到epoll_wait(2)事件后禁用相关的文件描述符。当EPOLLONESHOT属性启用时,调用 者负责使用epoll_ctrl的EPOLL_CTL_MOD再次解禁epoll的文件描述符。


epoll与内存


/proc/sys/fs/epoll/max_user_watches(Linux 2.6.28),负责限制epoll所使用的内核态下的内存总量。该文件定义了单个用户下所有epoll实例所拥有的文件描述符的总量。每个已注册的文件描述符在32-bit内核下占用90字节,64-bit 内核下占用160字节。目前,max_user_watches的默认值是1/25(4%)的可用物理内存除以每个文件描述符所用的内存大小。


常见陷阱其避免方式


  • 进程饥饿(边沿触发)


如果有大量的I/O空间,那么通过尝试耗尽它,其他文件可能不会得到处理,从而导致饥饿。(这个问题不是epoll特有的。) 解决方案是维护一个就绪列表,并在其关联的数据结构中将文件描述符标记为ready,从而允许应用程序记住需要处理哪些文件,但仍然在所有就绪文件之间进行循环。这还支持忽略已经准备好的文件描述符接收到的后续事件。


  • 如果使用了event cache


如果您使用事件缓存从epoll_wait返回的所有文件描述符,那么请确保提供一种动态标记其闭包的方法(即,由先前事件的处理引起)。假设你从epoll_wait收到100个事件,而在事件#47中,有一个条件会导致事件#13关闭。 如果删除结构并关闭事件#13的文件描述符,那么事件缓存可能仍然认为等待该文件描述符的事件从而导致混淆,这是常见的缓存状态不一直问题。


对此的一个解决方案是在事件#47的处理期间调用epoll_ctl(EPOLL_CTL_DEL)来删除文件描述符,并将其close,然后将其关联的数据结构标记为已移除并将其链接到清理列表。 如果在批处理中找到文件描述符#13的另一个事件,您将发现之前已删除的文件描述符,并且不会产生混淆。


Q&A


  • Q0:用于区分已经注册到epoll中的各个文件描述符的关键是什么?


  • A0:关键是文件描述符号和打开的文件描述的组合(也称为“打开文件句柄”,它是打开文件的内核内部表示)。


  • Q1:同一个文件描述符注册两次会出现什么问题?


  • A1:可能会返回EEXIST错误码。但是,可以向同一epoll实例注册一个使用(dup、dup2、fcntl)复制过的文件描述符。如果重复的文件描述符用不同的事件掩码注册,这对于过滤事件是一种有用的技术。


  • Q2:不同的epoll实例可以监测同一文件描述符吗?如果可以的话,两个epoll都会收到事件通知吗?


  • A2:是的,每个epoll都会收到事件通知。然而,小心编程完全可以避免该问题。


  • Q3:epoll本身的文件描述符是否可以poll/epoll/selectable?


  • A3:是的。如果一个epoll文件描述符可读时,其同样会产生相应的事件通知。


  • Q4:如果将epoll文件描述符添加到epoll自己的文件描述符集会发生什么问题?


  • A4:epoll_ctrl会返回EINVAL错误码。然而,你可以将epoll文件描述符添加到另一个epoll实例的文件描述符集中。


  • Q5:可以将一个epoll文件描述符通过UNIX域socket发送给另一个进程吗?


  • A5:可以,但是那样做没有什么意义,因为接收端的进程没有该epoll的其他文件描述符集。


  • Q6:关闭一个文件描述符后,epoll会自动将其从文件描述集中删除掉吗?


  • A6:是的,但是,需要注意下面一种情况。文件描述符是对打开文件描述的引用(参见open(2))。无论何时通过dup(2)、dup2(2)、fcntl(2) F_DUPFD或fork(2)复制描述符,都会创建一个引用相同打开文件描述的新文件描述符。打开的文件描述将继续存在,直到所有引用它的文件描述符都已关闭。只有在引用底层打开文件描述的所有文件描述符都已关闭之后(或者在使用epoll_ctl(2) EPOLL_CTL_DEL显式删除描述符之前),才会从epoll集中删除文件描述符。这意味着即使关闭了epoll集中的文件描述符,如果引用相同底层文件描述符的其他文件描述符仍然打开,也可能报告该文件描述符的事件。


  • Q7:如果在epoll_wait阻塞期间,产生了多个事件,那么,它们是一块上报还是逐个上报呢?


  • A7:一块上报。


  • Q8:对于一个收集到但尚未上报的事件所关联的文件描述操作,是否会产生影响?


  • A8:对于已经存在的文件描述符,你可以执行两项操作:没有太大意义的删除操作;修改会重新获得可读的IO。


  • Q9:当使用EPOLLET标志(边缘触发行为)时,我是否需要不断地读取/写入文件描述符,直到返回EAGAIN?


  • A9:从epoll_wait(2)接收一个事件应该会向您提示,该文件描述符已经为请求的I/O操作做好了准备。您必须认为它已经准备好了,直到下一次(非阻塞)读/写再次产生EAGAIN。何时以及如何使用文件描述符完全取决于您。


对于面向包/令牌的文件(例如,数据报套接字、规范模式下的终端),检测读/写I/O空间结束的惟一方法是继续读/写,直到返回EAGAIN。 对于面向流的文件(例如管道、FIFO、流套接字),也可以通过检查从读/写到目标文件描述符的数据量来检测读/写I/O空间耗尽的情况。例如,如果您通过请求读取一定数量的数据来调用read(2),而read(2)返回的字节数更少,那么可以肯定已经耗尽了文件描述符的read I/O空间。当使用write(2)进行写作时也是如此。(如果不能保证所监视的文件描述符始终引用面向流的文件,则避免使用后一种技术。)


相关文章
|
2月前
|
网络协议 Linux Python
Python网络编程基础(Socket编程)epoll在Linux下的使用
【4月更文挑战第12天】在上一节中,我们介绍了使用`select`模块来实现非阻塞IO的方法。然而,`select`模块在处理大量并发连接时可能会存在性能问题。在Linux系统中,`epoll`机制提供了更高效的IO多路复用方式,能够更好地处理大量并发连接。
|
10天前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
|
25天前
|
Linux C++
c++高级篇(三) ——Linux下IO多路复用之poll模型
c++高级篇(三) ——Linux下IO多路复用之poll模型
|
25天前
|
缓存 监控 网络协议
c++高级篇(二) ——Linux下IO多路复用之select模型
c++高级篇(二) ——Linux下IO多路复用之select模型
|
1月前
|
消息中间件 存储 监控
实战Linux I/O多路复用:借助epoll,单线程高效管理10,000+并发连接
本文介绍了如何使用Linux的I/O多路复用技术`epoll`来高效管理超过10,000个并发连接。`epoll`允许单线程监控大量文件描述符,显著提高了资源利用率。文章详细阐述了`epoll`的几个关键接口,包括`epoll_create`、`epoll_ctl`和`epoll_wait`,以及它们在处理并发连接中的作用。此外,还探讨了`epoll`在高并发TCP服务场景的应用,展示了如何通过`epoll`和线程/协程池来构建服务框架。
183 3
|
9天前
|
Linux 网络安全 虚拟化
Ngnix04系统环境准备-上面软件是免费版的,下面是收费版的,他更快的原因使用了epoll模型,查看当前Linux系统版本, uname -a,VMWARE建议使用NAT,PC端电脑必须使用网线连接
Ngnix04系统环境准备-上面软件是免费版的,下面是收费版的,他更快的原因使用了epoll模型,查看当前Linux系统版本, uname -a,VMWARE建议使用NAT,PC端电脑必须使用网线连接
|
2月前
|
监控 Linux
Linux的epoll用法与数据结构data、event
Linux的epoll用法与数据结构data、event
26 0
|
4天前
|
数据挖掘 Linux 数据处理
Linux命令sprof详解
**`sprof`是Linux下的共享库性能分析工具,补充`gprof`,专注分析`.profile`文件以识别性能瓶颈。通过调用次数、执行时间数据优化资源和代码。使用参数如`-F`、`-I`、`-d`进行定制化分析。示例:先设置`LD_PROFILE`环境变量,运行程序生成`.profile`,然后用`sprof`分析。注意需用`-g`编译程序,并在代表性的负载下分析。结合其他工具如`perf`、`valgrind`提升分析效果。**
|
4天前
|
存储 数据挖掘 Linux
Linux命令split详解:大文件处理的得力助手
`split`命令是Linux用于将大文件分割成小文件的工具,常用于日志处理、备份。它支持按行数(-l)、字节数(-b)分割,并能自定义输出文件名(-a, -d)。例如,`split -b 10M largefile.txt smallfile_`会按10MB切割`largefile.txt`,生成`smallfile_`开头的文件。注意确保磁盘空间充足,避免文件名冲突,并备份原始文件。结合其他命令使用,能提高文件管理效率。
|
23小时前
|
关系型数据库 MySQL Linux
Linux命令systemctl详解
`systemctl`是Linux系统用于管理systemd服务的核心命令,它与systemd守护进程交互,实现启动、停止、重启服务及查看服务状态等功能。主要参数包括`start`、`stop`、`restart`、`status`、`enable`和`disable`等。例如,启动Apache服务使用`systemctl start httpd.service`,查看服务状态用`systemctl status <service>`。使用时需注意权限,服务名通常以`.service`结尾,但命令中可省略。最佳实践包括利用tab键补全、定期查看服务状态和合理配置服务自启。