select、poll、epoll之间的区别总结
来源:互联网 发布:下载办公软件最新版 编辑:程序博客网 时间:2024/05/17 02:44
Poll和Select和Epoll都是事件触发机制,当等待的事件发生就触发进行处理,多用于Linux实现的服务器对客户端连接的处理。
Poll和Select都是这样的机制:可以阻塞地同时探测一组支持非阻塞的IO设备,是否有事件发生(如可读,可写,有高优先级的错误输出,出现错误等等),直至某一个设备触发了事件或者超过了指定的等待时间——也就是它们的职责不是做IO,而是帮助调用者寻找当前就绪的设备。
原文链接:http://blog.chinaunix.NET/uid-20792262-id-2909919.html
epoll相关系统调用是在Linux 2.5.44开始引入的。该系统调用针对传统的select/poll系统调用的不足,设计上作了很大的改动。select/poll的缺点在于:
1.每次调用时要重复地从用户态读入参数。
2.每次调用时要重复地扫描文件描述符。
3.每次在调用开始时,要把当前进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除。
在实际应用中,select/poll监视的文件描述符可能会非常多,如果每次只是返回一小部分,那么,这种情况下select/poll显得不够高效。 epoll的设计思路,是把select/poll单个的操作拆分为1个epoll_create+多个epoll_ctrl+一个wait。此外,内核针对epoll操作添加了一个文件系统”eventpollfs”,每一个或者多个要监视的文件描述符都有一个对应的eventpollfs文件系统的inode节点,主要信息保存在eventpoll结构体中。而被监视的文件的重要信息则保存在epitem结构体中。所以他们是一对多的关系。
由于在执行epoll_create和epoll_ctrl时,已经把用户态的信息保存到内核态了所以之后即使反复地调用epoll_wait,也不会重复地拷贝参数,扫描文件描述符,反复地把当前进程放入/放出等待队列。这样就避免了以上的三个缺点。
select、poll、epoll_wait参数及实现对比
1. int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select的第一个参数nfds为fdset集合中最大描述符值加1,fdset是一个位数组,其大小限制为__FD_SETSIZE(1024),位数组的每一位代表其对应的描述符是否需要被检查。
select的第二三四个参数表示需要关注读、写、错误事件的文件描述符位数组,这些参数既是输入参数也是输出参数,可能会被内核修改用于标示哪些描述符上发生了关注的事件。所以每次调用select前都需要重新初始化fdset。
timeout参数为超时时间,该结构会被内核修改,其值为超时剩余的时间。
select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返回。
select返回后,需要逐一检查关注的描述符是否被SET(事件是否发生)。
2. poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。
poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。
poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。
3.
epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
epoll通过epoll_create创建一个用于epoll轮询的描述符,通过epoll_ctl添加/修改/删除事件,通过epoll_wait检查事件,epoll_wait的第二个参数用于存放结果。
epoll与select、poll不同,首先,其不用每次调用都向内核拷贝事件描述信息,在第一次调用后,事件信息就会与对应的epoll描述符关联起来。另外epoll不是通过轮询,而是通过在等待的描述符上注册回调函数,当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间。
epoll返回后,该参数指向的缓冲区中即为发生的事件,对缓冲区中每个元素进行处理即可,而不需要像poll、select那样进行轮询检查。
本质而言,poll和select的共同点就是,对全部指定设备做一次poll,当然这往往都是还没有就绪的,那就会通过回调函数把当前进程注册到设备的等待队列,如果所有设备返回的掩码都没有显示任何的事件触发,就去掉回调函数的函数指针,进入有限时的睡眠状态,再恢复和不断做poll,再作有限时的睡眠,直到其中一个设备有事件触发为止。只要有事件触发,系统调用返回,回到用户态,用户就可以对相关的fd作进一步的读或者写操作了。当然,这个时候还不是所有的设备都就绪的喔,那就得不断地poll或者select了,而做一次这样的系统调用都得轮询所有的设备,次数是设备数*(睡眠次数-1),也就是时间复杂度是O(n),还得做几次O(n)呢。可见,对于现在普遍的服务器程序,需要同时并发监听数千个连接,并且连接需要重复使用的情况,poll和select就存在这样的性能瓶颈。另外,数千个设备fd在每次调用时,都需要将其从用户空间复制到内核空间,这里的开销不可忽略。
poll和select放在一起,是因为其机制一致,而参数和数据结构就略有不同。select一次性传入三组作用于不同信道的设备fd,分别是输入,输出和错误异常。各组的fd期待各组所特有的,由代码指定的一组事件,如输入信道期待输入就绪,输入挂起和错误等事件。 然后,select就挑选调用者关心的fd做poll文件操作,检测返回的掩码,看看是否有fd所属信道感兴趣的事件,比如看看这个属于输出信道的fd有没有输出就绪等一系列的事件发生,一样地,如果有一个fd发生感兴趣事件就返回调用了。select,为了同时处理三组使用不同的事件判断规则的fd,采用了位图的方式表示,一组一个位图,位长度是当中最大的fd值,上限是1024,三组就是3072,而且这还只是传入的位图,还有一样大小的传出的位图。当fd数越来越多时,所需的存储开销比较大。
既然,一组fd处理起来比较粗放,那就各个fd自己准备好了。poll()系统调用是System V的多元I/O解决方案。它有三个参数,第一个是pollfd结构的数组指针,也就是指向一组fd及其相关信息的指针,因为这个结构包含的除了fd,还有期待的事件掩码和返回的事件掩码,实质上就是将select的中的fd,传入和传出参数归到一个结构之下,也不再把fd分为三组,也不再硬性规定fd感兴趣的事件,这由调用者自己设定。这样,不使用位图来组织数据,也就不需要位图的全部遍历了。按照一般队列地遍历,每个fd做poll文件操作,检查返回的掩码是否有期待的事件,以及做是否有挂起和错误的必要性检查,如果有事件触发,就可以返回调用了。
回到poll和select的共同点,面对高并发多连接的应用情境,它们显现出原来没有考虑到的不足,虽然poll比起select又有所改进了。除了上述的关于每次调用都需要做一次从用户空间到内核空间的拷贝,还有这样的问题,就是当处于这样的应用情境时,poll和select会不得不多次操作,并且每次操作都很有可能需要多次进入睡眠状态,也就是多次全部轮询fd,我们应该怎么处理一些会出现重复而无意义的操作。
这些重复而无意义的操作有:1、从用户到内核空间拷贝,既然长期监视这几个fd,甚至连期待的事件也不会改变,那拷贝无疑就是重复而无意义的,我们可以让内核长期保存所有需要监视的fd甚至期待事件,或者可以再需要时对部分期待事件进行修改;2、将当前线程轮流加入到每个fd对应设备的等待队列,这样做无非是哪一个设备就绪时能够通知进程退出调用,聪明的开发者想到,那就找个“代理”的回调函数,代替当前进程加入fd的等待队列好了(这也是我后来才总结出来,Linux的等待队列,实质上是回调函数队列吧,也可以使用宏来将当前进程“加入”等待队列,其实就是将唤醒当前进程的回调函数加入队列)。这样,像poll系统调用一样,做poll文件操作发现尚未就绪时,它就调用传入的一个回调函数,这是epoll指定的回调函数,它不再像以前的poll系统调用指定的回调函数那样,而是就将那个“代理”的回调函数加入设备的等待队列就好了,这个代理的回调函数就自己乖乖地等待设备就绪时将它唤醒,然后它就把这个设备fd放到一个指定的地方,同时唤醒可能在等待的进程,到这个指定的地方取fd就好了。我们把1和2结合起来就可以这样做了,只拷贝一次fd,一旦确定了fd就可以做poll文件操作,如果有事件当然好啦,马上就把fd放到指定的地方,而通常都是没有的,那就给这个fd的等待队列加一个回调函数,有事件就自动把fd放到指定的地方,当前进程不需要再一个个poll和睡眠等待了。
epoll机制就是这样改进的了。诚然,fd少的时候,当前进程一个个地等问题不大,可是现在和尚多了,方丈就不好管了。以前设备事件触发时,只负责唤醒当前进程就好了,而当前进程也只能傻傻地在poll里面等待或者循环,再来一次poll,也不知道这个由设备提供的poll性能如何,能不能检查出当前进程已经在等待了就立即返回,当然,我也不明白为什么做了一遍的poll之后,去掉回调函数指针了,还得再做,不是说好了会去唤醒进程的吗?
现在就让事件触发回调函数多做一步。本来设备还没就绪就调用一个回调函数了,现在再在这个回调函数里面做一个注册另一个回调函数的操作,目的就是使得设备事件触发多走一步,不仅仅是唤醒当前进程,还要把自己的fd放到指定的地方。就像收本子的班长,以前得一个个学生地去问有没有本子,如果没有,它还得等待一段时间而后又继续问,现在好了,只走一次,如果没有本子,班长就告诉大家去那里交本子,当班长想起要取本子,就去那里看看或者等待一定时间后离开,有本子到了就叫醒他,然后取走。这个道理很简单,就是老师和班干们常说的,大家多做一点工作,我的工作就轻松很多了,尤其是需要管理的东西越来越多时。
这种机制或者说模式,我想在Java的FutureTask里面应该也会用到的,一堆在线程池里面跑着的线程(当然这是任务,不是线程,接口是Callable,不是Runnable.run,是Callable.call,它是可以返回结果的),谁先做好就应该先处理呀,可是难道得一个个问吗?干脆就谁好了,谁就按照既定的操作暴露自己,这样FutureTask的get方法就可以马上知道当前最先完成的线程了,就可以取此线程返回结果了。
系统接口原型
1. select
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
2. poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout); int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts, const sigset_t *sigmask);struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ };3. epoll
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);
参数对比
1. select
- select的第一个参数nfds为fdset集合中最大描述符值加1,fdset是一个位数组,其大小限制为__FD_SETSIZE(1024),位数组的每一位代表其对应的描述符是否需要被检查;
- select的第二三四个参数表示需要关注读、写、错误事件的文件描述符位数组,这些参数既是输入参数也是输出参数,可能会被内核修改用于标示哪些描述符上发生了关注的事件。所以每次调用select前都需要重新初始化fdset。
- timeout参数为超时时间,该结构会被内核修改,其值为超时剩余的时间。
- select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返回。
- select返回后,需要逐一检查关注的描述符是否被SET(事件是否发生)。
2. poll
- poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。
- poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。
- poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。
poll事件类型
事件描述是否可作为输入是否可作为输出POLLIN数据(包括普通数据和优先数据)是是POLLRDNORM普通数据可读是是POLLRDBAND优先级带数据可读(Linux不支持)是是POLLPRI高优先级数据可读,比如TCP带外数据是是POLLOUT数据(包括普通数据和优先数据)可写是是POLLWRNORM普通数据可写是是POLLWRBAND优先级带数据可写是是POLLRDHUPTCP连接被对方关闭,或者对方关闭了写操作。它由GNU引入是是POLLERR错误否是POLLHUP挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件否是POLLNVAL文件描述符没有打开否是
3. epoll
- epoll是Linux特有的I/O复用函数。它在实现上与select、poll有很大的差异。首先,epoll使用一组函数来完成任务,而不是单个函数;
- 其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无需像select和poll那样每次调用都要重复传入文件的事件放在内核里的一个事件表中。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表;
- epoll通过epoll_create创建一个用于epoll轮询的描述符,通过epoll_ctl添加/修改/删除事件,通过epoll_wait检查事件,epoll_wait的第二个参数用于存放结果。
- epoll与select、poll不同,首先,其不用每次调用都向内核拷贝事件描述信息,在第一次调用后,事件信息就会与对应的epoll描述符关联起来。另外epoll不是通过轮询,而是通过在等待的描述符上注册回调函数,当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间。
- epoll返回后,该参数指向的缓冲区中即为发生的事件,对缓冲区中每个元素进行处理即可,而不需要像poll、select那样进行轮询检查。
性能对比
select、poll的内部实现机制相似,性能差别主要在于向内核传递参数以及对fdset的位操作上,另外,select存在描述符数的硬限制,不能处理很大的描述符集合。
这里主要考察poll与epoll在不同大小描述符集合的情况下性能的差异。
测试程序会统计在不同的文件描述符集合的情况下,1s 内poll与epoll调用的次数。
统计结果如下,从结果可以看出,对poll而言,每秒钟内的系统调用数目虽集合增大而很快降低,而epoll基本保持不变,具有很好的扩展性。
描述符集合大小
poll
epoll
1
331598
258604
10
330648
297033
100
91199
288784
1000
27411
296357
5000
5943
288671
10000
2893
292397
25000
1041
285905
50000
536
293033
100000
224
285825
相同点
- 都能同时监听多个文件描述符;
- 它们将等待由timeout参数指定的超时时间,直到一个或者多个文件描述符上有事件发生时返回,返回值是就绪的文件描述符的数量;
收藏的文章:http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结[
- select、poll、epoll之间的区别总结
- 大胸不一定在任何时候都是优势....
- Java中static介绍
- MySQL5.7解压版配置
- 关于音乐下载的一个小技能
- 数据清洗(1clean to hbase)
- select、poll、epoll之间的区别总结
- Eclipse常用配置及快捷键课堂小结
- 代理模式(结构型)
- c语言基本数据类型及其使用
- MyBatis关联映射
- leetcode——TwoSum、3Sum
- Fedora 24下安装Chrome浏览器
- iOS之CALayer简介
- 欢迎使用CSDN-markdown编辑器