sigsuspend 与处理程序执行期间传递的其他信号

sigsuspend vs additional signals delivered during handler execution

sigsuspend 更改信号掩码,暂停调用线程的执行,直到它收到 "signal whose action is either to execute a signal-catching function or to terminate the process",然后(如果进程未终止且信号处理程序 returns)将信号掩码恢复到其原始状态。

POSIX.1-2008 的链接页面没有说明 多个 信号是否有可能在对 [=12= 的一次调用中传递],它也没有说明信号掩码变化的原子性;即在我看来,这是 sigsuspend 的一致实现,尽管 sigsuspend 的全部要点是它没有此代码所具有的竞争条件:

int sigsuspend(const sigset_t *mask)
{
    sigset_t oldmask;
    if (sigprocmask(SIG_SETMASK, mask, &oldmask)) return -1;
    pause();
    if (sigprocmask(SIG_SETMASK, &oldmask, 0)) return -1;
    return -1;
}

我真正担心的场景是一个使用 SIGUSR1 与自身通信的程序(这是一个很长的故事),我需要一种方法来确保信号处理程序执行 每次对 sigsuspend 的内部调用仅一次,即使同一系统上的其他进程向它发送信号也是如此。

所以我的问题是:

  1. 是否有要求(在 POSIX 或任何其他相关标准中)每次调用 sigsuspend 最多传递一个信号(任何类型)?
  2. 是否要求(同上)sigsuspend 自动更改信号掩码、暂停执行和恢复信号掩码?也就是说,没有任何信号将传递的风险 "in between" 上面假设的用户-space 实现中的三个系统调用?

由于这是相当抽象的,下面是一个测试程序,我会喜欢总是打印 1 并成功退出,但我担心在某些情况下它可能会打印 2 或 0 ,挂起直到警报响起,或崩溃。 (出于过度谨慎使用 C11 原子;从技术上讲,您不允许 read 来自信号处理程序的 volatile sig_atomic_t,只能 write 到一个。)它默认使用 SIGUSR1,如果您在命令行上传递 -r,则使用 SIGRTMIN。

#define _XOPEN_SOURCE 700
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

#ifndef ATOMIC_INT_LOCK_FREE
#error "This program uses atomic_uint from a signal handler."
#endif

static atomic_uint handler_call_count;
static pid_t self_pid;

static void perror_exit(const char *msg)
{
  perror(msg);
  exit(1);
}

static void handler(int signo)
{
  union sigval dummy;
  dummy.sival_int = 0;

  if (handler_call_count++ == 0)
    if (sigqueue(self_pid, signo, dummy))
      perror_exit("sigqueue");
}

int main(int argc, char **argv)
{
  sigset_t mask1, mask2;
  struct sigaction sa;
  int signo;
  union sigval dummy;

  if (argc > 1 && !strcmp(argv[1], "-r"))
    signo = SIGRTMIN;
  else
    signo = SIGUSR1;

  sigemptyset(&mask1);
  sigemptyset(&sa.sa_mask);

  sigaddset(&mask1, signo);
  sigaddset(&sa.sa_mask, signo);

  if (sigprocmask(SIG_BLOCK, &mask1, &mask2))
    perror_exit("sigprocmask");

  sigdelset(&mask2, SIGALRM);
  sigdelset(&mask2, signo);

  sa.sa_handler = handler;
  sa.sa_flags = SA_RESTART;
  if (sigaction(signo, &sa, 0))
    perror_exit("sigaction");

  self_pid = getpid();
  dummy.sival_int = 0;
  if (sigqueue(self_pid, signo, dummy))
    perror_exit("sigqueue");

  alarm(5);
  sigsuspend(&mask2);
  alarm(0);

  printf("%u\n", atomic_load(&handler_call_count));
  return 0;
}

1) 是否有最多处理一个信号的要求?

不行其实相反的是推荐。 POSIX 2008 年的 第 4 卷:基本原理,§B.2.4.1 信号生成和传递 指出:

When there are multiple pending signals that are not blocked, implementations should arrange for the delivery of all signals at once, if possible. Some implementations stack calls to all pending signal-catching routines, making it appear that each signal-catcher was interrupted by the next signal. In this case, the implementation should ensure that this stacking of signals does not violate the semantics of the signal masks established by sigaction(). Other implementations process at most one signal when the operating system is entered, with remaining signals saved for later delivery. Although this practice is widespread, this behavior is neither standardized nor endorsed. In either case, implementations should attempt to deliver signals associated with the current state of the process (for example, SIGFPE) before other signals, if possible.

