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

Does POSIX specify that only one signal can interrupt pselect?

POSIX pselect function 接受信号掩码参数。信号掩码被 "atomically" 设置为函数开始执行前的当前掩码,并恢复为函数 returns.

这允许在函数执行时解除屏蔽信号,并在函数 returns 时再次屏蔽。保证*,如果捕获到以这种方式未屏蔽的信号,pselect 函数将被信号中断并且(除非用 [=13 指定信号动作=] 标志)将 return 一个 EINTR 错误。

(*: 或者是吗?上面链接的文档中的语言似乎允许 之间收到信号,当 pselect 由于看到文件就绪或超时以及当它用原始信号掩码替换信号掩码时不一定会导致 EINTR,因为如果 "The function was interrupted while blocked ..." 则需要 EINTR - 但是,这最终不会影响这个问题) .

我的问题是:假设在 pselect 执行期间两个单独的信号被暂时暴露,这两个信号是否有可能在 pselect 函数 returns 和先前的信号掩码已恢复 - 或者是否有某种保证在这种情况下只会捕获一个信号(另一个未决)? (出于问题的目的,假设未为信号操作设置 SA_RESTART,并且当信号处理程序通过 sigaction 建立时,所有信号都被指定为在执行信号处理程序期间被屏蔽)。 =23=]

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

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

一般来说,很难实现实施os“只有一个”限制,因为os 运行时间必须留下一个或更多信号未决但未被屏蔽,直到某个未指定的点。请记住,当 pselect 被中断时 运行s 的信号处理程序可以执行 siglongjmp 而不是返回,因此内核必须保留一个复杂的 possibly 无限数据结构来跟踪要执行的信号掩码。

以下是您的测试程序的修改版本。在这一个中,每个事件都通过 write() 发出一个字符串,因此不存在缓冲问题。该程序将其“主要”环境设置为屏蔽 SIGUSR1、SIGUSR2;但是当 pselect 是 运行ning 时,它允许 SIGUSR1、SIGUSR2、SIGTERM。 该程序分叉,parent(默认:)处于循环调用 pselect() 中,然后在完成后输出“.”。 child 处于循环状态,将 SIGUSR1、SIGUSR2 传递给 parent,然后休眠一会儿。它在传递信号后输出“^”。 处理程序为 SIGUSR1、SIGUSR2 分别发出前缀“(1”或“(2”;然后休眠一会儿,并输出“)”以指示休眠已完成。

我在 macos(10.12.6,但我怀疑它是否重要)上看到的输出是: ^(2)(1).^(2)(1).^(2)(1).^(2)(1).终止:15 这表明每次调用 pselect() 时,SIGUSR1 和 SIGUSR2 的信号处理程序都是 运行。这是我所期望的;因为它被设计为不允许 window 的不确定性,就像括号 select() 和 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;
}

我继续搜索,没有找到更多信息,所以我只能得出结论,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 ...

它本质上是来自系统调用returns,允许信号处理程序执行,之后将立即恢复原始信号掩码(来自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上,上面的输出始终是:

Handled 1 signals while in pselect.

在 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 的普遍支持。