请考虑以下计划:
#define _GNU_SOURCE
#include <sys/epoll.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
int verify(int result, const char *msg) {
if( result>=0 )
return result;
perror(msg);
abort();
return -1;
}
void writepipe( int fd, int num_bytes, const char *msg ) {
unsigned char buffer[num_bytes];
ssize_t num_written = verify( write(fd, buffer, num_bytes), msg );
assert( num_written==num_bytes );
}
void readpipe( int fd, int num_bytes, const char *msg ) {
unsigned char buffer[num_bytes];
ssize_t num_read = verify( read(fd, buffer, num_bytes), msg );
assert( num_read==num_bytes );
}
int main() {
int pipefds[2];
verify( pipe2(pipefds, O_NONBLOCK), "pipe creation failed" );
int epollfd = verify(epoll_create1(0), "epoll creation failed");
struct epoll_event evt;
evt.events = EPOLLIN|EPOLLET;
evt.data.u64 = 17;
verify( epoll_ctl( epollfd, EPOLL_CTL_ADD, pipefds[0], &evt ), "epoll_add failed" );
int num_events = verify( epoll_wait(epollfd, &evt, 1, 0), "epoll_wait failed" );
assert(num_events == 0);
writepipe( pipefds[1], 12, "initial filling of pipe" );
num_events = verify( epoll_wait(epollfd, &evt, 1, 0), "epoll_wait failed" );
assert(num_events == 1);
assert(evt.data.u64 == 17);
num_events = verify( epoll_wait(epollfd, &evt, 1, 0), "epoll_wait failed" );
assert(num_events == 0);
readpipe( pipefds[0], 12, "clean the data" );
num_events = verify( epoll_wait(epollfd, &evt, 1, 0), "epoll_wait failed" );
assert(num_events == 0);
writepipe( pipefds[1], 3, "write no trigger" );
num_events = verify( epoll_wait(epollfd, &evt, 1, 0), "epoll_wait on unarmed fd" );
assert(num_events == 0);
return 0;
}
最后一个断言失败。
由于我们从未从epoll中读过EPOLLET
,我期待最后epoll_wait
返回0.相反,我得到1.
为什么?
来自Ubuntu 16.10的内核4.13.0-39-generic。
答案 0 :(得分:0)
答案较晚,但也许对其他人还是有帮助的。
您假设在上一个epoll_wait调用中,fd处于未设防状态。不是这种情况。如果您实际上希望将其解除武装,则可以使用EPOLLONESHOT。触发一次后,您就必须重新整理它以进行epoll。 您可能还假设第二次写入不会导致epoll触发。这个假设也是错误的。只要FD上没有更改,EPOLLET仅保证EPOLLET不会再次被触发。管道上的写入会触发更改,因此会触发epoll(不一定是人们期望发生的事情)。
原因 因为这是边缘触发模式仅在 更改发生在受监视的文件描述符上。
来源:video
我真的不知道您所说的“我们永远不会读取EPOLLET”是什么意思,您的意思是表示所有数据已被读取的EAGAIN吗?这实际上与您的问题无关。您完全清空了管道。因此,下一次读取将导致EAGAIN,但这不会更改上述行为。即使您不读取数据,第二次写入也会触发epoll。检查EAGAIN只是为了确保如果文件描述符上没有任何更改,我们将完全读取所有数据。