另外,可以同时排队多个相同信号号的信号。 POSIX 2008 第 2 卷:系统接口,§2.4.1 信号生成和传递 状态

If a subsequent occurrence of a pending signal is generated, it is implementation-defined as to whether the signal is delivered or accepted more than once in circumstances other than those in which queuing is required. The order in which multiple, simultaneously pending signals outside the range SIGRTMIN to SIGRTMAX are delivered to or accepted by a process is unspecified.

这也适用于 real-time 信号。 POSIX 2008 第 2 卷:系统接口,§2.4.2 实时信号生成和传递 状态

[...] Multiple occurrences of signals so generated are queued in FIFO order. [...]

If, when a pending signal is delivered, there are additional signals queued to that signal number, the signal shall remain pending. Otherwise, the pending indication shall be reset.

2)对原子性有要求吗?

合法性值得商榷。这可能是有意为之,但实际上并没有规范地阐明。赞成和反对都有很好的论据:

案例原子性:

POSIX 2008 确实在 第 2 卷:系统接口,§3 pause():

中强烈暗示了原子性

APPLICATION USAGE

Many common uses of pause() have timing windows. The scenario involves checking a condition related to a signal and, if the signal has not occurred, calling pause(). When the signal occurs between the check and the call to pause(), the process often blocks indefinitely. The sigprocmask() and sigsuspend() functions can be used to avoid this type of problem.

