select、poll、epoll的区别

本文写于2017-02-26,从老账号迁移到本账号,原文地址:https://www.cnblogs.com/huangweiyang/p/6444746.html

select、poll、epoll三组系统调用都能同时监听多个文件描述符,它们将等待由timeout参数指定的超时时间,直到一个或多个文件描述符上有事件时返回,返回值是就绪的文件描述符的数量。返回0表示没有事件发生。

下面从事件集、最大支持文件描述符数、工作模式和具体实现等四个方面进一步比较它们的异同。

事件集:

select的参数类型fd_set没有将文件描述符和事件绑定(没有用数据结构绑定fd和event),它仅仅是一个文件描述符集合,因此select需要提供3个这种类型的参数来分别传入可读、可写及异常事件。一方面这使得select不能处理更多类型的事件,另一方面由于内核对fd_set集合的在线修改,应用程序下次调用select前不得不重置这3个fd_set集合。

poll参数类型pollfd结构体将文件描述符和事件绑定起来,任何事件都被统一处理,并且内核每次修改的都是pollfd结构体的revents成员,而events成员不变,因此下次调用poll时应用程序无需重置pollfd。

由于每次select和poll调用都返回整个用户注册的事件集合(其中包括就绪的和未就绪的),所以应用程序索引就绪文件描述符的时间复杂度为O(n)。

epoll则采用与select和poll完全不同的方式来管理用户注册的事件。它在内核中维护一个事件表,并提供了一个独立的系统调用epoll_ctl来控制往其中添加、注册、修改事件。这样,每次epoll_wait调用都直接从内核事件表中取得用户注册的时间,而无需反复从用户空间读入这些事件。epoll_wait系统调用的events参数仅用来返回就绪的时间,应用程序索引文件描述符时间复杂度为O(1)。

工作模式

select和poll都只能工作在相对低效的LT模式,而epoll则可以工作在ET高效模式,并且epoll还支持EPOLLONESHOT事件。该事件能进一步减少可读、可写、异常事件被触发的次数。

实现原理

从实现原理上来说,select和poll采用的都是轮询的方式,即每次调用都要扫描整个注册文件描述符集合,并将其中就绪的文件描述符返回给用户程序,因此他们检测就绪事件的时间复杂度是O(n)。epoll则不同,当我们使用epoll_ctl注册事件时,epoll内部会把socket封装成为epitem插入到红黑树之中,还会给内核中断处理程序注册一个回调函数。当socket上有事件时,内核会把对应的epitem插入到rdlist中,epoll_wait检测rdlist不为空,就将该就绪队列的内容拷贝到用户空间。(如果是LT模式,在拷贝之后还会将epitem重新加入rdlist)。因此,epoll_wait不必轮询整个文件描述符集合,算法时间复杂读是O(1)。但是,活动链接比较多时,epoll_wait未必比select和poll高,因为此时回调函数触发过于频繁。所以epoll_wait适用于连接数量多,但活动连接较少的情况。

如下表:

系统调用 select poll epoll
事件集合 用户通过3个参数分别传入感兴趣的可读、可写及异常等事件,内核通过对着写参数的在线修改来反馈其中的就绪事件。这是用用户每次调用select都要重置这三个参数 poll统一处理所有事件类型,因此只需一个事件集参数,用户通过pollfd.events传入感兴趣的事件,内核通过修改pollfd.revents反馈其中就绪事件 内核通过一个事件表直接管理用户感兴趣的所有事件,因此每次调用epoll_wait时,无需反复传入用户感兴趣的时间。epoll_wait系统调用的参数events仅用来反馈就绪的事件
应用程序索引就绪文件描述符的时间复杂度 O(n) O(n) O(1)
最大支持的文件描述符数 1024 65535 65535
工作模式 LT LT LT和ET
内核实现和国祚效率 采用轮询方式来检测就绪事件,算法时间复杂度为O(n) 采用轮询方式来检测就绪事件,算法时间复杂度为O(n) 采用回调方式来检测就绪事件,时间复杂度O(1)

猜你喜欢

转载自www.cnblogs.com/hhwyt/p/10353361.html