Должны ли дочерние процессы также разблокировать заблокированные сигналы SIGCHLD?

Я пытаюсь понять, как работают сигналы блокировки и разблокировки, и я пытаюсь понять следующий фрагмент кода. В частности, я смотрю на строку 28 (прокомментированную в коде): int a = sigprocmask(SIG_UNBLOCK, &mask, NULL);, она же, где сигнал разблокирован в дочернем элементе.

В учебнике, из которого я получил код, говорится, что код использует блокировку сигналов, чтобы гарантировать, что программа выполняет свою функцию добавления (упрощенная до printf("adding %d\n", pid);) перед функцией удаления (упрощенная до printf("deleting %d\n", pid);). Это имеет смысл для меня; блокируя сигнал SIGCHLD, а затем разблокируя его после выполнения функции добавления, мы гарантируем, что обработчик не будет вызываться до тех пор, пока мы не выполним функцию добавления. Однако зачем нам разблокировать сигнал в ребенке? Разве это не устраняет весь смысл блокировки, немедленно разблокируя ее, позволяя ребенку удалить до того, как родитель добавит?

Однако вывод (описанный после кода) идентичен независимо от того, закомментирована ли строка или нет, а это означает, что это явно не то, что происходит. В учебнике указано:

«Обратите внимание, что дети наследуют заблокированный набор своих родителей, поэтому мы должны быть осторожны, чтобы разблокировать сигнал SIGCHLD в дочернем элементе, прежде чем вызывать execve».

Но мне все еще кажется, что разблокировка приведет к вызову обработчика. Что именно делает эта строка?

void handler(int sig) {
    pid_t pid;
    printf("here\n");
    while ((pid = waitpid(-1, NULL, 0)) > 0); /* Reap a zombie child */
    printf("deleting %d\n", pid); /* Delete the child from the job list */
}

int main(int argc, char **argv) {
    int pid;
    sigset_t mask;
    signal(SIGCHLD, handler);
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask, NULL); /* Block SIGCHLD */

    pid = fork();
    if (pid == 0) {
        printf("in child\n");

        int a = sigprocmask(SIG_UNBLOCK, &mask, NULL); // LINE 28

        printf("a is %d\n",a);
        execve("/bin/date", argv, NULL);
        exit(0);
    }

    printf("adding %d\n", pid);/* Add the child to the job list */
    sleep(5);
    printf("awake\n");

    int b = sigprocmask(SIG_UNBLOCK, &mask, NULL);
    printf("b is %d\n", b);
    sleep(3);

    exit(0);
}

Выходы:

adding 652

in child

a is 0

Wed Apr 24 20:18:04 UTC 2019

awake

here

deleting -1

b is 0

person Evan    schedule 24.04.2019    source источник
comment
Вы не можете безопасно вызывать printf() в обработчике сигнала. Согласно 7.1.4 Использование библиотечных функций , параграф 4 стандарта C: Реентерабельность функций в стандартной библиотеке не гарантируется, и они могут изменять объекты со статической продолжительностью хранения или хранения потоков. и сноска 188: Таким образом, обработчик сигнала, как правило, не может вызывать стандартные библиотечные функции . POSIX позволяет вызывать функции, безопасные для асинхронных сигналов, из обработчика сигналов и предоставляет список. printf() в этом списке нет.   -  person Andrew Henle    schedule 24.04.2019
comment
Блокирующий вызов waitpid() в обработчике сигнала, например, с waitpid(-1, NULL, 0), также является плохой идеей. while ((pid = waitpid(-1, NULL, WNOHANG)) > 0); намного лучше - он будет пожинать все дочерние процессы, которые завершились, без зависания на неопределенный срок в ожидании любого дочернего процесса, который не вышел. Если есть дочерние процессы, которые не завершились, ваш обработчик сигналов будет зависать до тех пор, пока они все не завершатся.   -  person Andrew Henle    schedule 25.04.2019
comment
Разблокировка в дочернем не влияет на родителя.   -  person Barmar    schedule 25.04.2019


Ответы (1)


Однако зачем нам разблокировать сигнал в ребенке? Разве это не устраняет весь смысл блокировки, немедленно разблокируя ее, позволяя ребенку удалить до того, как родитель добавит?

Нет. У каждого процесса своя маска сигнала. Новый процесс наследует маску сигнала своего родителя, но только в том же смысле, в каком он наследует содержимое памяти родителя — дочерний процесс получает независимую копию. Его изменения в этой копии не отражаются в родительской копии и наоборот после запуска дочерней. Если бы это было не так, то все процессы в системе использовали бы одну маску сигнала.

Только родитель не должен получать SIGCLD слишком рано, поэтому только родитель должен заблокировать этот сигнал.

[...] В учебнике говорится:

«Обратите внимание, что дочерние элементы наследуют заблокированный набор своих родителей, поэтому мы должны быть осторожны, чтобы разблокировать сигнал SIGCHLD в дочернем элементе перед вызовом execve».

Но мне все еще кажется, что разблокировка приведет к вызову обработчика.

Опять же, «наследовать» в смысле наследования копии, а не в смысле использования одной и той же маски.

Что именно делает эта строка?

Он разблокирует SIGCLD в дочернем элементе — опять же, не оказывая никакого влияния на родителя — на случай, если его блокировка помешает поведению /bin/date, которое собирается выполнить дочерний элемент.

person John Bollinger    schedule 24.04.2019