一:select poll epoll的区别
1 底层实现:
select是通过函数select传入感兴趣的可读、可写、异常事件文件描述符集合,select调用返回时,内核修改他们来通知哪些文件描述符就绪,每次使用都需要重新设置文件描述符。
poll是通过函数poll(struct pollfd* fds,nfds_t nfds,int timeout)来传入结构体数组参数,结构体中含有感兴趣的文件描述符以及感兴趣的事件;函数返回时修改结构体中的revents(实际发生的事件)来通知应用程序,不需要重新设置。
epoll是通过epoll_create创建内核事件表,在内核时间表中添加文件描述符以及感兴趣的事件(epoll_ctl(int epfd,int op,int fd,struct epoll_event * event)),通过epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout)来将就绪已发生的事件填充到传入的结构体数组中。
2应用程序索引就绪文件描述符的时间复杂度
select:O(N) poll:O(N) epoll:O(1)
3最大支持的文件描述符的数量
select:一般有最大值限制 (1024) poll epoll 系统的最大值,一般是65535;
工作模式:
select poll :LT epoll:LT ,同时支持ET的高效模式
4内核实现和工作效率
select poll 采用 轮询方式检测就是事件,算法事件复杂度为O(n) epoll采用回调方式来检测就绪事件,事件复杂度为O(1);
二:epoll的两种实现模式
epoll主要有两种实现模式,LT(电平触发)与ET(边沿触发),epoll默认的工作方式是LT模式。
两者的区别是事件处理方式不同。LT模式下,当epoll_wait检测到上面有事件发生通知应用程序后,应用程序可以不立即处理该事件,下次调用epoll_wait时,还会继续通告此事件,直到事件被处理。ET模式下,epoll_wait检测通知应用程序后,应用程序必须立即处理该事件,因为后续将不再通知这一事件。两者的区别主要是在内核实现中的差异。
(1)epoll_wait调用ep_poll,当rdlist为空时挂起当前进程,直到rdlist不为空唤醒进程。
(2 文件fd状态改变(buffer由不可读变为可读或由不可写变为可写),导致相应fd上的回调函数ep_poll_callback()被调用。
(3) ep_poll_callback将相应fd对应epitem加入rdlist,导致rdlist不空,进程被唤醒,epoll_wait得以继续执行。
(4) ep_events_transfer函数将rdlist中的epitem拷贝到txlist中,并将rdlist清空。
(5) ep_send_events函数(很关键),它扫描txlist中的每个epitem,调用其关联fd对用的poll方法(图中蓝线)。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。之后如果这个epitem对应的fd是LT模式监听且取得的events是用户所关心的,则将其重新加入回rdlist(图中蓝线),否则(ET模式)不在加入rdlist。
红线:fd状态改变是才会触发。那么什么情况会导致fd状态的改变呢?
对于读取操作:
(1) 当buffer由不可读状态变为可读的时候,即由空变为不空的时候。
(2) 当有新数据到达时,即buffer中的待读内容变多的时候。
对于写操作:
(1) 当buffer由不可写变为可写的时候,即由满状态变为不满状态的时候。
(2) 当有旧数据被发送走时,即buffer中待写的内容变少得时候。
蓝线:fd的events中有相应的时间(位置1)即会触发。那么什么情况下会改变events的相应位呢?
对于读操作:
(1) buffer中有数据可读的时候,即buffer不空的时候fd的events的可读为就置1。
对于写操作:
(1) buffer中有空间可写的时候,即buffer不满的时候fd的events的可写位就置1。
说明:红线是时间驱动被动触发,蓝线是函数查询主动触发。