Состояние гонки в моем обработчике сигналов POSIX

Следующая программа создает дочернюю программу, которая многократно запускает "/bin/sleep 10". Родитель устанавливает обработчик сигнала для SIGINT, который доставляет SIGINT дочернему элементу. Однако иногда отправка SIGINT дочернему элементу завершается ошибкой. Почему так и что мне не хватает?

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

pid_t foreground_pid = 0;

void sigint_handler(int sig)
{
    printf("sigint_handler: Sending SIGINT to process %d\n",
            foreground_pid);

    if ((foreground_pid != 0) && kill(foreground_pid, SIGCONT) == -1) {
        perror("sending SIGINT to forground process failed");
        printf("foreground_pid == %d", foreground_pid);
        exit(EXIT_FAILURE);
    }

    foreground_pid = 0;
}

int main(int argc, const char *argv[])
{
    while (1) {
        pid_t child_pid;

        if ((child_pid = fork()) == -1) {
            perror("fork failed");
            exit(EXIT_FAILURE);
        }

        if (child_pid) { /* parent */
            foreground_pid = child_pid;

            printf("waiting for child (%d) to complete ...\n", child_pid);
            fflush(stdout);

            /* install SIGINT signal handler */
            struct sigaction sa;
            struct sigaction old_handler;
            sa.sa_handler = sigint_handler;
            sigemptyset(&sa.sa_mask);
            sa.sa_flags = SA_RESTART | SA_RESETHAND;
            sigaction(SIGINT, &sa, NULL);

            int status = 0;

            /* wait for child to finish */
            if (waitpid(child_pid, &status, 0) == -1) {
                perror("waitpid failed");
                exit(EXIT_FAILURE);
            }

            printf("    done.\n");
            fflush(stdout);

        }
        else { /* child */
            char * const argv[] = { "/bin/sleep", "10", NULL};

            if (execve(argv[0], argv, NULL) == -1) {
                perror("execve failed");
                exit(EXIT_FAILURE);
            }

            exit(EXIT_SUCCESS);
        }

    }

    return EXIT_SUCCESS;
}

% make && ./foo
gcc -Wall -pedantic -std=c99 -D_POSIX_C_SOURCE=200809L foo.c -o foo
waiting for child (4582) to complete ...
^Csigint_handler: Sending SIGINT to process 4582
    done.
waiting for child (4583) to complete ...
^Csigint_handler: Sending SIGINT to process 4583
sending SIGINT to forground process failed: No such process
foreground_pid == 4583

person kmkkmk    schedule 26.06.2011    source источник


Ответы (1)


Драйвер tty выполняет SIGINT для всей группы процессов, когда вы набираете Ctrl + C; это включает в себя дочерний процесс, который завершится в ответ на него, потому что у него не установлен обработчик. Таким образом, вы дублируете то, что уже сделано, и если ребенок все еще рядом, когда родитель пытается kill(), это что-то вроде дерьма.

person geekosaur    schedule 26.06.2011
comment
Точно. На мой взгляд, решением было бы вызвать setsid() в вашем дочернем процессе после разветвления, чтобы поместить дочерний элемент в новый сеанс и группу, это должно предотвратить поступление сигнала SIGINT к дочернему процессу. - person SlappyTheFish; 27.06.2011
comment
@SlappyTheFish: в этом случае предпочтительнее setpgrp() или setpgid(), но я подозреваю, что правильный ответ - просто позволить драйверу tty справиться с этим. - person geekosaur; 27.06.2011
comment
Кроме того, можно установить SIG_IGN в качестве обработчика сигнала в дочернем (между fork и execve. - person kmkkmk; 28.06.2011
comment
@geekosaur Я проверил документы, но я не уверен на 100%, почему setgrp() в этом случае предпочтительнее, чем setsid(). Это потому, что новый сеанс не обязательно создается? - person SlappyTheFish; 28.06.2011
comment
@SlappyTheFish: де-факто в этом случае нет большой практической разницы; отдельные группы процессов в одном и том же сеансе могут быть присоединены к терминалу и отсоединены от него (так работает fg). Это скорее концептуальная разница между несвязанными процессами и процессами, которые связаны, но не должны быть привязаны к tty. - person geekosaur; 28.06.2011
comment
@kmkkmk: если вы проигнорируете SIG_IGN, он также проигнорирует сообщение, отправленное родительским обработчиком SIGINT. (Хотя я только что заметил, что в настоящее время код хочет отправить SIGCONT, что ничего не даст.) - person geekosaur; 28.06.2011