select,poll和epoll
select优点
1)select()的可移植性更好,在某些Unix系统上不支持poll()
2)select() 对于超时值提供了更好的精度:微秒,而poll是毫秒。
select缺点
1) 单个进程可监视的fd数量被限制。
2) 需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。
3) 对fd进行扫描时是线性扫描。fd剧增后,IO效率较低,因为每次调用都对fd进行线性扫描遍历,所以随着fd的增加会造成遍历速度慢的性能问题
4)select() 函数的超时参数在返回时也是未定义的,考虑到可移植性,每次在超时之后在下一次进入到select之前都需要重新设置超时参数。
poll
poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,
pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。
poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。
poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。
poll优点
1)poll() 不要求开发者计算最大文件描述符加一的大小。
2)poll() 在应付大数目的文件描述符的时候相比于select速度更快
3)它没有最大连接数的限制,原因是它是基于链表来存储的。
poll缺点
1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
2)与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符
epoll
epoll是Linux下多路复用IO接口select/poll的增强版本。
它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它不会复用文件描述符集合来传递结果而迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合。
另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
epoll除了提供select/poll 那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
底层实现
epoll在底层实现了自己的高速缓存区,并且建立了一个红黑树用于存放socket,另外维护了一个链表用来存放准备就绪的事件。
工作过程:
执行epoll_ create时,创建了红黑树和就绪链表,执行epoll_ ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。
epoll优点
1)支持一个进程打开大数目的socket描述符(FD)
2)IO效率不随FD数目增加而线性下降
3)使用mmap加速内核与用户空间的消息传递。
总结
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。
而epoll其实也需要调用 epoll_ wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在 epoll_wait中进入睡眠的进程。
虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的 时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间,这就是回调机制带来的性能提升。(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内 部定义的等待队列),这也能节省不少的开销。