IO多路复用之epoll总结
使用
server端
1 |
|
运行./epoll_demo_server
client端
1 | // |
运行./epoll_demo_client 127.0.0.1,输入字符,会返回对应字符的大写。
主要接口函数
int epoll_create ( int size ); 创建epoll实例,返回epoll对应的fd
int epoll_ctl ( int epfd, int op, int fd, struct epoll_event *event ); 注册监听的事件
事件类型op主要有以下几种
EPOLL_CTL_ADD:往事件表中注册fd上的事件
EPOLL_CTL_MOD:修改fd上的注册事件
EPOLL_CTL_DEL:删除fd上的注册事件
int epoll_wait ( int epfd, struct epoll_event* events, int maxevents, int timeout );
函数说明:
返回值:成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno
timeout:指定epoll的超时时间,单位是毫秒。当timeout为-1时,epoll_wait调用将永远阻塞,直到某个时间发生。当timeout为0时,epoll_wait调用将立即返回。
events:就绪的fd对应的事件,由内核态返回赋值到用户态,然后读取这个evnents[i]进行事件处理
maxevents:指定最多监听多少个事件
数据结构
调用epoll_create初始化epoll实例时,其实内核会创建一个eventpollfs类型的文件系统,因此我们看到该函数返回的是一个也是fd,后续的操作都围绕这个epoll实例展开。
调用epoll_ctl时会把传入的fd和监听事件包装为epitem,放入到红黑树中,这样做是为了加快查找删除fd的效率。
调用epoll_wait时,当有就绪的fd时,epoll会通过ep_poll_callback回调函数把就绪事件一个叫做rdlist的双向链表中。
总体处理流程
- epoll_wait调用ep_poll,当rdlist为空(无就绪fd)时挂起当前进程,直到rdlist不空时进程才被唤醒,这里用到的是内核的队列唤醒等待机制。
- 每个io设备的驱动中都注册了一个回调函数,当有数据发送获读取时会调用这个回调函数,比如键盘接收到用户的点击、网卡收到新事物数据包,这个回调函数就是 ep_ptable_queue_proc,然后调用ep_poll_callback。
- ep_poll_callback将相应fd对应epitem加入rdlist,导致rdlist不空,进程被唤醒,epoll_wait得以继续执行。
- ep_events_transfer函数将rdlist中的epitem拷贝到txlist中,并将rdlist清空。
- ep_send_events函数(很关键),它扫描txlist中的每个epitem,调用其关联fd对用的poll方法。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。
水平与边缘触发
水平触发EPOOLLT:默认模式,即fd就绪时,若fd对应的buffer数据未处理,则在下一次调用epoll_wait时会继续发送这个事件,直到就绪的fd对应的缓冲区数据被处理完,这种模式易于编程,不用担心事件数据丢失。缺点是需要不断调用epoll_wait系统调用来获取就绪事件,存在性能问题。水平触发的实现原理为:在调用完ep_send_events之后又执行了ep_reinject_items函数,该函数会把没有处理过的事件再次放到rdlist,因此下次调用epoll_wait时发现rdlist不为空就直接返回就绪事件了,因此看到的现象是会一直触发直到事件被处理。
边缘触发EPOLLET:当调用epoll_wait返回就绪事件后,若该事件未被处理,则下一次epoll_wait调用不会重复返回该事件,对程序编码要求较高,以防止事件未被处理而丢失,但该模式可以减少系统调用,提升效率。