Вопрос или проблема
Я запускаю группу процессов из 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
” или Ctrl–C, всегда влияет на передние процессы (группа передних процессов); все, что запускается асинхронно с помощью &, не затрагивается INT (за исключением подшеллов, работающих в фоне, таких как подстановка команд: (cmd;cmd, ...etc ) &
или $(cmd) &
).
И не забывайте: bash реализует WCE (ожидание и сотрудничество), что означает, что родительский процесс ждет завершения дочернего процесса, когда он получает SIGINT через Ctrl–C, а затем решает умереть только тогда, когда дочерний процесс завершен из-за SIGINT.
Лучше объяснение, чем мое, можно найти здесь: https://www.cons.org/cracauer/sigint.html
.
Ответ или решение
При рассмотрении вопроса о том, почему сигнал SIGINT иногда игнорируется в группе процессов, необходимо учитывать поведение и особенности управления процессами в Unix-подобных системах, таких как Linux и MacOS, а также влияние оболочки bash на эти процессы. Разберем теорию, примеры и их применение в различных ситуациях.
Теория
Сигнал SIGINT (сокращение от "interrupt") предназначен для прерывания выполнения процесса и обычно отправляется через комбинацию клавиш Ctrl+C в терминале. Когда передается SIGINT, запускается поиск обработчика сигнала в программе. Если процесс не определил специального обработчика для SIGINT, он будет завершен по умолчанию.
Иногда сигналы могут игнорироваться в зависимости от состояния процесса или поведения операционной системы. Например, процессы, работающие в фоновом режиме, относятся к другой группе процессов и, следовательно, не являются целевыми для SIGINT, посланного из активного терминала.
Bash, как оболочка, играет важную роль в управлении процессами и группами процессов. Когда процесс выполняется в фоновом режиме (например, с использованием &
), его группа процессов может быть не той, которая получает SIGINT из терминала. Более того, bash может обрабатывать сигналы иначе в зависимости от конструкций скриптов или вложенности оболочек.
Примеры
-
Процессы в фоновом режиме и ожидание:
Рассмотрим первое поведение: запуск процесса
sleep_in_pgrp.py
в фоновом режиме с использованием следующей команды:bash -c '{ sleep_in_pgrp.py; } & wait $!'
В этом сценарии процесс запускается в фоновом режиме и bash ждет завершения. Сигнал SIGINT в данном случае не убивает процесс, потому что команда
wait
стоит в отдельной строке и игнорирует SIGINT, пока не завершится целевой процесс. Кроме того, процессы, выполненные в фоновом режиме, не получают сигналы, предназначенные для группы процессов, работающих на переднем плане. -
Процессы на переднем плане:
Пример запуска:
bash -c '{ sleep_in_pgrp.py; }'
Здесь процесс запускается на переднем плане, и сигнал SIGINT завершает его. Это ожидаемое поведение, так как SIGINT предназначен для активных групп процессов.
-
Снятие вложенности оболочек:
Запуск без использования вложенной оболочки:
{ sleep_in_pgrp.py; } & wait $!
В этом случае, удалив уровень вложенности оболочки, вы изменяете идентификаторы групп процессов, что приводит к тому, что SIGINT снова принимает воздействие на процесс, поскольку он становится частью текущей последовательности вызова SIGINT по группам процессов.
-
Различные ОС:
Различия в поведении между Linux и Mac могут объясняться разными реализациями bash, настройками ядра и конфигурациями терминалов. Например, работающие версии Bash могут иметь различия в реализации WCE (ожидание и совместное завершение), что влияет на обработку сигналов.
Применение
Для устранения неожиданного поведения нужно:
- Убедиться, что группы процессов управляются правильно, и процессы, которые должны завершаться при SIGINT, запускаются на переднем плане.
- Понимать, что использование
&
может изменить идентификаторы групп процессов, поэтому для давления на группу процессов может потребоваться явная команда с использованиемkill -s SIGINT -- -<pgid>
, где<pgid>
— идентификатор целевой группы. - Проверить версию bash и особенности операционной системы, которые могут влиять на поведение сигналов.
- Рассмотреть возможность добавления явного обработчика SIGINT в ваш скрипт или программу, если необходимо специфичное поведение при получении SIGINT.
Понимание и использование этих аспектов поможет избежать неожиданностей при управлении процессами и их группами в различных окружениях и платформах.