sigsuspend против дополнительных сигналов, поступающих во время выполнения обработчика

sigsuspend изменения сигнала маски, приостанавливает выполнение вызывающего потока до получения "сигнала чьи действия либо выполнять сигнал ловила функции или завершить процесс", а затем (если процесс не прекращается и обработчик сигнала возвращает) восстанавливает маску сигналов в исходное состояние.

связанная страница POSIX.1-2008 не сказать ли несколько сигналы должны быть доставлены в один вызов 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 для связи с самим собой (это длинная история), и мне нужен способ убедиться, что обработчик сигнала выполняется только один раз на внутренний вызов sigsuspend, даже если другие процессы в той же системе отправить его сигналы.

Итак, мои вопросы:

  1. есть ли требование (в POSIX или любой другой соответствующий стандарт) для доставки не более одного сигнала (любого вида) за вызов sigsuspend?
  2. есть ли требование (то же самое) для sigsuspend изменить маску сигнала, приостановить выполнение и восстановить маску сигнала атомарно? То есть, без какого-либо риска того, что сигнал будет доставлен "между" тремя системными вызовами в гипотетической реализации пользовательского пространства выше?

так как это довольно абстрактно, ниже тестовой программы, которую я бы как всегда печатать 1 и выйти успешно, но я беспокоюсь, что при некоторых обстоятельствах он может напечатать 2 или 0, зависнуть, пока не сработает сигнал тревоги или сбой. (С11 Атомикс использовать излишняя осторожность; технически нельзя читать a volatile sig_atomic_t от обработчика сигнала, только написать в один.) Он использует SIGUSR1 по умолчанию и SIGRTMIN, если вы передаете -r в командной строке.

#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("%un", atomic_load(&handler_call_count));
  return 0;
}

3 ответов


1) Есть ли требование для обработки не более одного сигнала?

нет. на самом деле, наоборот рекомендовано. POSIX 2008's Том 4: Обоснование, §B. 2.4.1 генерация и доставка сигнала гласит:

когда есть несколько ожидающих сигналов, которые не заблокированы, реализации должны организовать доставку всех сигналов сразу, если это возможно. Некоторые реализации стек вызовы всех ожидающих процедуры улавливания сигналов, делая вид, что каждый ловец сигналов был прерван следующим сигналом. В этом случае реализация должна гарантировать, что эта укладка сигналов не нарушает семантику масок сигналов, установленных sigaction(). Другие реализации обрабатывают не более одного сигнала при входе в операционную систему, а остальные сигналы сохраняются для последующей доставки. Хотя эта практика широко распространена, такое поведение не стандартизировано и не одобрено. В любом случае, реализации должны пытаться доставлять сигналы, связанные с текущим состоянием процесса (например,SIGFPE) перед другими сигналами, если это возможно.

кроме того, is возможность одновременной очереди нескольких сигналов с одинаковым номером сигнала. В POSIX 2008 Том 2: Системные интерфейсы, §2.4.1 генерация и доставка сигналов государства

если последующее возникновение отложенного сигнала генерируемый, он определяется реализацией относительно того, доставляется ли сигнал или принимается более одного раза в обстоятельствах, отличных от тех, в которых требуется очередь. Порядок, в котором несколько одновременно ожидающих сигналов за пределами диапазона SIGRTMIN-SIGRTMAX доставляются или принимаются процессом, не указан.

это также относится к сигналам в реальном времени. В POSIX 2008 Том 2: Системные интерфейсы, §2.4.2 генерация сигналов в реальном времени и Доставка государства

[...] Несколько вхождений генерируемых таким образом сигналов выстраиваются в очередь в порядке FIFO. [...]

если при подаче ожидающего сигнала на этот номер сигнала ставятся в очередь дополнительные сигналы, то сигнал должен оставаться ожидающим. В противном случае ожидающая индикация должна быть сброшена.

2) существует ли требование атомарности?

Законно Спорно. это, наверное, было намеренно, но на самом деле это не прописано в норме. Есть веские аргументы в пользу и против:

по делу на атомарность:

POSIX 2008 сильно подразумевает атомарность в Том 2: Системные Интерфейсы, §3 pause():

ИСПОЛЬЗОВАНИЕ ПРИЛОЖЕНИЯ

много общих польз pause() есть синхронизации Windows. Сценарий включает проверку условия, связанного с сигналом и, если сигнал имеет не произошло, звоню pause(). Когда сигнал возникает между проверкой и вызовом pause() процесс часто блокирует на неопределенный срок. The sigprocmask() и sigsuspend() функции можно использовать для избежания этого типа проблемы.

подобный сильный подтекст появляется под обоснование заголовок sleep(). Кроме того, слово можете определяется в POSIX 2008 Том 1: Базовые Определения, § 1.5 Терминология' as:

для целей POSIX.1-2008, применяются следующие терминологические определения:

можете

описывает допустимую дополнительную функцию или поведение, доступное пользователю или приложению. Функция или поведение являются обязательными для реализации, соответствующей POSIX.1-2008. Приложение может полагаться на существование функции или поведения.

и POSIX 2008's объем 4: Обоснование, §A. 1.5 терминология гласит:

мая

использование мая был ограничен, насколько это возможно, как из-за путаницы, вытекающей из его обычного английского значения, так и из возражений относительно желательности иметь как можно меньше вариантов и как можно более четко определенных.

