1 epoll一种网络模式,采用的是 IO多路复用技术(就是可以监控多个文件描述符),相比较于select 和poll是非常快的;
首先看这三个函数:
1 int epoll_create(int size);
2 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
3 int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
函数用法不在这里讲解;
首先epoll_create创建一个epoll文件描述符,底层同时创建一个红黑树,和一个就绪链表;红黑树存储所监控的文件描述符的节点数据,就绪链表存储就绪的文件描述符的节点数据;epoll_ctl将会添加新的描述符,首先判断是红黑树上是否有此文件描述符节点,如果有,则立即返回。如果没有, 则在树干上插入新的节点,并且告知内核注册回调函数。当接收到某个文件描述符过来数据时,那么内核将该节点插入到就绪链表里面。epoll_wait将会接收到消息,并且将数据拷贝到用户空间,清空链表。对于LT模式epoll_wait清空就绪链表之后会检查该文件描述符是哪一种模式,如果为LT模式,且必须该节点确实有事件未处理,那么就会把该节点重新放入到刚刚删除掉的且刚准备好的就绪链表,epoll_wait马上返回。ET模式不会检查,只会调用一次
每个epollfd在内核中有一个对应的eventpoll结构对象.其中关键的成员是一个readylist(eventpoll:rdllist)
和一棵红黑树(eventpoll:rbr).
一个fd被添加到epoll中之后(EPOLL_ADD),内核会为它生成一个对应的epitem结构对象.epitem被添加到
eventpoll的红黑树中.红黑树的作用是使用者调用EPOLL_MOD的时候可以快速找到fd对应的epitem。
调用epoll_wait的时候,将readylist中的epitem出列,将触发的事件拷贝到用户空间.之后判断epitem是否需
要重新添加回readylist.
epitem重新添加到readylist必须满足下列条件:
1) epitem上有用户关注的事件触发.
2) epitem被设置为水平触发模式(如果一个epitem被设置为边界触发则这个epitem不会被重新添加到readylist
中,在什么时候重新添加到readylist请继续往下看).
注意,如果epitem被设置为EPOLLONESHOT模式,则当这个epitem上的事件拷贝到用户空间之后,会将
这个epitem上的关注事件清空(只是关注事件被清空,并没有从epoll中删除,要删除必须对那个描述符调用
EPOLL_DEL),也就是说即使这个epitem上有触发事件,但是因为没有用户关注的事件所以不会被重新添加到
readylist中.
epitem被添加到readylist中的各种情况(当一个epitem被添加到readylist如果有线程阻塞在epoll_wait中,那
个线程会被唤醒):
1)对一个fd调用EPOLL_ADD,如果这个fd上有用户关注的激活事件,则这个fd会被添加到readylist.
2)对一个fd调用EPOLL_MOD改变关注的事件,如果新增加了一个关注事件且对应的fd上有相应的事件激活,
则这个fd会被添加到readylist.
3)当一个fd上有事件触发时(例如一个socket上有外来的数据)会调用ep_poll_callback(见eventpoll::ep_ptable_queue_proc),
如果触发的事件是用户关注的事件,则这个fd会被添加到readylist中.
了解了epoll的执行过程之后,可以回答一个在使用边界触发时常见的疑问.在一个fd被设置为边界触发的情况下,
调用read/write,如何正确的判断那个fd已经没有数据可读/不再可写.epoll文档中的建议是直到触发EAGAIN
错误.而实际上只要你请求字节数小于read/write的返回值就可以确定那个fd上已经没有数据可读/不再可写.
最后用一个epollfd监听另一个epollfd也是合法的,epoll通过调用eventpoll::ep_eventpoll_poll来判断一个
epollfd上是否有触发的事件(只能是读事件).