代码之家  ›  专栏  ›  技术社区  ›  davmac

POSIX是否指定只有一个信号可以中断pselect?

  •  9
  • davmac  · 技术社区  · 6 年前

    这个 POSIX pselect function 取一个信号掩码参数。在函数开始执行之前,“原子地”将信号掩码设置为当前掩码,并在函数返回时恢复。

    这允许在函数执行时解除屏蔽其他屏蔽信号,并在函数返回时再次屏蔽。这是有保证的 * 如果捕获到以这种方式显示的信号 P选择 功能将被信号和中断(除非用 SA_RESTART 标志)将返回 EINTR 错误

    (*:还是?上面链接的文档中的语言似乎允许接收信号 之间 什么时候 P选择 由于看到文件准备就绪或超时而取消阻止,并且用原始文件替换信号掩码时不一定会导致 EINTR公司 自从 EINTR公司 如果“功能在被阻止时被中断…”,则需要-然而,这最终不会影响这个问题)。

    我的问题是:假设在 P选择 执行时,是否可能在 P选择 函数返回并恢复以前的信号掩码-或者是否有某种保证,在这种情况下,只捕获一个信号(另一个信号挂起)?(就问题而言,假设 SA\U重新启动 未为信号操作设置,并且指定在通过 sigaction ).

    我找不到任何迹象表明只能处理一个信号,但我可能遗漏了什么,我正在编写一些代码,这将是一个非常有用的保证。我很想知道POSIX本身是否有任何保证,以及不同的操作系统是否独立提供这种保证。

    2 回复  |  直到 6 年前
        1
  •  1
  •   mevets    6 年前

    不,但它也没有指定多个信号可以或必须。由于未指定,因此最好遵循一般规则,该规则允许处理所有挂起的未屏蔽信号。如果您试图严格依赖这一点,那么您可能会走上一条错误的道路,因为异步事件的时间很难预测。

    一般来说,很难实现一个只施加一个限制的实现,因为os运行时必须保留一个或多个挂起的信号,但在某个未指定的点之前不加屏蔽。请记住,当pselect被中断时运行的信号处理程序可以执行siglongjmp,而不是返回,因此内核必须保持一个复杂的、可能没有边界的数据结构,以跟踪要执行的信号掩码。

    下面是您的测试程序的修改版本。在本例中,每个事件都通过write()发出一个字符串,因此没有缓冲问题。程序将其–main–环境设置为屏蔽SIGUSR1、SIGUSR2;但当pselect运行时,它允许SIGUSR1、SIGUSR2、SIGTERM。 程序分叉,父级(默认值:)位于调用pselect()的循环中,然后输出。完成后。 孩子们坐成一个圈,把SIGUSR1、SIGUSR2传递给父母,然后睡一会儿。发送信号后输出^。 处理程序发出一个前缀(1或(2表示SIGUSR1,SIGUSR2 resp;然后休眠一位,并输出)以指示休眠已完成。

    我在macos(10.12.6,但我怀疑这是否重要)上看到的输出是: ^(2)(1).^(2)(1).^(2)(1).^(2)(1).终止日期:15 这表示每次调用pselect()时,都会运行SIGUSR1和SIGUSR2的信号处理程序。这就是我所期望的;因为它的设计不允许出现不确定性窗口,就像使用sigprocmasks()的括号select()一样。

    #include <stdio.h>
    #include <signal.h>
    #include <sys/select.h>
    #include <unistd.h>
    
    void handle(int signo)
    {
        char s[2];
        s[0] = '(';
        s[1] = signo == SIGUSR1? '1' : '2';
        write(1, s, 2);
        sleep(1);
        write(1, ")", 1);
    }
    
    int main(int argc, char **argv)
    {
      sigset_t mask;
      sigemptyset(&mask);
      sigaddset(&mask, SIGUSR1);
      sigaddset(&mask, SIGUSR2);
      sigprocmask(SIG_SETMASK, &mask, NULL);
    
      sigfillset(&mask);
      sigdelset(&mask, SIGUSR1);
      sigdelset(&mask, SIGUSR2);
      sigdelset(&mask, SIGTERM);
      signal(SIGUSR1, handle);
      signal(SIGUSR2, handle);
      pid_t t = fork();
      switch (t) {
      default:
        while (1) {
                /* no USR1, USR2 */
                pselect(0, NULL, NULL, NULL, NULL, &mask);
                /* no USR1, USR2 */
                write(1, ".", 1);
        }
        break;
      case 0:
        t = getppid();
        for (int i = 0; i < 4; i++) {
            kill(t, SIGUSR1);
            kill(t, SIGUSR2);
            write(1, "^", 1);
            sleep(5);
        }
        kill(t, SIGTERM);
        break;
      case -1:
        perror("fork\n");
      }
      return 0;
    }
    
        2
  •  0
  •   davmac    6 年前

    我一直在继续搜索,没有发现任何其他信息,所以我只能得出结论,POSIX中一般没有保证。

    在Linux下,如果我正确理解了下面的代码,那么只能处理一个信号(假设信号处理程序本身没有取消屏蔽信号):相关的代码和一条说明性的注释在fs/select中。c、 在 do_pselect 功能:

    ret = core_sys_select(n, inp, outp, exp, to);
    ret = poll_select_copy_remaining(&end_time, tsp, 0, ret);
    
    if (ret == -ERESTARTNOHAND) {
        /*
         * Don't restore the signal mask yet. Let do_signal() deliver
         * the signal on the way back to userspace, before the signal
         * mask is restored.
         */
        if (sigmask) {
            memcpy(&current->saved_sigmask, &sigsaved,
                    sizeof(sigsaved));
            set_restore_sigmask();
        }
    } else ...
    

    它基本上从系统调用返回,允许信号处理程序执行,然后立即恢复原始信号掩码(从 current->saved_sigmask 因为 set_restore_sigmask() 设置指示应发生此情况的标志)。

    以下测试程序对此进行验证:

    #include <stdio.h>
    #include <signal.h>
    #include <sys/select.h>
    
    volatile sig_atomic_t got_usr1 = 0;
    volatile sig_atomic_t got_usr2 = 0;
    
    void handle_usr1(int signo, siginfo_t *info, void *v)
    {
      got_usr1 = 1;
    }
    
    void handle_usr2(int signo, siginfo_t *info, void *v)
    {
      got_usr2 = 1;
    }
    
    int main(int argc, char **argv)
    {
      // mask SIGUSR1 and SIGUSR2:
      sigset_t curmask;
      sigemptyset(&curmask);
      sigaddset(&curmask, SIGUSR1);
      sigaddset(&curmask, SIGUSR2);
      sigprocmask(SIG_SETMASK, &curmask, NULL);
    
      // Create a mask for all but SIGUSR1 and SIGUSR2:
      sigset_t mask;
      sigfillset(&mask);
      sigdelset(&mask, SIGUSR1);
      sigdelset(&mask, SIGUSR2);
    
      // Set up signal handlers:
      struct sigaction action;
      action.sa_sigaction = handle_usr1;
      sigfillset(&action.sa_mask);
      action.sa_flags = SA_SIGINFO;
      sigaction(SIGUSR1, &action, NULL);
    
      action.sa_sigaction = handle_usr2;
      sigaction(SIGUSR2, &action, NULL);
    
      // Make signals pending:
      raise(SIGUSR1);
      raise(SIGUSR2);
    
      // pselect with no file descriptors and no timeout:
      pselect(0, NULL, NULL, NULL, NULL, &mask);
    
      int count = got_usr1 + got_usr2;
    
      printf("Handled %d signals while in pselect.\n", count);
      return 0;
    }
    

    在Linux上,上述输出一致:

    在pselect中处理1个信号。

    FreeBSD似乎也是如此;然而,我不希望在所有其他平台上都出现这种情况。我找到的确保只能处理一个信号的解决方案是 siglongjmp 跳出信号处理程序以及 pselect 调用,同时恢复信号掩码,以便无法处理更多信号。

    基本上,该代码如下所示:

        jmp_buf jbuf; // signal handlers have access to this
    
        if (sigsetjmp(jbuf, 1) != 0) {
            // We received a signal while in pselect ...
        }
    
        int r = pselect(nfds, &read_set_c, &write_set_c, &err_set, wait_ts, &sigmask);
    

    信号处理程序必须执行 siglongjmp :

    void signal_handler(int signo, siginfo_t *siginfo, void *v)
    {
        siglongjmp(jbuf, 1);
    }
    

    这感觉很粗糙,但似乎在我测试过的所有平台(Linux、MacOS和FreeBSD)上都能正常工作,而且POSIX一般都支持它。