类似的强烈暗示出现在 RATIONALE 标题下 sleep()。此外,单词 can 在 POSIX 2008 Volume 1: Base Definitions, §1.5 Terminology` 中定义为:

For the purposes of POSIX.1-2008, the following terminology definitions apply:

can

Describes a permissible optional feature or behavior available to the user or application. The feature or behavior is mandatory for an implementation that conforms to POSIX.1-2008. An application can rely on the existence of the feature or behavior.

和 POSIX 2008 年的 第 4 卷:基本原理,§A.1.5 术语 指出:

may

The use of may has been limited as much as possible, due both to confusion stemming from its ordinary English meaning and to objections regarding the desirability of having as few options as possible and those as clearly specified as possible.

The usage of can and may were selected to contrast optional application behavior (can) against optional implementation behavior (may).

也就是说,pause()APPLICATION USAGE标题指出应用程序没有义务调用sigsuspend()来避免计时问题,但是如果它选择,那么需要 POSIX.1-2008-conformant 实现 sigsuspend() 来避免上述 timing-window 问题(例如,在内核的帮助下成为原子)。

如果 sigsuspend() 不是原子的,那么

  • 关于应用程序可以通过使用 sigsuspend() 来避免时序 window 问题的说法是错误的,因此自 1988 年第一个标准以来的所有版本的 POSIX.1 标准都是内部不一致。
  • sigsuspend() 将毫无用处,因为它在 pause() 之外没有任何用处。然后人们不得不问,为什么 POSIX.1 从第一个版本开始就包括两者,以及错误的建议使用 sigsuspend() 而不是 pause().

案例反对原子性:

POSIX 2008 第 2 卷:系统接口,§1.2 条目格式 状态:

[...]

APPLICATION USAGE

This section is informative.

This section gives warnings and advice to application developers about the entry. In the event of conflict between warnings and advice and a normative part of this volume of POSIX.1-2008, the normative material is to be taken as correct.

RATIONALE

This section is informative.

This section contains historical information concerning the contents of this volume of POSIX.1-2008 and why features were included or discarded by the standard developers.

[...]

这意味着这些部分是信息性,而不是规范性,并且只是规范性部分可以直接对实现者提出要求。然而,他们可以帮助解释标准,并且声明“sigprocmask()sigsuspend()函数可以用来避免这种类型问题”不与标准的任何规范部分冲突。

中性案例

POSIX 2008 年的 第 4 卷:基本原理,§A.1.5 术语 指出:

implementation-defined

This definition is analogous to that of the ISO C standard and, together with ‘‘undefined’’ and ‘‘unspecified’’, provides a range of specification of freedom allowed to the interface implementor.

[...]

unspecified

See implementation-defined.

[...]

In many places POSIX.1-2008 is silent about the behavior of some possible construct. For example, a variable may be defined for a specified range of values and behaviors are described for those values; nothing is said about what happens if the variable has any other value. That kind of silence can imply an error in the standard, but it may also imply that the standard was intentionally silent and that any behavior is permitted. There is a natural tendency to infer that if the standard is silent, a behavior is prohibited. That is not the intent. Silence is intended to be equivalent to the term ‘‘unspecified’’.

ISO C 标准(目前为 C11)将 "implementation-defined behaviour" 定义为实现可以选择的行为,但必须记录其选择。

缺少关于 sigsuspend() 原子性的任何规范性声明可能是一个错误或等同于明确声明它是 implementation-defined 行为。

  • pause()sleep() 中的应用说明和基本原理表明这是标准中的一个错误。
  • 但相反,sigsuspend() 的原子性是 implementation-defined 的隐式规范性陈述优先于相反的信息性陈述。

实施

那么我们能否从 in-the-wild 实施中获得启发?也许。我知道没有不以原子方式实现它的实现:

  • Linux 内核以原子方式实现 sigsuspend()
  • BSD 内核以原子方式实现 sigsuspend()

这不是一个答案,而是一个探索性的程序。我希望好心的 macOS and/or *BSD 用户可以在他们的机器上测试它,并报告他们的结果。


出于兴趣,我编写了一个粗略的程序来评估每次调用 sigsuspend() 传递多个信号的频率。

下面程序的思路是让用户在命令行指定信号。该进程将分叉一个 child 进程。

child 进程阻塞该信号,然后安装一个信号处理程序,该处理程序以原子方式(使用 GCC built-ins)递增一个计数器。然后它将进入一个循环,并在其中自行停止(通过 raise(SIGSTOP))。 parent 进程将检测到这一点,并发送一组信号,然后 SIGCONT。当 child 进程醒来时,它调用 sigsuspend() 直到没有更多的信号挂起。对于每个 sigsuspend() 调用,将计算传递的信号数。在没有更多信号挂起后,child 再次自行停止。

parent 使用 kill() 发送一半(信号的副本),其余使用 sigqueue() 和不同的有效负载。

这是一个彻头彻尾的 hack,可能根本不遵循 POSIX.1 标准,并且可能包含很多错误。 (如果您发现任何问题,请在评论中告诉我,以便我进行修复。)

#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#ifndef   CLUSTER
#define   CLUSTER 8
#endif

#ifndef   INTERVAL
#define   INTERVAL 100000
#endif

static const struct {
    const int  number;
    const char name[8];
} signal_list[] = {
    { SIGHUP,     "HUP"     },
    { SIGINT,     "INT"     },
    { SIGQUIT,    "QUIT"    },
    { SIGILL,     "ILL"     },
    { SIGABRT,    "ABRT"    },
    { SIGFPE,     "FPE"     },
    { SIGSEGV,    "SEGV"    },
    { SIGPIPE,    "PIPE"    },
    { SIGALRM,    "ALRM"    },
    { SIGTERM,    "TERM"    },
    { SIGUSR1,    "USR1"    },
    { SIGUSR2,    "USR2"    },
    { SIGTSTP,    "TSTP"    },
    { SIGTTIN,    "TTIN"    },
    { SIGTTOU,    "TTOU"    },
    { SIGBUS,     "BUS"     },
    { SIGPOLL,    "POLL"    },
    { SIGPROF,    "PROF"    },
    { SIGSYS,     "SYS"     },
    { SIGTRAP,    "TRAP"    },
    { SIGURG,     "URG"     },
    { SIGVTALRM,  "VTALRM"  },
    { SIGXCPU,    "XCPU"    },
    { SIGXFSZ,    "XFSZ"    },
    { SIGIO,      "IO"      },
    { SIGPWR,     "PWR"     },
    { SIGWINCH,   "WINCH"   },
    { -1,         ""        }
};


static volatile sig_atomic_t done = 0;

static void handle_done(int signum)
{
    done = 1;
}

static int install_done(int signum)
{
    struct sigaction act;

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);
    act.sa_handler = handle_done;
    act.sa_flags = 0;
    if (sigaction(signum, &act, NULL) == -1)
        return errno;

    return 0;
}

static unsigned long counter = 0UL;
static void          increment_counter(void)     { __atomic_add_fetch(&counter, 1UL, __ATOMIC_SEQ_CST); }
static unsigned long get_and_clear_counter(void) { return __atomic_fetch_and(&counter, 0UL, __ATOMIC_SEQ_CST); }

static void handle_counter(int signum)
{
    increment_counter();
}

static int install_counter(int signum)
{
    struct sigaction  act;

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, signum);
    act.sa_handler = handle_counter;
    act.sa_flags = 0;
    if (sigaction(signum, &act, NULL) == -1)
        return errno;

    return 0;
}

int child_process(int signum)
{
    sigset_t      signals, no_signals, pending;
    unsigned long count, corrects, incorrects, cluster, noncluster, clustercount, nonclustercount;
    int           result, exitcode;

    sigemptyset(&no_signals);
    sigemptyset(&signals);
    sigaddset(&signals, signum);

    if (sigprocmask(SIG_BLOCK, &signals, NULL) == -1) {
        fprintf(stderr, "Child: Cannot block the signal: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    if (install_done(SIGINT) ||
        install_done(SIGHUP) ||
        install_done(SIGTERM) ||
        install_counter(signum)) {
        fprintf(stderr, "Child: Cannot install signal handlers: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    /* Ready to wait for signals to become pending. */
    exitcode = EXIT_SUCCESS;
    corrects = 0UL;         incorrects = 0UL;
    cluster = 0UL;          clustercount = 0UL;
    noncluster = CLUSTER;   nonclustercount = 0UL;

    raise(SIGSTOP);

    while (1) {

        if (done)
            return exitcode;

        sigemptyset(&pending);
        if (sigpending(&pending) == -1) {
            fprintf(stderr, "Child: Sigpending failed: %s.\n", strerror(errno));
            return EXIT_FAILURE;
        }

        if (!sigismember(&pending, signum)) {
            if (cluster != CLUSTER) {
                if (cluster != noncluster) {
                    fprintf(stderr, "Child: Signals are delivered in clusters of %lu signals; expected %d.\n", cluster, CLUSTER);
                    noncluster = cluster;
                }
                nonclustercount++;
            } else
                clustercount++;
            if ((clustercount + nonclustercount) % INTERVAL == 0UL) {

                if (incorrects > 0UL)
                    printf("Child: %lu times out of %lu times only one signal is delivered per sigsuspend() call.\n", corrects, corrects + incorrects);
                else
                    printf("Child: All %lu times sigsuspend() was called, only one signal was delivered.\n", corrects);

                if (clustercount > 0UL && nonclustercount > 0UL)
                    printf("Child: In %lu of %lu sets of signals, all %d copies of the signal were delivered.\n", clustercount, clustercount + nonclustercount, CLUSTER);
                else
                if (clustercount > 0UL)
                    printf("Child: In all %lu sets of signals, all %d copies of the signal were delivered.\n", clustercount, CLUSTER);

                fflush(stdout);
            }
            cluster = 0UL;
            raise(SIGSTOP);
        }

        if (done)
            return exitcode;

        result = sigsuspend(&no_signals);
        if (result != -1 || errno != EINTR) {
            printf("Child: sigsuspend() returned %d, expected -1 with errno == EINTR!\n", result);
            return EXIT_FAILURE;
        }
        if (done)
            return exitcode;

        count = get_and_clear_counter();
        cluster += count;
        if (count != 1UL) {
            printf("Child: Received %lu signals on one sigsuspend() call!\n", count);
            fflush(stdout);
            exitcode = EXIT_FAILURE;
            ++incorrects;
        } else
            ++corrects;
    }
}

int parse_signum(const char *name)
{
    unsigned int u;
    int          i;
    char         c;

    if (!name || !*name) {
        errno = EINVAL;
        return -1;
    }

    if (name[0] == 'S' &&
        name[1] == 'I' &&
        name[2] == 'G' &&
        name[3] != '[=10=]')
        for (i = 0; signal_list[i].number >= 0; i++)
            if (!strcmp(name + 3, signal_list[i].name))        
                return signal_list[i].number;

    for (i = 0; signal_list[i].number >= 0; i++)
        if (!strcmp(name, signal_list[i].name))        
            return signal_list[i].number;

    if ((sscanf(name, " SIGRT%u %c", &u, &c) == 1 ||
         sscanf(name, " RT%u %c", &u, &c) == 1 ||
         sscanf(name, " SIGRTMIN+%u %c", &u, &c) == 1 ||
         sscanf(name, " RTMIN+%u %c", &u, &c) == 1) &&
         u <= (unsigned int)(SIGRTMAX - SIGRTMIN))
        return SIGRTMIN + u;

    if ((sscanf(name, " SIGRTMAX-%u %c", &u, &c) == 1 ||
         sscanf(name, " RTMAX-%u %c", &u, &c) == 1) &&
         u <= (unsigned int)(SIGRTMAX - SIGRTMIN))
        return SIGRTMAX - u;

    errno = EINVAL;
    return -1;
}

int main(int argc, char *argv[])
{
    pid_t child, p;
    int   signum, i, status;

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s [ -l | --list ]\n", argv[0]);
        fprintf(stderr, "       %s SIGNAL\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program uses a stopped child process to see if\n");
        fprintf(stderr, "a single call to sigsuspend() can cause more than\n");
        fprintf(stderr, "one SIGNAL to be delivered.\n");
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }
    if (!strcmp(argv[1], "-l") || !strcmp(argv[1], "--list")) {
        fprintf(stderr, "List of known standard POSIX signals:\n");
        for (i = 0; signal_list[i].number >= 0; i++)
            fprintf(stderr, "\tSIG%-7s (%d)\n", signal_list[i].name, signal_list[i].number);
        fprintf(stderr, "POSIX realtime signals can be referred to as\n");
        fprintf(stderr, "\tSIGRTMIN+0      or SIGRTMAX-%d\n", SIGRTMAX-SIGRTMIN);
        fprintf(stderr, "\t                to\n");
        fprintf(stderr, "\tSIGRTMIN+%d     or SIGRTMAX-0\n", SIGRTMAX-SIGRTMIN);
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    if (install_done(SIGINT) ||
        install_done(SIGHUP) ||
        install_done(SIGTERM)) {
        fprintf(stderr, "Parent: Cannot install signal handlers: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    signum = parse_signum(argv[1]);
    if (signum < 0) {
        fprintf(stderr, "%s: Unknown signal.\n", argv[1]);
        return EXIT_FAILURE;
    }

    if (signum >= SIGRTMIN && signum <= SIGRTMAX)
        fprintf(stderr, "Using POSIX realtime signal number %d (SIGRTMIN%+d)\n", signum, signum-SIGRTMIN);
    else
        fprintf(stderr, "Using standard POSIX signal number %d.\n", signum);

    child = fork();
    if (child == (pid_t)-1) {
        fprintf(stderr, "Cannot fork a child process: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    } else
    if (!child)
        return child_process(signum);

    /* Parent process. */
    while (!done) {

        /* Wait for child to become stopped or continued. */
        while (!done) {

            do {
                p = waitpid(child, &status, WUNTRACED | WCONTINUED);
            } while (!done && p == (pid_t)-1 && errno == EINTR);
            if (done)
                break;

            if (p == (pid_t)-1) {
                if (errno == EINTR)
                    continue;
                fprintf(stderr, "Parent: Child process vanished: %s.\n", strerror(errno));
                return EXIT_FAILURE;
            } else
            if (p != child)
                continue;

            if (WIFSTOPPED(status) || WIFCONTINUED(status))
                break;

            if (WIFEXITED(status)) {
                if (WEXITSTATUS(status))
                    fprintf(stderr, "Parent: Child exited with exit status %d.\n", WEXITSTATUS(status));
                else
                    fprintf(stderr, "Parent: Child exited successfully.\n");
            } else
            if (WIFSIGNALED(status))
                fprintf(stderr, "Parent: Child died from signal %d.\n", WTERMSIG(status));
            else
                fprintf(stderr, "Parent: Lost child process.\n");
            return EXIT_FAILURE;
        }

        if (done)
            break;

        if (WIFSTOPPED(status)) {

            /* Send child a total of CLUSTER signals.
               Half of them using sigqueue(), half via kill().
            */
            i = 0;
            while (i < CLUSTER) {
                union sigval sv;
                sv.sival_int = ++i;
                sigqueue(child, signum, sv);
                if (i++ < CLUSTER)
                    kill(child, signum);
            }

            /* Wake child up. */
            kill(child, SIGCONT);
        }

    }

    /* Tell the child process to terminate. */
    kill(child, SIGCONT);
    kill(child, signum);
    kill(child, SIGTERM);

    while (1) {
        p = waitpid(child, &status, 0);
        if (p == (pid_t)-1) {
            if (errno == EINTR)
                continue;
            return EXIT_FAILURE;
        }

        if (p == child)
            return status; /* HACK to return the child process status as-is */
    }
}

将上面的内容保存为例如hack.c,并使用

编译和 运行 宁它
gcc -Wall -O2 hack.c -o hack
./hack SIGUSR1

提供 OP 担心的情况的输出。在 x86-64 架构(内核 4.4.0,GCC 5.4.0)上的 Linux 中,它输出类似

的内容
Using standard POSIX signal number 10.
Child: Signals are delivered in clusters of 1 signals; expected 8.
Child: All 100000 times sigsuspend() was called, only one signal was delivered.
Child: All 200000 times sigsuspend() was called, only one signal was delivered.
Child: All 300000 times sigsuspend() was called, only one signal was delivered.
Child: All 400000 times sigsuspend() was called, only one signal was delivered.

以上输出显示,所有400,000次sigsuspend()被调用,只传递了一个信号。 (但是,即使 parent 发送了 8 次信号,也只传递了一个信号实例:四次使用不同的 sigqueue() 有效负载,四次使用 kill()`。)

