Linux Epoll

Epoll描述

epoll: I/O事件通知机制

epoll是由poll进化而来,可用作边沿和水平(level)触发接口,并且能够很好的支持对大数量描述符的监视。系统提供了三个系统调用用来创建和控制epoll集合,分别是epoll_create, epoll_ctl, epoll_wait。

一个epoll集合与一个通过epoll_create创建的描述符相关联。创建epoll集合之后可以通过epoll_ctl来注册对某个文件描述符感兴趣的事件。最后,通过epoll_wait开始等待事件发生。

 

Notes

epoll事件分发接口支持边沿触发(EdgeTriggered)和水平触发(Level Triggered)。ET和LT事件分发机制的区别描述如下。假设发生如下情况:

1 代表管道读端(RFD)的文件描述符被添加进epoll设备

2 管道写者在管道写端写了2Kb的数据

3 epoll_wait调用完成,将返回RFD作为一个就绪文件描述符

4 管道读者从RFD读了1Kb的数据

5 调用epoll_wait

如果RFD文件描述符已经通过使用EPOLLET标志添加到epoll接口,第5步进行的epoll_wait调用将很可能挂起,因为可用的数据仍然在文件输入缓冲中并且远程对端可能正期待着一个对已发数据的响应。发生这种情况的原因是ET事件分发只有当所监听的文件上的事件发生时才发送事件。所以,在第5步,调用者可能会等待一些数据,这些数据已经在输入缓冲了。在上述例子中,由于第2步的完成而产生了一个RFD上的事件,这个事件在第三步被消费掉。然而第4步完成的读操作没有消费掉整个缓冲数据,第5步中的epoll_wait调用可能无限期的等待。当以EPOLLLT标志(Edge Triggered)来使用epoll接口时,应该使用非阻塞文件描述符,以避免一个正在处理多个文件描述符的任务因为一个阻塞读/写而使任务处于饥饿状态。如果要使用epoll作为ET接口,建议以如下方式使用,后面还讨论了可能遇到的陷阱。---- 对于这里的理解可以参考边缘触发和水平触发一节

i   与非阻塞文件描述符配合使用

ii  只有在read或write调用返回EAGAIN之后才继续调用wait来等待事件

以之相反,当作为LT接口使用epoll时,epoll相当于一个快速的poll,后者能够应用的场景都可以使用epoll,因为它们的语义是一样的。即时使用ET模式的epoll,收到多块数据时也会产生多个事件,调用者可以通过指定EPOLLONESHOT 标志,来告诉epoll在收到epoll_wait返回的一个事件之后禁用(不在监听)相关联的文件描述符。如果指定了EPOLLONESHOT标志,调用者要负责重新使能文件描述符,这可以通过以EPOLL_CTL_MOD使用epoll_ctl来完成。

 

建议用法实例

当把epoll作为水平触发接口使用时,它和poll语义是一样的。ET使用方法要求more clarification以避免在应用程序循环中的延迟。在下面这个例子中,listener(已经在其上调用了listen())是一个非阻塞socket. 函数do_use_fd()一直使用就绪的文件描述符直到read()或者write()返回EAGAIN。一个事件驱动状态机应用程序,应该在收到EAGAIN之后,记录下当前的状态以便在下一次调用do_use_fd()时,可以从之前停止的地方继续读或写。

 

struct epoll_event ev, *events;
for(;;) {
    nfds = epoll_wait(kdpfd, events, maxevents, -1);
    for(n = 0; n < nfds; ++n) {
        if(events[n].data.fd == listener) {
            client = accept(listener, (struct sockaddr *) &local,
                            &addrlen);
            if(client < 0){
                perror("accept");
                continue;
            }
            setnonblocking(client);
            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = client;
            if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
                fprintf(stderr, "epoll set insertion error: fd=%d0,
                        client);
                return -1;
            }
        }
        else
            do_use_fd(events[n].data.fd);
    }
}

