Почему sigprocmask используется для блокировки доставки SIGCHLD в следующем коде

На основе http://man7.org/tlpi/code/online/book/procexec/multi_SIGCHLD.c.html

int
main(int argc, char *argv[])
{
    int j, sigCnt;
    sigset_t blockMask, emptyMask;
    struct sigaction sa;

    if (argc < 2 || strcmp(argv[1], "--help") == 0)
        usageErr("%s child-sleep-time...\n", argv[0]);

    setbuf(stdout, NULL);       /* Disable buffering of stdout */

    sigCnt = 0;
    numLiveChildren = argc - 1;

    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sa.sa_handler = sigchldHandler;
    if (sigaction(SIGCHLD, &sa, NULL) == -1)
        errExit("sigaction");

    /* Block SIGCHLD to prevent its delivery if a child terminates
       before the parent commences the sigsuspend() loop below */

    sigemptyset(&blockMask);
    sigaddset(&blockMask, SIGCHLD);
    if (sigprocmask(SIG_SETMASK, &blockMask, NULL) == -1)
        errExit("sigprocmask");

    for (j = 1; j < argc; j++) {
        switch (fork()) {
        case -1:
            errExit("fork");

        case 0:         /* Child - sleeps and then exits */
            sleep(getInt(argv[j], GN_NONNEG, "child-sleep-time"));
            printf("%s Child %d (PID=%ld) exiting\n", currTime("%T"),
                    j, (long) getpid());
            _exit(EXIT_SUCCESS);

        default:        /* Parent - loops to create next child */
            break;
        }
    }

    /* Parent comes here: wait for SIGCHLD until all children are dead */

    sigemptyset(&emptyMask);
    while (numLiveChildren > 0) {
        if (sigsuspend(&emptyMask) == -1 && errno != EINTR)
            errExit("sigsuspend");
        sigCnt++;
    }

    printf("%s All %d children have terminated; SIGCHLD was caught "
            "%d times\n", currTime("%T"), argc - 1, sigCnt);

    exit(EXIT_SUCCESS);
}

Вот мое понимание:

sigprocmask(SIG_SETMASK, &blockMask, NULL)

Результирующий набор сигналов вызывающего процесса должен быть набором, на который указывает blockMask.

Вопрос

Почему мы говорим следующее утверждение?

Заблокируйте SIGCHLD, чтобы предотвратить его доставку, если дочерний процесс завершится до того, как родитель запустит цикл sigsuspend() ниже.

Другими словами, я не понимаю, почему sigprocmask используется для блокировки SIGCHLD на основе данного описания оператора sigprocmask.


person q0987    schedule 20.07.2011    source источник
comment
Переменная sigCnt не означает того, что утверждает этот код...   -  person Ben Voigt    schedule 21.07.2011


Ответы (1)


Ну, думаю, комментарий понятен...

Всякий раз, когда вы вызываете fork() и хотите каким-либо образом взаимодействовать с дочерним элементом, необходимо учитывать условия гонки. Что делать, если ребенок бежит на некоторое время раньше, чем родитель? Или наоборот?

В этом случае невозможно узнать, сколько времени потребуется родителю после вызова fork, чтобы достичь вызова sigsuspend. Так что, если разветвленный дочерний элемент закончит свой sleep и вызовет exit до того, как родитель вызовет sigsuspend? Затем родитель получит SIGCHLD, который он игнорирует... И затем он вызовет sigsuspend, который никогда не вернется, потому что SIGCHLD уже был доставлен.

Единственное стопроцентное решение — заблокировать SIGCHLD перед вызовом fork, а затем атомарно разблокировать его при входе в sigsuspend. (Работа с таким состоянием гонки — именно поэтому sigsuspend нуждается в маске сигнала в качестве аргумента... Если вы попытаетесь разблокировать сигнал перед вызовом sigsuspend, возникнет состояние гонки, т. е. сигнал может быть доставлен до того, как вы начнете ждать. Изменение маска сигнала и затем вход в ожидание должны быть атомарными, и вы должны заблокировать любой сигнал, который вы хотите ожидать, прежде чем он сможет быть сгенерирован.)

person Nemo    schedule 20.07.2011
comment
Но зачем вообще заморачиваться с sigsuspend? в чем причина этого? почему родитель не может просто обрабатывать сигналы SIGCHLD по мере их поступления, а не игнорировать их, а затем обрабатывать их за один раз с помощью sigsuspend ? - person horseyguy; 19.05.2021
comment
@horseyguy: Потому что делать что-то нетривиальное в обработчике асинхронных сигналов — это рецепт незаметных и трудно воспроизводимых ошибок. Если все, что вы хотите сделать, это wait для дочернего элемента, ОК... Но если вам нужно сделать что-то сложное, синхронизация управления потоком с использованием sigsuspend сделает код более надежным (и его будет легче проверять на надежность). - person Nemo; 19.05.2021