正如我在评论中提到的,基于 Linux 内核源代码,我相信 Linux 内核在每次 sigsuspend() 调用时仅传递一个信号,如果信号被阻塞的话在 sigsuspend() 调用之前(以及之后)。上面的输出支持这种信念。

运行 与实时信号相同,比如 ./hack SIGRTMIN+0,输出类似于

Using POSIX realtime signal number 34 (SIGRTMIN+0)
Child: All 800000 times sigsuspend() was called, only one signal was delivered.
Child: In all 100000 sets of signals, all 8 copies of the signal were delivered.
Child: All 1600000 times sigsuspend() was called, only one signal was delivered.
Child: In all 200000 sets of signals, all 8 copies of the signal were delivered.
Child: All 2400000 times sigsuspend() was called, only one signal was delivered.
Child: In all 300000 sets of signals, all 8 copies of the signal were delivered.
Child: All 3200000 times sigsuspend() was called, only one signal was delivered.
Child: In all 400000 sets of signals, all 8 copies of the signal were delivered.

这表明在此 运行 上,信号的每个实例都已交付,每个 sigsuspend() 调用恰好一次。

该程序无法提供任何形式的证据证明系统如 OP 希望的那样工作(或者 Linux 以我认为基于内核源代码的方式工作)。它只能用来反驳它(并且 Linux,我相信)。如果有系统报告