当作为ET接口使用时,出于性能方面的考虑,可以通过指定(EPOLLIN | EPOLLOUT)标志来一次性把文件描述符加入到epoll中。这样做可以使你避免不断的通过epoll_ctl调用在EPOLLIN和EPOLLOUT直接切换。(如果指定了EPOLLIN就不会监视写就绪事件,如果指定了EPOLLOUT就不会监视读事件,所以要切换。如果指定调用标志为EPOLLIN | EPOLLOUT,就能够同时监视读就绪和写就绪事件)

QUESTIONS AND ANSWERS

Q1 如果两次添加同样的fd到一个epoll_set会怎么样/

A1 你或许会得到一个EEXIST错误。然而,这种情况是很可能发生的(两个线程可能添加同样的文件描述符两次)。不过不用担心,这不会引起什么副作用。

Q2 两个epoll sets可以等等同样的fd吗?如果可以,事件会同时报告给两个epoll sets fds吗?

A2 是的,可以。但是,不推荐这样做。是的事件将会同时报告给两者。

Q3 epoll fd本身是可以被poll/epoll/select的吗?

A3 是的

Q4 如果一个epoll fd被加入到它本身会怎么样?

A4 这样的操作是不回成功的,你可以把一个epollfd加入到另一个epoll set之中。

Q5 关闭一个文件描述符会导致其自动从epollset中删除吗?

A5 是的,会这样。

Q6 如果在两次调用epoll_wait之间有多个事件发生了,它们是一起报告给epoll_wait还是分别报告。

A6 一起报告

Q7 对fd的操作影响已经收集到但是还未报告的事件?

A7 你可以对一个存在的fd做两次同样的操作。删除情况对这种情况没有意义。Modify将导致重读可用的I/O。

Q8 当使用EPOLLET标志(边沿触发行为),我需要在一个fd上持续调用read/write直到返回EAGAIN吗?

A8 你不必非得这么做。从epoll_wait收到一个事件是告诉你某个文件描述符已经准备好了所请求的I/O操作。你必须简单的把它看做是一直处于就绪状态直到收到EAGAIN。什么时候、怎么样使用该文件描述符,完全取决于你。你还可以通过检查在目标文件描述符上执行的read/write返回的数据长度侦测到读/写 I/O空间耗尽的情况。例如,如果你调用read读取特定数量的数据,而read返回的数量小于请求数量,这样你就可以确定已经消耗掉该文件描述符上的所有的I/O空间(数据已经取完,可以继续调用epoll_wait了)。write的情况也是这样(write返回的数量小于请求的数量,表示写缓冲满了,应该调用epoll_waid等待写就绪)。

 

陷阱及规避方法

▷  饥饿(EdgeTriggred)

如果有大量的I/O空间,很可能在你消耗掉它之前其它文件都得不到处理,这将导致饥饿。换句话将,如果某个fd持续有数据可读,如果在返回EAGAIN之前你一直在该fd上调用read就会导致这种情况。并不是只有epoll才有这种情况。

解决方案是,维护一个就绪列表并且在相关结构中标记相应的fd处于就绪状态,这样应用程序可以记住哪些文件需要被处理,并循环在这些文件中分配合理的时间。

 

▷  如果使用事件缓存

如果你使用事件缓存或者存储了从epoll_wait中返回的所有fds, 之后要确保提供一种方法来动态标记它是否被关闭。假定你从epoll_wait中收到100个事件,并且在第47#事件中的一个条件导致13#事件被关闭。如果你移除了13#事件相关的结构并且关闭了相应的fd(没有把13#事件从事件缓存中删除),而你的事件缓存仍然认为有事件正在等待着那个fd,这将引起混乱。

一个解决方案是,在处理47#事件的时候,调用epoll_ctl(EPOLL_CTL_DEL)来删除fd 13并且把它关闭,然后还要标记与它相关联的结构已经被删除,把该fd链接到一个清除列表。如果你在你的批处理中发现了与fd 13相关联的另一个事件,你就会知道该fd之前已经被删除掉了,这样就没有混乱了。

 

边缘触发和水平触发

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
ET和LT的区别就在这里体现,LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。而ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。

原文:http://linux.die.net/man/4/epoll


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值