【问题标题】:Handle signals with epoll_wait and signalfd使用 epoll_wait 和 signalfd 处理信号
【发布时间】:2017-08-29 22:35:48
【问题描述】:

我正在使用套接字和系统调用编写自己的echo server。我正在使用epoll 同时与许多不同的客户端一起工作,并且与客户端完成的所有操作都是非阻塞的。当服务器打开并且什么都不做时,它位于epoll_wait。现在我想添加使用信号关闭服务器的可能性。例如,我在bash terminal 中启动服务器,然后按ctrl-c,服务器以某种方式处理SIGINT。我的计划是使用signalfd。我创建了新的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);

然后我希望,当epoll 正在等待并且我按下ctrl-c 时,epoll 上的事件会发生,它会唤醒,然后我使用以下代码处理信号:

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

虽然实际上服务器只是停止而不处理信号。我做错了什么,解决我的问题的正确方法是什么?如果我没有正确理解信号,那么应该在哪里使用signalfd

【问题讨论】:

  • 来自精品手册: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 解决了所有问题!

标签: c sockets signals epoll


【解决方案1】:

在创建信号 FD 之前,您必须阻止所有要使用信号 FD 处理的信号。否则,这些信号仍然会中断阻塞的系统调用,例如 epoll_wait() - 正如您所观察到的那样。

另见signalfd(2) man page

通常,通过文件描述符接收的一组信号 应使用sigprocmask(2) 阻止,以防止信号被 根据他们的默认处置进行处理。

因此,您必须像这样更改示例:

    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
    }

【讨论】:

    【解决方案2】:

    虽然实际上服务器只是停止而不处理信号。我做错了什么,解决我的问题的正确方法是什么?如果我没有正确理解信号,那么应该在哪里使用 signalfd?

    信号处理程序是每个进程的。您将信号处理程序保留为默认值,即终止进程。

    所以你需要添加这样的东西,

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

    对于您希望应用程序接收中断的每个签名。还要处理 sigaction 的返回值。我的经验是,如果您使用 SIG_IGN 作为处理程序,那么您仍然会从“外部”中断像 epoll_pwait 这样的系统调用,但是当您尝试通过直接发送信号来从程序本身唤醒线程时,它将不起作用线程使用pthread_kill

    接下来,您需要屏蔽来自每个线程的所有信号,以便默认情况下没有线程会接收它(否则会唤醒随机线程来处理信号)。最简单的方法是在创建任何线程之前在 main 中执行此操作。

    例如,

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

    然后,当您希望每个线程接收信号时,取消阻塞每个线程的信号。

    如果您使用signalfd,那么您不想解除阻塞 - 系统调用会解除对信号本身的阻塞,只需传递适当的掩码(为signalfd 设置位(它使用传递的掩码解除阻塞)。参见也是signalfd的手册页)。

    epoll_pwait 工作方式不同;就像pselect 一样,您解除对您感兴趣的信号的阻塞。您为该信号设置一个处理程序(见上文),该处理程序设置一个标志。然后在调用epoll_pwait 之前阻塞信号,然后测试标志并处理它,然后调用epoll_pwait 而不先解除阻塞信号。在 epoll_wait 返回后,您可以再次解除对信号的阻塞,以便再次调用您的处理程序。

    【讨论】:

      【解决方案3】:

      epoll_wait 在被信号中断时返回-1errno == EINTR。在这种情况下,您需要从signal_fdread

      将信号的信号处理程序设置为SIG_IGN,否则信号可能会终止您的应用程序。

      man signal 7:

      以下接口被中断后永远不会重启 一个信号处理程序,不管SA_RESTART的使用;他们总是失败 被信号处理程序中断时出现错误EINTR

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

      【讨论】:

      • 感谢您的回答和参考!但我仍然无法处理信号。当 signalfd 在 epoll 中时,我有 epoll_wait 系统调用。然后,当我从 bash 发送信号时,似乎程序没有进一步执行。所以如果 (return_value_of_epoll_wait == -1) 条件在发送信号时永远不会被检查。我该如何处理这个问题?非常感谢提前!
      • @AleksandrTukallo 为您添加了关于信号处理程序的注释。
      • 如果信号被屏蔽,那么 signalfd 是完全可轮询的,还有 epoll_pwait 可以接受信号屏蔽。 poll(2), select(2) (和类似的)如果掩码中的一个或多个信号对进程未决,则文件描述符是可读的(select(2) readfds 参数;poll(2) POLLIN 标志)。 signalfd 文件描述符还支持其他文件描述符复用 API:pselect(2)、ppoll(2) 和 epoll(7)。
      • @Maquefel 不清楚你想说什么。添加您自己的答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-08
      • 2014-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多