Linux多路复用之select/poll/epoll实现原理及优缺点对比

原创 2016年08月02日 11:29:57

一、select的实现原理

    支持阻塞操作的设备驱动通常会实现一组自身的等待队列如读/写等待队列用于支持上层(用户层)所需的BLOCK或NONBLOCK操作。当应用程序通过设备驱动访问该设备时(默认为BLOCK操作),若该设备当前没有数据可读或写,则将该用户进程插入到该设备驱动对应的读/写等待队列让其睡眠一段时间,等到有数据可读/写时再将该进程唤醒。


select就是巧妙的利用等待队列机制让用户进程适当在没有资源可读/写时睡眠,有资源可读/写时唤醒。


二、poll的实现原理

    poll()系统调用是System V的多元I/O解决方案。它有三个参数,第一个是pollfd结构的数组指针,也就是指向一组fd及其相关信息的指针,因为这个结构包含的除了fd,还有期待的事件掩码和返回的事件掩码,实质上就是将select的中的fd,传入和传出参数归到一个结构之下,也不再把fd分为三组,也不再硬性规定fd感兴趣的事件,这由调用者自己设定。这样,不使用位图来组织数据,也就不需要位图的全部遍历了。按照一般队列地遍历,每个fd做poll文件操作,检查返回的掩码是否有期待的事件,以及做是否有挂起和错误的必要性检查,如果有事件触发,就可以返回调用了。

三、epoll的实现原理

     回到poll和select的共同点,面对高并发多连接的应用情境,它们显现出原来没有考虑到的不足,虽然poll比起select又有所改进了。除了上述的关于每次调用都需要做一次从用户空间到内核空间的拷贝,还有这样的问题,就是当处于这样的应用情境时,poll和select会不得不多次操作,并且每次操作都很有可能需要多次进入睡眠状态,也就是多次全部轮询fd,我们应该怎么处理一些会出现重复而无意义的操作。


     这些重复而无意义的操作有:

1、从用户到内核空间拷贝,既然长期监视这几个fd,甚至连期待的事件也不会改变,那拷贝无疑就是重复而无意义的,我们可以让内核长期保存所有需要监视的fd甚至期待事件,或者可以在需要时对部分期待事件进行修改(MOD,ADD,DEL);

2、将当前线程轮流加入到每个fd对应设备的等待队列,这样做无非是哪一个设备就绪时能够通知进程退出调用,聪明的开发者想到,那就找个“代理”的回调函数,代替当前进程加入fd的等待队列好了。这样,像poll系统调用一样,做poll文件操作发现尚未就绪时,它就调用传入的一个回调函数,这是epoll指定的回调函数,它不再像以前的poll系统调用指定的回调函数那样,而是就将那个“代理”的回调函数加入设备的等待队列就好了,这个代理的回调函数就自己乖乖地等待设备就绪时将它唤醒,然后它就把这个设备fd放到一个指定的地方,同时唤醒可能在等待的进程,到这个指定的地方取fd就好了(ET与LT)。

    我们把1和2结合起来就可以这样做了,只拷贝一次fd,一旦确定了fd就可以做poll文件操作,如果有事件当然好啦,马上就把fd放到指定的地方,而通常都是没有的,那就给这个fd的等待队列加一个回调函数,有事件就自动把fd放到指定的地方,当前进程不需要再一个个poll和睡眠等待了。


上面说的就是epoll了,epoll由三个系统调用组成,分别是epoll_create,epoll_ctl和epoll_wait。epoll_create用于创建和初始化一些内部使用的数据结构;epoll_ctl用于添加,删除或者修改指定的fd及其期待的事件,epoll_wait就是用于等待任何先前指定的fd事件。



下面在分析下select的缺点:

    select在执行之前必须先循环添加要监听的文件描述符到fd集合中,所以

缺点一:每次调用select都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时开销很大。

    select调用的时候都需要在内核遍历传递进来的所有fd,判断是不是我关心的事件。所以

缺点二:每次调用select都需要在内核遍历所有传递进来的fd,这个开销在fd很多时,开销也很大。 

缺点三:这个由系统内核决定了,支持的文件描述符的默认值只有1024,想想应用到稍微大一点的服务器就不够用了。


下面在分析下poll的缺点:

    poll对于select来说包含了一个pollfd结构,pollfd结构包含了要监视的event和发生的revent,而不像select那样使用参数-值的传递方式。同时poll没有最大数量的限制。但是

缺点一:数量过大以后其效率也会线性下降。

缺点二:poll和select一样需要遍历文件描述符来获取已经就绪的socket。当数量很大时,开销也就很大。


epoll的优点:

    优点一:支持一个进程打开大数目的socket描述符

select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远远于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

  优点二:IO效率不随FD数目增加而线性下降

