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

时间:2018-01-31 11:43:22

标签: c select posix

POSIX pselect function采用信号掩码参数。信号掩码是原子地"在函数执行开始之前设置为当前掩码,并在函数返回时恢复。

这允许在函数执行时屏蔽未屏蔽的信号,并在函数返回时再次屏蔽。它保证 * ,如果捕获了以这种方式未屏蔽的信号,pselect函数将被信号中断(除非用{{指定信号动作) 1}} flag)将返回SA_RESTART错误。

(*:或者是吗?上面链接的文档中的语言似乎允许在EINTR由于看到文件准备就绪或超时而被阻止时之间接收​​信号当它用原始信号替换信号掩码时不一定会导致pselect,因为如果"该功能在被阻止时被中断,则需要EINTR ... - 然而,最终并不会影响这个问题。)

我的问题是:假设在EINTR执行期间暂时取消屏蔽两个单独的信号,是否有可能在pselect函数返回并恢复先前的信号掩码之前捕获这两个信号 - 或者是否有某种保证,在这种情况下只会捕获一个信号(另一个信号待处理)? (出于问题的目的,假设没有为信号操作设置pselect,并且在通过SA_RESTART建立信号处理程序时执行信号处理程序时指定所有信号都被屏蔽。 / p>

我找不到任何暗示只能处理一个信号的东西,但我可能错过了一些东西,而且我正在编写一些代码,这将是一个非常有用的保证。我有兴趣知道POSIX本身是否有任何保证,以及不同的操作系统是否独立提供此类保证。

2 个答案:

答案 0 :(得分:1)

No, but it also doesn’t specify that multiple signals can or must. Since it is unspecified, it is best to follow the general rule, which allows all pending unmasked signals to be processed. If you attempt to strictly depend upon this, you are likely on a bad path because the timing of asynchronous events is difficult to predict.

In general, it would be very difficult to make an implementation that imposed an ‘only one' restriction because the os runtime would have to leave one or more signals pending but unmasked until some unspecified point. Remember that the signal handler which runs when pselect is interrupted could do a siglongjmp rather than returning, so the kernel would have to keep a complicated, possibly unbounded data structure to track which signal mask to enforce.

Below is a modified version of your test program. In this one, each event emits a string via write() so there are no buffering problems. The program sets its “main” environment to mask SIGUSR1, SIGUSR2; but while pselect is running, it permits SIGUSR1, SIGUSR2, SIGTERM. The program forks, with the parent (default:) sitting in a loop invoking pselect(), then outputting ‘.’ after it completes. The child sits in a loop, delivering SIGUSR1, SIGUSR2 to the parent, then sleeping for a bit. It outputs ‘^’ after delivering the signals. The handler emits a prefix “(1” or “(2” for SIGUSR1, SIGUSR2 resp; then sleeps for a bit, and outputs “)” to indicate the sleep has completed.

The output I see on macos (10.12.6, but I doubt it matters much) is: ^(2)(1).^(2)(1).^(2)(1).^(2)(1).Terminated: 15 which indicates that the signal handler for each of SIGUSR1 and SIGUSR2 are being run for every invocation of pselect(). This is what I would expect; as it is designed to not admit a window of uncertainty as would be the case with bracketting select() with sigprocmasks().

#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;
}

答案 1 :(得分:0)

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

在Linux下,如果我正确理解下面的代码,只能处理一个信号(假设信号处理程序本身不会取消屏蔽信号):相关代码和显示注释位于fs / select.c,in 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的支持。