您的位置:首页 > 其它

Epoll 的LT和ET模式

2011-07-22 16:02 393 查看
man epoll The epoll event distribution interface is able to behave both as Edge Triggered ( ET ) and Level Triggered ( LT ). The difference between ET
and LT event distribution mechanism can be described as follows. Sup-
pose that this scenario happens :

1 The file descriptor that represents the read side of a pipe (
RFD ) is added inside the epoll device.

2 Pipe writer writes 2Kb of data on the write side of the pipe.

3 A call to epoll_wait(2) is done that will return RFD as ready
file descriptor.

4 The pipe reader reads 1Kb of data from RFD.

5 A call to epoll_wait(2) is done.

If the RFD file descriptor has been added to the epoll interface using
the EPOLLET flag, the call to epoll_wait(2) done in step 5 will proba-
bly hang because of the available data still present in the file input
buffers and the remote peer might be expecting a response based on the
data it already sent. The reason for this is that Edge Triggered event
distribution delivers events only when events happens on the monitored
file. So, in step 5 the caller might end up waiting for some data that
is already present inside the input buffer. In the above example, an
event on RFD will be generated because of the write done in 2 and the
event is consumed in 3. Since the read operation done in 4 does not
consume the whole buffer data, the call to epoll_wait(2) done in step 5
might lock indefinitely. The epoll interface, when used with the EPOL-
LET flag ( Edge Triggered ) should use non-blocking file descriptors to
avoid having a blocking read or write starve the task that is handling
multiple file descriptors. The suggested way to use epoll as an Edge
Triggered (EPOLLET) interface is below, and possible pitfalls to avoid
follow.

i with non-blocking file descriptors

ii by going to wait for an event only after read(2) or
write(2) return EAGAIN

On the contrary, when used as a Level Triggered interface, epoll is by
all means a faster poll(2), and can be used wherever the latter is used
since it shares the same semantics. Since even with the Edge Triggered
epoll multiple events can be generated up on receipt of multiple chunks
of data, the caller has the option to specify the EPOLLONESHOT flag, to
tell epoll to disable the associated file descriptor after the receipt
of an event with epoll_wait(2). When the EPOLLONESHOT flag is speci-
fied, it is caller responsibility to rearm the file descriptor using
epoll_ctl(2) with EPOLL_CTL_MOD.EPOLL事件分发系统可以运转在两种模式下:
Edge Triggered (ET)
Level Triggered (LT)
接下来说明ET, LT这两种事件分发机制的不同。我们假定一个环境:
1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符
2. 这个时候从管道的另一端被写入了2KB的数据
3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作
4. 然后我们读取了1KB的数据
5. 调用epoll_wait(2)......
Edge Triggered 工作模式:
如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。
i 基于非阻塞文件句柄
ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待
Level Triggered 工作模式
相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。
/////////////////////////////////////////////////////////////////////////////我是传说中的分割线//////////////////////////////////////////////////////////////////////////////////
下面两段是从网上找的epoll的较详细的解释: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确认(这句话不理解)。LT模式的时候,epoll_wait 会把有事件的 file 再次加到 rdllist 列表中,以便下次epoll_wait可以再检查一遍。 if (epi->event.events & EPOLLONESHOT)
epi->event.events &= EP_PRIVATE_BITS;
else if (!(epi->event.events & EPOLLET)) { //LT模式
/*
* If this file has been added with Level
* Trigger mode, we need to insert back inside
* the ready list, so that the next call to
* epoll_wait() will check again the events
* availability. At this point, noone can insert
* into ep->rdllist besides us. The epoll_ctl()
* callers are locked out by
* ep_scan_ready_list() holding "mtx" and the
* poll callback will queue them in ep->ovflist.
*/
list_add_tail(&epi->rdllink, &ep->rdllist);
}LT 模式下,状态不会丢失,程序完全可以于 epoll 来驱动。ET模式下,程序首先要自己驱动逻辑,如果遇到 EAGAIN错误的时候,就要依赖epoll_wait来驱动,这时epoll帮助程序从阻碍中脱离。ET 的驱动事件依靠 socket的 sk_sleep 等待队列唤醒,这只有在有新包到来才能发生,数据包导致POLLIN, ACK确认导致 sk_buffer destroy从而导致POLLOUT, 但这不是一对一得关系,是多对一(多个网络包产生一个POLLIN, POLLOUT事件)。
ET常见错误:recv到了合适的长度, 程序处理完毕后就epoll_wait。这时程序可能长期阻塞,因为这时socket的 rev_buffer里还有数据,或对端close了连接,但这些信息都在上次通知了你,你没有处理完,就epoll_wait了。
正确做法是:recv到了合适的长度, 程序处理; 再次recv, 若果是EAGAIN则epoll_wait。
这个问题尤其在多个线程同时对一个epoll调用epoll_wait更要注意。istenfd和几个epollfd关联了. 有几个epoll_wait在等待....
总结:ET 使用准则,只有出现EAGAIN错误才调用epoll_wait。如果accpet调用有返回,除了建立当前这个连接外,不能马上就epoll_wait还需要继续循环accpet,直到返回-1,且errno==EAGAIN,TAF里面的示例代码:

if(ev.events & EPOLLIN)
{
do
{
struct sockaddr_in stSockAddr;
socklen_t iSockAddrSize = sizeof(sockaddr_in);
TC_Socket cs;
cs.setOwner(false);
//接收连接
TC_Socket s;
s.init(fd, false, AF_INET);
int iRetCode = s.accept(cs, (struct sockaddr *) &stSockAddr, iSockAddrSize);
if (iRetCode > 0)
{
…建立连接
}
else
{
//直到发生EAGAIN才不继续accept
if(errno == EAGAIN)
{
break;
}
}
}while(true);
}

同样,recv/send等函数, 都需要到errno==EAGAIN

对于ET而言,当然也不一定非send/recv到前面所述的结束条件才结束,用户可以自己随时控制,即用户可以在自己认为合适的时候去设置IN和OUT事件:
1 如果用户主动epoll_mod OUT事件,此时只要该句柄可以发送数据(发送buffer不满),则epoll_wait就会响应(有时候采用该机制通知epoll_wai醒过来)。
2 如果用户主动epoll_mod IN事件,只要该句柄还有数据可以读,则epoll_wait会响应。
这种逻辑在普通的服务里面都不需要,可能在某些特殊的情况需要。 但是请注意,如果每次调用的时候都去epoll mod将显著降低效率! 因此采用et写服务框架的时候,最简单的处理就是:
建立连接的时候epoll_add IN和OUT事件, 后面就不需要管了。每次read/write的时候,到两种情况下结束:
1 发生EAGAIN
2 read/write的实际字节数小于 需要读写的字节数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: