Отправка SIGINT группам процессов иногда игнорируется.

Вопрос или проблема

Я запускаю группу процессов из bash. Затем я отправляю SIGINT всей группе процессов. Иногда SIGINT убивает процессы, иногда нет. Почему SIGINT иногда игнорируется?

Я наблюдаю разное поведение в зависимости от того, запущена ли группа процессов в фоновом режиме или нет, от вложенности bash оболочек и от операционной системы Mac/Linux. Я был бы очень признателен, если бы кто-то мог пролить свет на это.

В следующих примерах я использую исполняемый файл python под названием sleep_in_pgrp.py

#!/usr/bin/env python2.7
import os;
import subprocess
os.setpgrp();
subprocess.check_call(["sleep","10000"]);

Он создает группу процессов и запускает sleep. Наблюдаемое явление не должно быть связано с python. Я использую python только потому, что в bash нет команды или встроенной функции setpgrp. Обновление: Очевидно, можно также запустить интерактивную оболочку для создания новой группы процессов

1) Запустить группу процессов в фоне и ждать лидера. SIGINT игнорируется.

Выполните следующую команду:

$ bash -c '  { sleep_in_pgrp.py; } & wait $!  '

Bash запускает python в фоне и ожидает его завершения. В другом терминале:

$ ps -Heo pid,ppid,tpgid,pgid,sid,user,args
   PID   PPID  TPGID   PGID    SID     COMMAND
  2507   1574   2963   2507   2507     -bash
  2963   2507   2963   2963   2507       bash -c   { sleep_in_pgrp.py; } & wait $!
  2964   2963   2963   2963   2507         bash -c   { sleep_in_pgrp.py; } & wait $!
  2965   2964   2963   2965   2507           python2.7 ./sleep_in_pgrp.py
  2966   2965   2963   2965   2507             sleep 10000

SIGINT’ing группы процессов python не убивает процессы. В чем может быть причина?

$ sudo kill -s SIGINT -- -2965

2) Запустить группу процессов на переднем плане. SIGINT работает.

Если я удалю & wait $!, SIGINT убивает группу процессов, как и ожидалось. Я не знаю почему, но я не удивлен, что SIGINT в этом случае убил процессы.

$ bash -c '  { sleep_in_pgrp.py; }  '

В другом терминале:

$ ps -Heo pid,ppid,tpgid,pgid,sid,user,args
   PID   PPID  TPGID   PGID    SID     COMMAND
  2507   1574   3352   2507   2507     -bash
  3352   2507   3352   3352   2507       bash -c   { sleep_in_pgrp.py; }
  3353   3352   3352   3353   2507         python2.7 ./sleep_in_pgrp.py
  3354   3353   3352   3353   2507           sleep 10000

SIGINT убивает группу процессов.

$ sudo kill -s SIGINT -- -3353

3) Удаление подшелла при запуске python в фоне. SIGINT работает.

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

Я удаляю bash -c в начале:

$ { sleep_in_pgrp.py; } & wait $!

В другом терминале:

$ ps -Heo pid,ppid,tpgid,pgid,sid,user,args
   PID   PPID  TPGID   PGID    SID     COMMAND
  2507   1574   2507   2507   2507     -bash
  3488   2507   2507   3488   2507       -bash
  3489   3488   2507   3489   2507         python2.7 ./sleep_in_pgrp.py
  3490   3489   2507   3489   2507           sleep 10000

SIGINT убивает группу процессов.

$ sudo kill -s SIGINT -- -2507

4) Выполнение первой команды на Mac: SIGINT работает.

Первые 2 команды были выполнены на CentOs7 VM.

$ uname -a
Linux ip-10-229-193-124 3.10.0-693.5.2.el7.x86_64 #1 SMP Fri Oct 13 10:46:25 EDT 2017 x86_64 x86_64 x86_64 GNU/Linux

Теперь я выполняю первую команду с фоновым python в подшелле на Mac.

$ uname -a
Darwin mbp-005063 15.6.0 Darwin Kernel Version 15.6.0: Sun Jun  4 21:43:07 PDT 2017; root:xnu-3248.70.3~1/RELEASE_X86_64 x86_64

На Mac:

$ bash -c '  { sleep_in_pgrp.py; } & wait $!  '

В другом терминале:

$   PID  PPID TPGID  PGID   SESS COMMAND
18741 40096 18741 18741      0 bash -c   { sleep_in_pgrp.py; } & wait $!
18742 18741 18741 18741      0 bash -c   { sleep_in_pgrp.py; } & wait $!
18743 18742 18741 18743      0 /usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python ./sleep_in_pgrp.py
18744 18743 18741 18743      0 sleep 10000
40094  2423 18741 40094      0 /Applications/iTerm.app/Contents/MacOS/iTerm2 --server /usr/bin/login -fpl hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2 --launch_shell
40095 40094 18741 40095      0 /usr/bin/login -fpl hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2 --launch_shell
40096 40095 18741 40096      0 -bash
-+= 00001 root /sbin/launchd
 \-+= 02423 hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2
   \-+= 40094 hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2 --server /usr/bin/login -fpl hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2 --launch_shell
     \-+= 40095 root /usr/bin/login -fpl hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2 --launch_shell
       \-+= 40096 hbaba -bash
         \-+= 18741 hbaba bash -c   { sleep_in_pgrp.py; } & wait $!
           \-+- 18742 hbaba bash -c   { sleep_in_pgrp.py; } & wait $!
             \-+= 18743 hbaba /usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python ./sleep_in_pgrp.py
               \--- 18744 hbaba sleep 10000

И в этом случае SIGINT также убивает группу процессов

$ sudo kill -s INT -18743

Версия bash в CentOs7:

$ echo $BASH_VERSION
4.2.46(2)-release

На Mac версия bash:

$ echo $BASH_VERSION
4.4.12(1)-release

Этот ответ объясняет, как
Ctrl+C отправляет SIGINT в группу процессов. Это то, что я пытаюсь сделать здесь,
отправляя SIGINT в группу процессов.
Этот ответ упоминает, что неинтерактивные
задачи требуют обработчика SIGINT. Я не уверен, объясняет ли это
различное поведение, которое я вижу. Я также интересуюсь, влияет ли ожидание фонового
процесса на обработку SIGINT этим процессом.

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

Процесс завершает работу, только если полученный сигнал ему неизвестен. Но если процесс устанавливает функцию, связанную с этим сигналом, он не завершится, если только функция, связанная с этим сигналом, не завершит программу.

Например, простая программа на C:

 #include <signal.h>

 int sigreceived = 0;

 void mysignal();

 int main(){
    signal(2, mysignal); //SIGINT соответствует сигналу 2

    while(1);
    return 0;
  }

 void mysignal(){
  sigreceived=1;
 }

В этой программе сигнал 2 захватывается для вызова функции mysignal, которая вместо того, чтобы убить процесс, просто изменяет значение переменной

Следовательно, этот процесс не завершится с помощью SIGINT

Помните, что SIGINT, отправленный с помощью “kill -INT pid” или “kill -INT -- -pgid” или CtrlC, всегда влияет на передние процессы (группа передних процессов); все, что запускается асинхронно с помощью &, не затрагивается INT (за исключением подшеллов, работающих в фоне, таких как подстановка команд: (cmd;cmd, ...etc ) & или $(cmd) &).

И не забывайте: bash реализует WCE (ожидание и сотрудничество), что означает, что родительский процесс ждет завершения дочернего процесса, когда он получает SIGINT через CtrlC, а затем решает умереть только тогда, когда дочерний процесс завершен из-за SIGINT.

Лучше объяснение, чем мое, можно найти здесь: https://www.cons.org/cracauer/sigint.html

.

Ответ или решение

При рассмотрении вопроса о том, почему сигнал SIGINT иногда игнорируется в группе процессов, необходимо учитывать поведение и особенности управления процессами в Unix-подобных системах, таких как Linux и MacOS, а также влияние оболочки bash на эти процессы. Разберем теорию, примеры и их применение в различных ситуациях.

Теория

Сигнал SIGINT (сокращение от "interrupt") предназначен для прерывания выполнения процесса и обычно отправляется через комбинацию клавиш Ctrl+C в терминале. Когда передается SIGINT, запускается поиск обработчика сигнала в программе. Если процесс не определил специального обработчика для SIGINT, он будет завершен по умолчанию.

Иногда сигналы могут игнорироваться в зависимости от состояния процесса или поведения операционной системы. Например, процессы, работающие в фоновом режиме, относятся к другой группе процессов и, следовательно, не являются целевыми для SIGINT, посланного из активного терминала.

Bash, как оболочка, играет важную роль в управлении процессами и группами процессов. Когда процесс выполняется в фоновом режиме (например, с использованием &), его группа процессов может быть не той, которая получает SIGINT из терминала. Более того, bash может обрабатывать сигналы иначе в зависимости от конструкций скриптов или вложенности оболочек.

Примеры

  1. Процессы в фоновом режиме и ожидание:

    Рассмотрим первое поведение: запуск процесса sleep_in_pgrp.py в фоновом режиме с использованием следующей команды:

    bash -c '{ sleep_in_pgrp.py; } & wait $!'

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

  2. Процессы на переднем плане:

    Пример запуска:

    bash -c '{ sleep_in_pgrp.py; }'

    Здесь процесс запускается на переднем плане, и сигнал SIGINT завершает его. Это ожидаемое поведение, так как SIGINT предназначен для активных групп процессов.

  3. Снятие вложенности оболочек:

    Запуск без использования вложенной оболочки:

    { sleep_in_pgrp.py; } & wait $!

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

  4. Различные ОС:

    Различия в поведении между Linux и Mac могут объясняться разными реализациями bash, настройками ядра и конфигурациями терминалов. Например, работающие версии Bash могут иметь различия в реализации WCE (ожидание и совместное завершение), что влияет на обработку сигналов.

Применение

Для устранения неожиданного поведения нужно:

  • Убедиться, что группы процессов управляются правильно, и процессы, которые должны завершаться при SIGINT, запускаются на переднем плане.
  • Понимать, что использование & может изменить идентификаторы групп процессов, поэтому для давления на группу процессов может потребоваться явная команда с использованием kill -s SIGINT -- -<pgid>, где <pgid> — идентификатор целевой группы.
  • Проверить версию bash и особенности операционной системы, которые могут влиять на поведение сигналов.
  • Рассмотреть возможность добавления явного обработчика SIGINT в ваш скрипт или программу, если необходимо специфичное поведение при получении SIGINT.

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

Оцените материал
Добавить комментарий

Капча загружается...