Предотвращение распространения SIGINT в родительский процесс

10

Рассматривая сценарий, в котором Родительская программа (может быть программа C ++ или Shell Script) выполняет сценарий оболочки Child, когда мы нажимаем Control + C (или любой другой символ, настроенный как символ INTR) во время выполнения сценария Child Shell Script, SIGINT отправляется всем процессам в группе процессов переднего плана. Это включает в себя родительский процесс.

Источник: POSIX.1-2008 XBD, раздел 11.1.9.

Есть ли способ переопределить это поведение по умолчанию? Что процесс CHILD сам обрабатывает СИГНАЛ без его распространения на родителя?

Ссылка: Пост переполнения стека - родительский процесс не завершается, когда дочерний процесс прерывается (TRAP INT)

Guddu
источник

Ответы:

11

(Вдохновленный ответом Жиля)

При установленном ISIGфлаге единственный способ Childполучить сценарий SIGINTбез получения родительского элемента SIGINT- это войти в свою собственную группу процессов. Это может быть достигнуто с set -mопцией.

Если вы включите -mопцию в Childсценарии оболочки, он будет выполнять управление заданиями, не будучи интерактивным. Это заставит его запускать вещи в отдельной группе процессов, предотвращая получение родительского элемента, SIGINTкогда INTRчитается символ.

Вот описание -mопции POSIX :

-mЭта опция должна поддерживаться, если реализация поддерживает опцию User Portability Utilities. Все задания должны выполняться в своих собственных группах процессов. Непосредственно перед тем, как оболочка выдает подсказку после завершения фонового задания, сообщение о состоянии завершения фонового задания должно быть записано со стандартной ошибкой. Если приоритетное задание останавливается, оболочка должна записать сообщение со стандартной ошибкой на этот счет в формате, описанном утилитой заданий. Кроме того, если задание изменяет состояние, отличное от выхода (например, если оно останавливается для ввода или вывода или останавливается сигналом SIGSTOP), оболочка должна написать аналогичное сообщение непосредственно перед написанием следующего приглашения. Эта опция включена по умолчанию для интерактивных оболочек.

-mВариант похож -i, но это не меняет поведение оболочечных почти столько же , сколько -iделает.

Пример:

  • Parentскрипт:

    #!/bin/sh
    
    trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
    
    echo "PARENT: pid=$$"
    echo "PARENT: Spawning child..."
    ./Child
    echo "PARENT: child returned"
    echo "PARENT: exiting normally"
  • Childскрипт:

    #!/bin/sh -m
    #         ^^        
    # notice the -m option above!
    
    trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
    
    echo "CHILD: pid=$$"
    echo "CHILD: hit enter to exit"
    read foo
    echo "CHILD: exiting normally"

Вот что происходит, когда вы нажимаете Control+, Cпока Childожидает ввода:

$ ./Parent
PARENT: pid=12233
PARENT: Spawning child...
CHILD: pid=12234
CHILD: hit enter to exit
^CCHILD: caught SIGINT; exiting
PARENT: child returned
PARENT: exiting normally

Обратите внимание, как SIGINTобработчик родителя никогда не выполняется.

Кроме того, если вы хотите изменить Parentвместо Child, вы можете сделать это:

  • Parentскрипт:

    #!/bin/sh
    
    trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
    
    echo "PARENT: pid=$$"
    echo "PARENT: Spawning child..."
    sh -m ./Child  # or 'sh -m -c ./Child' if Child isn't a shell script
    echo "PARENT: child returned"
    echo "PARENT: exiting normally"
  • Childскрипт (нормальный, нет необходимости -m):

    #!/bin/sh
    
    trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
    
    echo "CHILD: pid=$$"
    echo "CHILD: hit enter to exit"
    read foo
    echo "CHILD: exiting normally"

Альтернативные идеи

  1. Измените другие процессы в группе процессов переднего плана, чтобы игнорировать их SIGINTв течение Child. Это не отвечает на ваш вопрос, но может дать вам то, что вы хотите.
  2. Изменить Childна:
    1. Используйте stty -gдля резервного копирования текущих настроек терминала.
    2. Бегите , stty -isigчтобы не генерировать сигналы с INTR, QUITи SUSPперсонажами.
    3. В фоновом режиме прочитайте входной сигнал терминала и отправьте сигналы самостоятельно (например, запустите, kill -QUIT 0когда Control+ \читается, kill -INT $$когда Control+ Cчитается). Это не тривиально, и может быть невозможно заставить это работать гладко, если Childсценарий или все, что он выполняет, должно быть интерактивным.
    4. Восстановите настройки терминала перед выходом (в идеале из ловушки EXIT).
  3. То же, что №2, но вместо того, чтобы работать stty -isig, подождите, пока пользователь нажмет Enterили другой не специальный ключ, прежде чем убивать Child.
  4. Напишите свою собственную setpgidутилиту на C, Python, Perl и т. Д., Которую вы можете использовать для вызова setpgid(). Вот грубая реализация C:

    #define _XOPEN_SOURCE 700
    #include <unistd.h>
    #include <signal.h>
    
    int
    main(int argc, char *argv[])
    {
        // todo: add error checking
        void (*backup)(int);
        setpgid(0, 0);
        backup = signal(SIGTTOU, SIG_IGN);
        tcsetpgrp(0, getpid());
        signal(SIGTTOU, backup);
        execvp(argv[1], argv + 1);
        return 1;
    }

    Пример использования от Child:

    #!/bin/sh
    
    [ "${DID_SETPGID}" = true ] || {
        # restart self after calling setpgid(0, 0)
        exec env DID_SETPGID=true setpgid "$0" "$@"
        # exec failed if control reached this point
        exit 1
    }
    unset DID_SETPGID
    
    # do stuff here
Ричард Хансен
источник
4

Как объясняется в главе, которую вы цитируете из POSIX, SIGINT отправляется всей группе процессов переднего плана. Поэтому, чтобы избежать уничтожения родительского элемента программы, организуйте его запуск в собственной группе процессов.

Оболочки не предоставляют доступ setpgrpчерез встроенную или синтаксическую конструкцию, но есть косвенный способ достичь этого, а именно запустить оболочку в интерактивном режиме. (Спасибо Стефану Гименесу за трюк.)

ksh -ic '
  … the part that needs to be interruptible without bothering the parent …
'
Жиль "ТАК - перестань быть злым"
источник
+1 за умную идею, хотя имейте в виду, что поведение оболочки меняется, когда она является интерактивной оболочкой (по крайней мере, оболочка POSIX; я не знаком с деталями ksh). Примеры: ${ENV}источник получен, оболочка не выйдет сразу, когда обнаружит ошибку, SIGQUITи SIGTERMигнорируется.
Ричард Хансен
1

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

Во-вторых, ссылка на POSIX четко гласит, что «Если установлен ISIG, символ INTR должен быть отброшен при обработке».

Так что это два варианта. Третий - запустить ребенка в своей собственной группе процессов.

bahamat
источник
Спасибо за ваш ответ. Мне также нужно выяснить способ выхода пользователя из сценария. Есть ли способ установить ISIG и в то же время разрешить некоторой комбинации клавиш CONTRL (может быть CTRL-Q) выйти из сценария оболочки без отправки сигналов родителю и аналогичным образом? Кроме того, не могли бы вы сказать мне, как я могу запустить ребенка в другой группе процессов, чем его родитель?
Гудду