epoll 事件不会引发 SIGIO

epoll events do not raise SIGIO

根据我的研究,您可以添加一个 epoll 文件描述符来轮询,select 或另一个 epoll,如果事件可用,它将 return POLLIN。根据 epoll(7):

   Q3  Is the epoll file descriptor itself poll/epoll/selectable?

   A3  Yes.  If an epoll file descriptor has events waiting, then it  will
       indicate as being readable.

这在我的测试中有效。但是,我现在正尝试将 O_ASYNC 应用于我的 epoll fd,以便在事件准备好时它会引发 SIGIO:

//Epoll setup code - trySc runs perror on the string and exits if the function returns -1
int epollFd = epoll_create1(EPOLL_CLOEXEC);
trySc(epollFd, "epoll_create1");
trySc(fcntl(epollFd, F_SETOWN, getpid()), "fcntl F_SETOWN");
trySc(fcntl(epollFd, F_SETSIG, SIGIO), "fcntl F_SETSIG");
int oldFlags = fcntl(epollFd, F_GETFL, 0);
trySc(oldFlags, "fcntl F_GETFL");
trySc(fcntl(epollFd, F_SETFL, oldFlags | O_ASYNC), "fcntl F_SETFL");

//Set up SIGIO and get a socket fd, you don't need to see this
//All my handler does is exit - it's a definite method of knowing SIGIO was raised

struct epoll_event event = {
        .events = EPOLLIN | EPOLLET,
        .data.fd = socketFd
};
trySc(epoll_ctl(epollFd, EPOLL_CTL_ADD, socketFd, &event), "epoll_ctl");
//Then connect
while(1){
        struct pollfd pfd = {
                .fd = epollFd,
                .events = POLLIN
        };
        //This blocks until I write something to the socket. Then it enters an infinite loop.
        printf("Returning to main(), poll = %d\n", poll(&pfd, 1, -1));
}

当我这样做时,它不会为 epoll 中的新事件引发 SIGIO。 Poll 表明 epollFd 中有事件就绪,但它应该首先引发 SIGIO(它只是检查事件是否在 epollFd 中并退出)。我知道我可以将 O_ASYNC 应用于套接字(我也试过这个)但我希望在我的事件中包含数据。 Epoll 让我这样做。

这是我的完整代码:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <netdb.h>
#include <poll.h>
#include <signal.h>
#include <errno.h>

void handleSIGIO(int, siginfo_t *, void *);

const struct sigaction saSIGIO = {
    .sa_sigaction = handleSIGIO,
    .sa_flags = SA_SIGINFO
};

const struct addrinfo hints = {
    .ai_flags = AI_PASSIVE,
    .ai_family = AF_INET,
    .ai_socktype = SOCK_STREAM
};

void trySc(int err, const char *msg){
    if(err != -1) return;
    perror(msg);
    exit(errno);
}

int main(){
    signal(SIGPIPE, SIG_IGN);
    trySc(sigaction(SIGIO, &saSIGIO, NULL), "sigaction");
    int epollFd = epoll_create1(EPOLL_CLOEXEC);
    trySc(epollFd, "epoll_create1");
    trySc(fcntl(epollFd, F_SETOWN, getpid()), "fcntl F_SETOWN");
    trySc(fcntl(epollFd, F_SETSIG, SIGIO), "fcntl F_SETSIG");
    int oldFlags = fcntl(epollFd, F_GETFL, 0);
    trySc(oldFlags, "fcntl F_GETFL");
    trySc(fcntl(epollFd, F_SETFL, oldFlags | O_ASYNC), "fcntl F_SETFL");
    int socketFd = socket(AF_INET, SOCK_STREAM, 0);
    trySc(socketFd, "socket");
    struct addrinfo *servinfo;
    trySc(getaddrinfo("127.0.0.1", "5000", &hints, &servinfo),
        "getaddrinfo");
    struct epoll_event event = {
        .events = EPOLLIN | EPOLLET,
        .data.fd = socketFd
    };
    trySc(epoll_ctl(epollFd, EPOLL_CTL_ADD, socketFd, &event), "epoll_ctl");
    trySc(connect(socketFd, servinfo->ai_addr, servinfo->ai_addrlen),
        "connect");
    printf("Connected\n");
    while(1){
        struct pollfd pfd = {
            .fd = epollFd,
            .events = POLLIN
        };
        printf("Returning to main(), poll = %d\n", poll(&pfd, 1, -1));
    }
}

void handleSIGIO(int sn, siginfo_t *info, void *ctx){
    printf("SIGIO called\n");
    struct epoll_event event;
    if(epoll_wait(info->si_fd, &event, 1, 0) != 1){
        printf("Warning: no event available\n");
        return;
    }
    printf("Event raised for fd %d\n", event.data.fd);
    exit(0);
}

编辑:根据 this website,我尝试做的应该有效:

Note that an epoll set descriptor can be used much like a regular file 
descriptor. That is, it can be made to generate SIGIO (or another signal)
when input (i.e. events) is available on it; likewise it can be used with 
poll() and can even be stored inside another epoll set.

经过更多研究,我发现这是不可能的。根据open(2)

O_ASYNC

Enable signal-driven I/O: generate a signal (SIGIO by default, but this can be changed via fcntl(2)) when input or output becomes possible on this file descriptor. This feature is available only for terminals, pseudoterminals, sockets, and (since Linux 2.6) pipes and FIFOs. See fcntl(2) for further details. See also BUGS, below.

我真的希望我可以将 epoll 与 SIGIO 一起使用。我会找到我能做的其他事情。

我找到了这个问题的解决方案:将 O_ASYNC 应用于每个套接字,然后在您的 SIGIO 处理程序中,在您的 epoll fd 上调用 epoll_wait。然后像往常一样处理来自 epoll 的事件。它不会与不支持 SIGIO 的 FD 一起使用,即使它们支持 epoll,但它可以与套接字和管道一起使用,这可能是异步 IO 最常见的用法。