什么是epoll?
更多c/c++ Linux服务器高阶知识请后台私信【架构】获取
知识点有C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等。
epoll是linux中IO多路复用的一种机制,I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。IO多路复用的方法,处理有select,poll,epoll。
epoll是为处理大批量句柄而作了改进的poll,被认为Linux下性能最好的多路I/O就绪通知方法。
epoll的相关系统调用epoll只有epoll_create , epoll_ctl , epoll_wait 3个系统调用。
#创建epoll fd
int epoll_create(int size);
#添加epoll需要监听的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
#收集在epoll监控的事件中已经发送的事件
#当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符
#而是一个代表就绪描述符数量的值,
#你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,
int epoll_wait(int epfd, struct epoll_event * events,
int maxevents, int timeout);
程序的大体框架:
epoll的工作原理select/poll 每次调用都要传递所要监控的所有fd给系统调用(这意味着每次调用都要将fd列表从用户态拷贝到内核态,当fd数目很多时,这会造成低效)。 而每次调用epoll_wait时(作用相当于调用select/poll),不需要再传递fd列表给内核,因为已经在epoll_ctl中将需要监控的fd告诉了内核(epoll_ctl不需要每次都拷贝所有的fd,只需要进行增量式操作)。所以,在调用epoll_create之后,内核已经在内核态开始准备数据结构存放要监控的fd了。每次epoll_ctl只是对这个数据结构进行简单的维护。select/poll一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。当我们调用epoll_ctl往里塞入百万个fd时,epoll_wait仍然可以飞快的返回,并有效的将发生事件的fd给我们用户。epoll源码分析
epoll相关的内核代码在fs/eventpoll.c文件中,下面分别分析epoll_create、epoll_ctl和epoll_wait三个函数在内核中的实现。
<code> SYSCALL_DEFINE1(epoll_create,
int
, size) {if
(size <=0
)return
-EINVAL;return
sys_epoll_create1(0
); } /<code>Epoll的2种工作方式 — 水平触发(LT)和边缘触发(ET)epoll 的默认模式是水平触发模式,但是也可以设置为边缘触发模式,在边缘触发模式的效率更高。
具体的区别在于:
如上图所示,0表示文件还没有准备好,1表示文件描述符已经准备好了。
如果在水平模式下面,有一个文件描述符从0变成1,那么表示的是这个描述符已经准备就绪了,可以对它进行io操作了。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。
在边缘模式下面,如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。