Child: Received # signals on one sigsuspend() call!

如果 # 为 2 或更大,那么我们知道在该系统上,每个 sigsuspend() 调用会传递不止一个(信号的副本)。

(# 为 0 的情况只有在选择的信号是 SIGINT、SIGHUP 或 SIGTERM 时才会发生,这些信号也会被捕获,以便用户可以停止程序。)

当然不能保证即使 运行 在这样的系统上,这个程序也会设法偶然发现 multiple-delivery 的情况。

但是,让 child 进程停止,获取多个(一个)信号,然后继续,这将是系统每次传送多个(一个)信号(副本)的最佳点sigsuspend() 调用。如果在这种情况下它不这样做,那么它会是什么情况?

这是我在对原始问题的评论中提到的 sem_post()/sem_wait() 解决方法的概要。

这假设 SIGUSR1 通常被阻止;仅在 sigsuspend() 通话期间未被阻止。

首先,信号处理程序被替换为一个简单的信号处理程序,它只发布一个专用信号量:

sem_t  sigusr1_semaphore;

void sigusr1_handler(int signum)
{
    sem_post(&sigusr1_semaphore);
}

void sigusr1_work(void)
{
    /* Whatever the original handler did */
}

int install_sigusr1_handler(void)
{
    struct sigaction  act;

    sem_init(&sigusr1_semaphore, 0, 0);

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, SIGUSR1);
    act.sa_handler = sigusr1_handler;
    act.sa_flags = 0;

    if (sigaction(SIGUSR1, &act, NULL) == -1)
        return errno; /* Failed */   

    return 0;
}

每次调用 sigsuspend() 解除对上述信号处理程序的阻塞都会在其后立即添加以下代码:

    if (!sem_trywait(&sigusr1_semaphore)) {
        sigusr1_work();
        while (!sem_trywait(&sigusr1_semaphore));
    }

从技术上讲,实际工作完成前 sigsuspend() return 秒。然而,由于工作是在同一个线程中完成的,如果工作是在 sigsuspend() 之后的语句之前完成的,应该没有真正的区别。毕竟,sigsuspend() 只影响线程信号掩码,因此排序(在工作和 sigsuspend() 的 return 之间)纯粹是线程内部的。

(编辑注意到 OP,zwol,指出在某些情况下它确实很重要。在 OP 的情况下,信号处理程序使用备用堆栈,这意味着工作不能真正转移到正常程序流。)

您显然可以使用每线程原子变量实现相同的目的。

(此时,并不是所有的 C 编译器都以可移植的方式支持原子操作,但是所有的 POSIXy 系统都应该支持信号量。我个人会使用原子操作,但包裹在辅助函数中,以便于移植如有必要。)