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)进行写作时也是如此。(如果不能保证所监视的文件描述符始终引用面向流的文件,则避免使用后一种技术。)


相关文章
|
17天前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
66 0
|
17天前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
58 1
Linux C/C++之IO多路复用(aio)
|
17天前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
16 0
Linux C/C++之IO多路复用(poll,epoll)
|
4月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
166 1
|
5月前
|
消息中间件 存储 监控
实战Linux I/O多路复用:借助epoll,单线程高效管理10,000+并发连接
本文介绍了如何使用Linux的I/O多路复用技术`epoll`来高效管理超过10,000个并发连接。`epoll`允许单线程监控大量文件描述符,显著提高了资源利用率。文章详细阐述了`epoll`的几个关键接口,包括`epoll_create`、`epoll_ctl`和`epoll_wait`,以及它们在处理并发连接中的作用。此外,还探讨了`epoll`在高并发TCP服务场景的应用,展示了如何通过`epoll`和线程/协程池来构建服务框架。
525 8
|
5月前
|
Linux C++
c++高级篇(三) ——Linux下IO多路复用之poll模型
c++高级篇(三) ——Linux下IO多路复用之poll模型
|
5月前
|
缓存 监控 网络协议
c++高级篇(二) ——Linux下IO多路复用之select模型
c++高级篇(二) ——Linux下IO多路复用之select模型
|
4月前
|
Linux 网络安全 虚拟化
Ngnix04系统环境准备-上面软件是免费版的,下面是收费版的,他更快的原因使用了epoll模型,查看当前Linux系统版本, uname -a,VMWARE建议使用NAT,PC端电脑必须使用网线连接
Ngnix04系统环境准备-上面软件是免费版的,下面是收费版的,他更快的原因使用了epoll模型,查看当前Linux系统版本, uname -a,VMWARE建议使用NAT,PC端电脑必须使用网线连接
|
3天前
|
运维 安全 Linux
Linux中传输文件文件夹的10个scp命令
【10月更文挑战第18天】本文详细介绍了10种利用scp命令在Linux系统中进行文件传输的方法,涵盖基础文件传输、使用密钥认证、复制整个目录、从远程主机复制文件、同时传输多个文件和目录、保持文件权限、跨多台远程主机传输、指定端口及显示传输进度等场景,旨在帮助用户在不同情况下高效安全地完成文件传输任务。
32 5
|
3天前
|
Linux
Linux系统之expr命令的基本使用
【10月更文挑战第18天】Linux系统之expr命令的基本使用
25 4