简体   繁体   English

使用 epoll_wait 和 signalfd 处理信号

[英]Handle signals with epoll_wait and signalfd

I'm writing my own echo server using sockets and syscalls.我正在使用套接字和系统调用编写自己的echo server I am using epoll to work with many different clients at the same time and all the operations done with clients are nonblocking.我正在使用epoll同时与许多不同的客户端一起工作,并且对客户端完成的所有操作都是非阻塞的。 When the server is on and doing nothing, it is in epoll_wait .当服务器打开并且什么都不做时,它在epoll_wait Now I want to add the possibility to shut the server down using signals.现在我想添加使用信号关闭服务器的可能性。 For example, I start the server in bash terminal , then I press ctrl-c and the server somehow handles SIGINT .例如,我在bash terminal启动服务器,然后按ctrl-c并且服务器以某种方式处理SIGINT My plan is to use signalfd .我的计划是使用signalfd I create new signalfd and add it to epoll instance with the following code:我创建新的signalfd并使用以下代码将其添加到epoll实例:

    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGINT);
    signal_fd = signalfd(-1, &mask, 0);

    epoll_event event;
    event.data.fd = signal_fd;
    event.events = EPOLLIN;
    epoll_ctl(fd, EPOLL_CTL_ADD, signal_fd, &event);

Then I expect, that when epoll is waiting and I press ctrl-c , event on epoll happens, it wakes up and then I handle the signal with the following code:然后我期望,当epoll正在等待并且我按下ctrl-cepoll事件发生,它被唤醒,然后我使用以下代码处理信号:

    if (events[i].data.fd == signal_fd)
    {
        //do something
        exit(0);
    }

Though in reality the server just stops without handling the signal.虽然实际上服务器只是停止而不处理信号。 What am I doing wrong, what is the correct way to solve my problem?我做错了什么,解决我的问题的正确方法是什么? And if I'm not understanding signals correctly, what is the place, where the one should use signalfd ?如果我没有正确理解信号,那么应该使用signalfd的地方是什么?

epoll_wait returns -1 and errno == EINTR when it is interrupted by a signal. epoll_wait在被信号中断时返回-1errno == EINTR In this case you need to read from signal_fd .在这种情况下,您需要从signal_fd read

Set the signal handler for your signals to SIG_IGN , otherwise signals may terminate your application.将信号的信号处理程序设置为SIG_IGN ,否则信号可能会终止您的应用程序。

See man signal 7 :参见man 信号 7

The following interfaces are never restarted after being interrupted by a signal handler, regardless of the use of SA_RESTART ;无论是否使用SA_RESTART ,以下接口在被信号处理程序中断后永远不会重新启动; they always fail with the error EINTR when interrupted by a signal handler:当被信号处理程序中断时,它们总是以错误EINTR失败:

  • File descriptor multiplexing interfaces: epoll_wait(2), epoll_pwait(2), poll(2), ppoll(2), select(2), and pselect(2).文件描述符复用接口:epoll_wait(2)、epoll_pwait(2)、poll(2)、ppoll(2)、select(2) 和 pselect(2)。

Though in reality the server just stops without handling the signal.虽然实际上服务器只是停止而不处理信号。 What am I doing wrong, what is the correct way to solve my problem?我做错了什么,解决我的问题的正确方法是什么? And if I'm not understanding signals correctly, what is the place, where one should use signalfd?如果我没有正确理解信号,那么应该使用signalfd 的地方是什么?

Signal handlers are per process.信号处理程序是每个进程。 You left the signal handler at the default, which is to terminate the processes.您将信号处理程序保留为默认值,即终止进程。

So you need to add something like this,所以你需要添加这样的东西,

struct sigaction action;
std::memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = your_handler;
sigaction(signum, &action, NULL);

for each signum that you want your application to receive interrupts for.对于您希望应用程序接收中断的每个符号。 Also handle the return value of sigaction.还要处理sigaction的返回值。 My experience is that if you use SIG_IGN as handler than you still interrupt a system call like epoll_pwait from the "outside", but it won't work when you try to wake up the thread from the program itself by sending the signal directly to that thread using pthread_kill .我的经验是,如果您使用 SIG_IGN 作为处理程序,那么您仍然会从“外部”中断像 epoll_pwait 这样的系统调用,但是当您尝试通过直接向程序发送信号来从程序本身唤醒线程时,它将无法工作线程使用pthread_kill

Next you need to mask all signals from every thread, so that by default no thread will receive it (otherwise a random thread is woken up to handle the signal).接下来您需要屏蔽来自每个线程的所有信号,以便默认情况下没有线程会接收它(否则随机线程被唤醒来处理信号)。 The easiest way to do that is by doing it in main before creating any thread.最简单的方法是在创建任何线程之前在 main 中执行它。

For example,例如,

sigset_t all_signals;
sigemptyset(&all_signals);
sigaddset(&all_signals, signum); // Repeat for each signum that you use.
sigprocmask(SIG_BLOCK, &all_signals, NULL);

And then unblock the signals per thread when you want that thread to receive the signal.然后在您希望该线程接收信号时解除每个线程的信号阻塞。

If you use signalfd , then you do not want to unblock them - that system call unblocks the signals itself, just pass the appropriate mask (set bits for signalfd (it uses the passed mask to unblock). See also the man page of signalfd).如果您使用signalfd ,那么您不想解除它们的阻塞 - 该系统调用本身解除阻塞信号,只需传递适当的掩码(为signalfd设置位(它使用传递的掩码来解除阻塞)。另请参阅 signalfd 的手册页) .

epoll_pwait works differently; epoll_pwait工作方式不同; like pselect you unblock the signal that you are interested in. You set a handler for that signal (see above) that sets a flag.pselect一样,您可以解锁您感兴趣的信号。您为该信号设置一个处理程序(见上文),设置一个标志。 Then just before calling epoll_pwait you block the signal, then test the flag and handle it, and then call epoll_pwait without first unblocking the signal.然后在调用epoll_pwait之前阻塞信号,然后测试标志并处理它,然后调用epoll_pwait而不先解除信号阻塞。 After epoll_wait returns you can unblock the signal again so that your handler can be called again.在 epoll_wait 返回后,您可以再次解除对信号的阻塞,以便您的处理程序可以再次被调用。

You have to block all the signals you want to handle with your signal-FD before you create that signal-FD.在创建信号 FD 之前,您必须阻止所有要使用信号 FD 处理的信号。 Otherwise, those signals still interrupt blocked system calls such as epoll_wait() - as you observed.否则,这些信号仍会中断阻塞的系统调用,例如epoll_wait() - 正如您所观察到的。

See also the signalfd(2) man page :另请参阅signalfd(2) 手册页

Normally, the set of signals to be received via the file descriptor should be blocked using sigprocmask(2) , to prevent the signals being handled according to their default dispositions.通常,应使用sigprocmask(2)阻止要通过文件描述符接收的信号集,以防止根据其默认配置处理信号。

Thus, you have to change your example like this:因此,您必须像这样更改示例:

    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGINT);
    int r = sigprocmask(SIG_BLOCK, &mask, 0);
    if (r == -1) {
        // XXX handle errors
    }
    signal_fd = signalfd(-1, &mask, 0);
    if (signal_fd == -1) {
        // XXX handle errors
    }

    epoll_event event;
    event.data.fd = signal_fd;
    event.events = EPOLLIN;
    r = epoll_ctl(fd, EPOLL_CTL_ADD, signal_fd, &event);
    if (r == -1) {
        // XXX handle errors
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM