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)



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

相关文章推荐

Linux I/O复用中select poll epoll模型的介绍及其优缺点的比较

**关于I/O多路复用:** I/O多路复用(又被称为“事件驱动”),首先要理解的是,操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知。这样当配合非阻塞的soc...

Linux下select, poll和epoll IO模型的详解

一).Epoll 介绍 Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复...

实现一个线程安全的单例模式

一、单例模式        单例模式也叫单件模式。Singleton是一个非常常用的设计模式,几乎所有稍微大一些的程序都会使用它,所以构建一个高效的Singleton很重要。 1、单例类保证全局只...

【技术晨读】TCP慢启动、拥塞避免、快速重传、快速恢复

为了防止网络的拥塞现象,TCP提出了一系列的拥塞控制机制。最初由V. Jacobson在1988年的论文中提出的TCP的拥塞控制由“慢启动(Slow start)”和“拥塞避免(Congestion ...

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

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

(Bruce Molay)Unix/Linux编程实践教程读书笔记(一)----select/poll/epoll I/O多路复用

select、poll、epoll 比较

I/O多路复用之select/poll/epoll

1、基本概念  IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:   (1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须...

Python异步非阻塞IO多路复用Select/Poll/Epoll使用

来源:http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架,底层在linux基于最新的epoll实现,为了更好的使用,了解其底层原...

I/O 多路复用入门——select/poll/epoll

select poll epoll

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

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但s...
  • xx_yTm
  • xx_yTm
  • 2017年01月31日 23:31
  • 210
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux多路复用之select/poll/epoll实现原理及优缺点对比
举报原因:
原因补充:

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