传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动是在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了

 优点三:使用mmap加速内核与用户空间的消息传递

这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核与用户空间mmap同一块内存实现的。而如果你想像我一样从2.5内核就关注epoll的话,一定不会忘记手工mmap这一步的。(mmap底层是使用红黑树加队列实现的,每次需要在操作的fd,先在红黑树中拿到,放到队列中,那么用户收到epoll_wait消息以后只需要看一下消息队列中有没有数据,有我就取走)

  优点四:内核微调

这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小-- 通过echoXXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包里面数据巨大但同时每个数据包本本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。

  最后我们谈下epoll ET模式为何fd必须要设置为非阻塞这个问题

ET边缘触发,数据就只会通知一次,也就是说,如果要使用ET模式,当数据就绪时,需要一直read,知道完成或出错为止。但倘若当前fd为阻塞的方式,那么当读完成缓冲区数据时,而对端并没有关闭写端,那么该read就会阻塞,影响其他fd以及他以后的逻辑,所以需要设置为非阻塞,当没有数据的时候,read虽然读取不到数据,但是肯定不会阻塞,那么说明此时数据已经读取完毕,可以继续处理后续逻辑了(读取其他的fd或者进入wait)



版权声明:本文为博主原创文章,未经博主允许不得转载。

事件触发机制:Poll,Select和Epoll实现原理分析

Poll和Select和Epoll都是事件触发机制,当等待的事件发生就触发进行处理,多用于linux实现的服务器对客户端连接的处理。 Poll和Select都是这样的机制:可以阻塞地同时探测一组支持非...
  • wangxiaoqin00007
  • wangxiaoqin00007
  • 2013年11月07日 13:36
  • 7184

Linux后台网络编程中select/poll/epoll的比较分析

二.poll 1.概述 和select基本一样,除了poll没有使用低效的三个基于位的文件描述符set,而是采用了一个单独的结构体pollfd数组,由fds指针指向这个组。pollfd结构...
  • xiaojun111111
  • xiaojun111111
  • 2016年03月22日 18:35
  • 277

深度理解select、poll和epoll

在linux 没有实现epoll事件驱动机制之前,我们一般选择用select或者poll等IO多路复用的方法来实现并发服务程序。在大数据、高并发、集群等一些名词唱得火热之年代,select和poll的...
  • davidsguo008
  • davidsguo008
  • 2017年06月21日 23:42
  • 552

Poll,Select和Epoll实现原理和性能对比

 Poll和Select和Epoll都是事件触发机制,当等待的事件发生就触发进行处理,多用于Linux实现的服务器对客户端连接的处理。 Poll和Select都是这样的机制:可以阻塞地同时探测...
  • JoysonQin
  • JoysonQin
  • 2017年04月20日 11:40
  • 649

linux下select/poll/epoll机制的比较

select、poll、epoll简介 epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POS...
  • sundray24
  • sundray24
  • 2016年11月20日 23:55
  • 64

linux select与poll实现机制与实例分析

我们直到上层对文件操作结合select与poll可以实现阻塞操作,那么究竟是如何实现的呢? select接口:     int select(int nfds, fd_set *readset, f...
  • eqwewr
  • eqwewr
  • 2015年01月20日 18:09
  • 1499

Linux多路复用之select/poll/epoll实现原理及优缺点对比

一、select的实现原理     支持阻塞操作的设备驱动通常会实现一组自身的等待队列如读/写等待队列用于支持上层(用户层)所需的BLOCK或NONBLOCK操作。当应用程序通过设备驱动访问该设...
  • xiaofei0859
  • xiaofei0859
  • 2016年11月17日 16:40
  • 1353

I/O 多路复用之select、poll、epoll实现原理及对比总结

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但s...
  • xx_yTm
  • xx_yTm
  • 2017年01月31日 23:31
  • 258

select、poll、epoll的原理、优点、缺点比较总结

实现多路复用输入/输出型模型的一种。让程序监视多个文件句柄的状态变化的。程序会停在select这里等待,知道监视到的文件句柄有一个或者多个发生了状态变化(从数据无到有)。 编写select服务器步骤...
  • hanjing_1995
  • hanjing_1995
  • 2016年07月30日 20:30
  • 302

select,poll,epoll优缺点及比较

在之前我已经分析了这三个函数,请看我之前的文章: IO多路复用之select函数详解 IO多路复用之poll函数详解 IO多路复用之epoll函数详解   这篇文章只总结优缺点,以便面试时回...
  • lixungogogo
  • lixungogogo
  • 2016年08月17日 01:51
  • 4532
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux多路复用之select/poll/epoll实现原理及优缺点对比
举报原因:
原因补充:

(最多只允许输入30个字)