边缘触发的 Epoll 和 oneshot 只报告一次

Epoll with edge triggered and oneshot only reports once

我目前正在将 accept 创建的 sockfds 添加到具有以下事件的 epoll 实例:

const int EVENTS = (
    EPOLLET |
    EPOLLIN |
    EPOLLRDHUP |
    EPOLLONESHOT |
    EPOLLERR |
    EPOLLHUP);

一旦事件被触发,我将其传递给处理程序线程,读取并使用相同的标志通过 epoll_ctl 重新启用 sockfd。但是,我只收到一次 EPOLLIN 事件。此外,如果我在收到第一个事件后随时终止客户端,我也不会收到挂断事件。通过阅读手册页,我认为我理解了 EdgeTriggered 和 OneShot 的正确方法。

下面是我正在使用的过程的一些伪代码:

const int EVENTS = (
    EPOLLET |
    EPOLLIN |
    EPOLLRDHUP |
    EPOLLONESHOT |
    EPOLLERR |
    EPOLLHUP);

void event_loop()
{
    struct epoll_event event;
    struct epoll_event *events;
    events = calloc(100, sizeof event);
    while (1)
    {
        int x;
        int num_events = epoll_wait(epfd, events, 100, -1);
        for (x = 0; x < num_events; x++)
        {
            another_thread(fd);
        }
    }
}

void another_thread(int fd)
{
    // Read stuff until EAGAIN

    struct epoll_event event;
    event.data.fd = fd;
    event.events = EVENTS;
    epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event);
}

当我执行 EPOLL_CTL_MOD 操作时,我没有收到任何错误,但从未收到其他事件的通知。如果我在第一个事件后让读取循环保持重复,它将读取客户端发送的所有后续数据,所以我知道数据正在进入并且 fd 仍然打开并工作。

根据检查 strace,线程是从 clone 创建的并具有标志 CLONE_FILES,因此所有线程共享相同的 fd table.

为来自单独线程的读取事件重新启用 fd 的正确方法是什么?

However, I only receive the EPOLLIN event one time. Also, if I kill the client anytime after the first event is received, I do not get hangup events either.

epoll_ctl(2) 的手册页说:

EPOLLONESHOT (since Linux 2.6.2) Sets the one-shot behavior for the associated file descriptor. This means that after an event is pulled out with epoll_wait(2) the associated file descriptor is internally disabled and no other events will be reported by the epoll interface. The user must call epoll_ctl() with EPOLL_CTL_MOD to rearm the file descriptor with a new event mask.

在您的情况下,当您获得第一个事件时,epoll 会禁用您的 sockfd。 当您使用 EPOLL_CTL_MOD 重新启用 sockfd 时,它会通知内核 重新注册后收到的所有事件.因此,第一次通知和重新注册之间的任何事件都将丢失。这可能是没有获得任何挂断事件或数据的原因。

从事件中删除 EPOLLONESHOT 将更正您的代码,最终您也不需要重新启用 sockfd。

并且由于您使用的是 EPOLLET,因此也不会有任何性能问题。