использование can и мая были выбраны для контраста необязательное поведение приложения (can) против необязательного поведения реализации (may).

то есть, pause() ' s ИСПОЛЬЗОВАНИЕ ПРИЛОЖЕНИЯ заголовок гласит, что приложение не обязано звонить sigsuspend() чтобы избежать проблем с синхронизацией, но если он решит, то POSIX.1-2008-совместимых реализаций sigsuspend() необходимы, чтобы избежать указанных проблем с временным окном (например, быть атомарным, с помощью ядра).

если sigsuspend() не были атомными, тогда

  • заявление о том, что приложение может избежать проблем с окном синхронизации с помощью sigsuspend() является false, и, следовательно, все версии POSIX.1 стандарт, начиная с первого в 1988 году, внутренне противоречив.
  • sigsuspend() было бы бесполезно, так как это не служило бы никакой цели за пределами pause(). Тогда нужно было бы спросить, почему POSIX.1 Включите оба с самой первой версии, а также ложную рекомендацию использовать sigsuspend() в предпочтение pause().

по делу против атомарность:

POSIX 2008 Том 2: Системные интерфейсы, §1.2 формат записей гласит:

[...]

ИСПОЛЬЗОВАНИЕ ПРИЛОЖЕНИЯ

этот раздел информативен.

этот раздел дает предупреждения и советы разработчикам приложений о записи. В случае конфликта между предупреждениями и консультация и нормативная часть этого тома POSIX.1-2008, нормативный материал должен быть принят как правильный.

обоснование

этот раздел информативен.

этот раздел содержит историческую информацию, касающуюся содержания данного тома POSIX.1-2008 и почему были или отброшены по стандартам компании.

[...]

это означает, что эти разделы are познавательно, а не нормативно -, и только нормативно - разделы могут напрямую предъявлять требования к исполнителю. Тем не менее, они могут помочь в перевод стандарт и утверждение, что"на sigprocmask() и sigsuspend() функции могут быть использованы, чтобы избежать такого рода проблем " не противоречит ни одной нормативной части стандарта.

нейтральный случай

в POSIX 2008 года Том 4: Обоснование, §A. 1.5 терминология гласит:

реализация-определено

это определение аналогично определению стандарта ISO C и вместе с ‘undefined’ и ‘unspecified’ предоставляет диапазон спецификации свободы, разрешенной для реализации интерфейса.

[...]

нет данных

посмотреть реализация-определено.

[...]

во многих местах POSIX.1-2008 молчит о поведении какой-то возможной конструкции. Например, переменная может быть определена для заданного диапазона значений, а поведение для этих значений описано; ничего не говорится о том, что происходит, если переменная имеет какое-либо другое значение. Такого рода молчание может означать ошибку в стандарте, но оно также может означать, что стандарт намеренно молчал и что любое поведение разрешенный. Существует естественная тенденция делать вывод, что если стандарт молчит, поведение запрещено. Это не намерение. Предполагается, что молчание эквивалентно термину ‘неопределенное’.

стандарт ISO C (в настоящее время, C11) определяет "поведение, определяемое реализацией" как поведение, в котором реализация может выбирать, но должна документировать свой выбор.

отсутствие какого-либо нормативного утверждения об атомарности sigsuspend() is возможно, либо ошибка, либо эквивалент явного утверждения о том, что это поведение, определяемое реализацией.

  • примечания к приложению и обоснование в pause() и sleep() предположить, что это ошибка в стандарте.
  • но, наоборот, неявное нормативное утверждение, что определяется реализацией, имеет приоритет над информативным утверждением об обратном.

реализации

так можем ли мы вдохновить себя от реализации в дикой природе? Возможно. Я не знаю никаких реализаций, которые не реализуют его атомарно:

  • Linux ядра реализации sigsuspend() атомарно.
  • BSD ядра реализации sigsuspend() атомарно.

это не ответ, А исследовательская программа. Я надеюсь, что добрый пользователь 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] != '')
        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

обеспечивает выход для случая операция обеспокоена. В Linux на архитектуре 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 раз!--5--> был вызван, был доставлен только один сигнал. (Однако был доставлен только один экземпляр сигнала, даже если родитель отправил его 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, эти также будучи пойманным, так, что потребитель сможет остановить программа.)

конечно, не гарантируется, что даже при запуске в такой системе эта программа сможет наткнуться на случай множественной доставки.

однако, имея остановку дочернего процесса, получить несколько (копий) сигнала, а затем продолжить, было бы оптимальной точкой для системы для доставки более одного (копии) сигнала на вызов 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() возвращает до фактической работы. Однако, поскольку работа выполняется в том же потоке, не должно быть никакой реальной разницы, если работа выполняется до оператора, следующего за sigsuspend() вместо. Ведь sigsuspend() влияет только на маску сигнала потоков, поэтому порядок (между работой и возвращением из sigsuspend()) является чисто внутренним для потока.

(отредактировано, чтобы отметить, что OP, zwol, указал, что есть случаи где это имеет значение. В случае OP обработчик сигналов использует альтернативный стек, что означает, что работа не может быть перемещена в обычный поток программы.)

вы, очевидно, можете достичь того же, используя атомарную переменную для каждого потока.

(на данный момент не все компиляторы C поддерживают атомарные операции переносными способами, но все системы POSIXy должны поддерживать семафоры. Я бы лично использовал atomic ops, но завернутый в вспомогательные функции, чтобы сделать их легкими для порт, если необходимо.)