epoll
epoll_create 创建一个epoll对象,一般epollfd = epoll_create()
epoll_ctl(int epfd,int op,int fd.struct epoll_event *event) //往epoll对象中增加/删除某一个流的某一个事件
epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件,即有数据流入
epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件,即流可以被写入
epoll_wait(int epfd,struct epoll_event * event,int maxevents,int timeout)//等待直到注册的事件发生
//(注:当对一个非阻塞流的读写发生缓冲区满或缓冲区空,write/read会返回-1,并设置errno=EAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件)。
首先epoll_create创建一个epoll文件描述符,底层同时创建一个红黑树,和一个就绪链表;红黑树存储所监控的文件描述符的节点数据,就绪链表存储就绪的文件描述符的节点数据;epoll_ctl将会添加新的描述符,首先判断是红黑树上是否有此文件描述符节点,如果有,则立即返回。如果没有, 则在树干上插入新的节点,并且告知内核注册回调函数。当接收到某个文件描述符过来数据时,那么内核将该节点插入到就绪链表里面。epoll_wait将会接收到消息,并且将数据拷贝到用户空间,清空链表。对于LT模式epoll_wait清空就绪链表之后会检查该文件描述符是哪一种模式,如果为LT模式,且必须该节点确实有事件未处理,那么就会把该节点重新放入到刚刚删除掉的且刚准备好的就绪链表,epoll_wait马上返回。ET模式不会检查,只会调用一次
LT (LevelTriggered):水平触发
ET(EdgeTriggered):边缘触发(高效模式)
- LT:对于LT水平触发下的文件描述符,当epoll_wait检测到其上有事件发生并将其通知应用程序后,应用程序可不立即处理改事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait会再次向应用程序通知此事件,直到该事件被处理为止
- ET:对于ET模式下的文件描述符,当epoll_wait检测到其上有事件发生并将其通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait将不再向应用程序通知该事件。
- 由此可见,ET模式在很大程度上降低了同一epoll事件被重复触发的次数,因此效率比LT水平触发模式高;
epoll实现原理
在linux,一切皆文件.所以当调用epoll_create时,内核给这个epoll分配一个file,但是这个不是普通的文件,而是只服务于epoll.
所以当内核初始化epoll时,会开辟一块内核高速cache区,用于安置我们监听的socket,这些socket会以红黑树的形式保存在内核的cache里,以支持快速的查找,插入,删除.同时,建立了一盒list链表,用于存储准备就绪的事件.所以调用epoll_wait时,在timeout时间内,只是简单的观察这个list链表是否有数据,如果没有,则睡眠至超时时间到返回;如果有数据,则在超时时间到,拷贝至用户态events数组中.
那么,这个准备就绪list链表是怎么维护的呢?当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。
epoll有两种模式LT(水平触发)和ET(边缘触发),LT模式下,主要缓冲区数据一次没有处理完,那么下次epoll_wait返回时,还会返回这个句柄;而ET模式下,缓冲区数据一次没处理结束,那么下次是不会再通知了,只在第一次返回.所以在ET模式下,一般是通过while循环,一次性读完全部数据.epoll默认使用的是LT.
这件事怎么做到的呢?当一个socket句柄上有事件时,内核会把该句柄插入上面所说的准备就绪list链表,这时我们调用epoll_wait,会把准备就绪的socket拷贝到用户态内存,然后清空准备就绪list链表,最后,epoll_wait干了件事,就是检查这些socket,如果不是ET模式(就是LT模式的句柄了),并且这些socket上确实有未处理的事件时,又把该句柄放回到刚刚清空的准备就绪链表了。所以,非ET的句柄,只要它上面还有事件,epoll_wait每次都会返回。而ET模式的句柄,除非有新中断到,即使socket上的事件没有处理完,也是不会次次从epoll_wait返回的.
select、poll、epoll 区别
系统调用 | select | poll | epoll |
---|---|---|---|
事件集合 | 用户通过三个参数分别传入感兴趣的可读、可写及异常事件,内核通过对这些参数的在线修改来反馈其中的就绪事件。这使得用户每次调用 select 都要重置这三个参数 | 统一处理所有事件类型,因此只需一个事件集参数,用户通过pollfd.events 传入感兴趣的事件,内核通过修改 pollfd.events 反馈其中的就绪事件 | 内核通过一个事件表直接管理所有的就绪事件,因此每次调用epoll_wait 时,无需反复传入用户感兴趣的事件。 epoll_wait 系统调用的参数 events 仅用来反馈就绪的事件 |
应用程序索引就绪文件描述符的时间复杂度 | O(n) | O(n) | O(1) |
最大支持文件描述符数 | 一般有最大值限制 | 65535 | 65535 |
工作模式 | LT | LT | 支持ET 高效模式 |
内核实现和工作效率 | 采用轮询方式检测就绪事件,O(n) | 采用轮询方式来检测就绪事件,O(n) | 采用回调方式来检测就绪事件,O(1) |
上一篇: linux系统启动
下一篇: 在循环里,怎么让一段代码只执行一次?