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

时间:2016-11-14 15:17:30

标签: c signals posix language-lawyer

sigsuspend更改信号掩码,暂停执行调用线程,直到收到一个"信号,其动作是执行信号捕获功能或终止进程",然后(如果进程未终止且信号处理程序返回)将信号掩码恢复到其原始状态。

POSIX.1-2008的链接页面并未说明多个信号是否可以在MaxBy<>的单次调用中传递,也不能是否有关于信号掩模变化的原子性的说法;即在我看来,这是sigsuspend的符合要求的实现,即使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时,只有一次,即使同一系统上的其他进程发送信号也是如此。

所以我的问题是:

  1. 是否有要求(在POSIX或任何其他相关标准中)每次调用sigsuspend时最多发送一个(任何类型的)信号?
  2. 是否有要求(同上)sigsuspend更改信号掩码,暂停执行,并原子恢复信号掩码?也就是说,没有任何风险,信号将被传递到&#34;之间。上面假设的用户空间实现中的三个系统调用?
  3. 由于这是相当抽象的,在我将喜欢的测试程序下总是打印1并成功退出,但我担心在某些情况下它可能会打印2或0,挂起直到报警熄火,或崩溃。 (C11原子用于过度谨慎;从技术上讲,你不能从信号处理程序中读取一个sigsuspend,只有到一个。)默认情况下使用SIGUSR1,如果在命令行上传递volatile sig_atomic_t则使用SIGRTMIN。

    -r

2 个答案:

答案 0 :(得分:5)

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

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

以下程序中的想法是让用户在命令行上指定信号。该过程将分叉子进程。

子进程阻塞该信号,然后安装一个原子(使用GCC内置函数)递增计数器的信号处理程序。然后它将进入一个循环,在那里它自行停止(通过raise(SIGSTOP))。父进程将检测到这一点,然后发送一组信号,然后SIGCONT。当子进程唤醒时,它会调用sigsuspend(),直到没有更多信号待处理。对于每个sigsuspend()呼叫,计算传递的信号数。在没有待处理的信号之后,孩子再次停止。

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

这是一个彻底的黑客攻击,可能根本不遵循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] != '\0')
        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担心的情况的输出。在Linux on x86-64架构(内核4.4.0,GCC 5.4.0)中,它输出类似

的内容
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()被调用,只传递了一个信号。 (但是,只有一个信号实例被传递,即使父母发送了8次:4个有不同的sigqueue()有效载荷,4个有kill()`。)

正如我在评论中提到的,基于Linux内核源代码,我相信如果信号在{{1之前(之后)之前被阻塞,则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. 次呼叫只发送一次。

该程序无法提供任何类型的证据证明系统的工作正如OP所希望的那样(或者Linux的工作原理我认为它的工作方式基于内核源代码)。它只能用于反驳它(在Linux上,我的信念)。如果有系统报告

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

(#0的情况只有在选择的信号是SIGINT,SIGHUP或SIGTERM时才会出现,这些信号也会被捕获,这样用户就可以停止该程序了。)

当然不能保证即使在这样的系统上运行,该程序也会设法偶然发现多个交付案例。

然而,让子进程停止,得到多个(a个副本)信号,然后继续,这将是系统每sigsuspend()调用传递多个(副本)信号的最佳点。如果在这种情况下不这样做,它会是什么情况呢?

答案 1 :(得分:3)

这是我在对原始问题的评论中提到的from turtle import Turtle, Screen from math import pi def polyline(turtle, n, length, angle): for _ in range(n): turtle.fd(length) turtle.lt(angle) def arc(turtle, radius, angle): arc_length = 2 * pi * radius * angle / 360 n = int(arc_length / 3) + 1 step_length = arc_length / n step_angle = float(angle) / n polyline(turtle, n, step_length, step_angle) def petal(turtle, radius, angle): for _ in range(2): arc(turtle, radius, angle) turtle.lt(180 - angle) def flower(turtle, n, radius, angle): for _ in range(n): petal(turtle, radius, angle) turtle.lt(360 / n) screen = Screen() bob = Turtle() flower(bob, 5, 77, 99) screen.exitonclick() / 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()之后的语句之前完成,则应该没有真正的区别。毕竟,sigsuspend()只影响线程信号掩码,因此排序(在工作和从sigsuspend()返回之间)完全是线程内部的。

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

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

(目前,并非所有C编译器都以可移植的方式支持原子操作,但所有POSIXy系统都应支持信号量。我个人会使用原子操作,但包含在辅助函数中,以便在必要时轻松移植。)