Можно ли принудительно завершить работу программы, игнорирующей сигналы, по сочетанию клавиш Ctrl-C?

Можно ли принудительно завершить работу программы, игнорирующей сигналы, по сочетанию клавиш Ctrl-C?

У меня есть программа, которая игнорирует SIGINT, но которую я хочу запустить на переднем плане. Я хотел бы найти способ принудительно закрыть ее по Ctrl-C. Есть ли способ написать оболочку (которую вы бы назвали ./wrapper.sh my_program), которая заставила бы неправильно работающую программу завершить работу, возможно, обнаружив проигнорированный SIGINT и сгенерировав SIGKILL?

Этот ответэто полная противоположность тому, что я ищу — я хотел бы заставить программу, которая игнорирует сигнал, завершить работу при получении сигнала SIGINT.

решение1

Давайте создадим оболочку, которая «конвертирует» SIGINT в SIGKILL.

Нам нужен процесс, который выполняется my_programи получает SIGINT при Ctrl+ c. Когда он получает SIGINT, он должен отправить SIGKILL в my_program. Обратите внимание, что нам не нужно my_programна самом деле получать SIGINT, вызванный Ctrl+ c; достаточно, чтобы он получил SIGKILL, вызванный дополнительным процессом.

Соответствующие факты:

  • Стандартный способ запуска одного процесса вместе с другим — это запуск одного из них асинхронно в фоновом режиме (т. е. с завершением &).
  • Когда вы нажимаете Ctrl+ c, ваш эмулятор терминала отправляет сигнал SIGINT процессам в группе процессов переднего плана.
  • Некоторые (простые) оболочки запускают все в одной группе процессов. Если им приказано запустить команду в фоновом режиме, они перенаправят ее stdin в /dev/nullили эквивалентный файл, чтобы команда не могла украсть ввод.
  • Другие оболочки могут запускать каждую команду в отдельной группе процессов. Если им приказано запустить команду в фоновом режиме, то они оставят ее stdin как есть; тем не менее, команда в фоновом режиме не сможет украсть ввод с управляющего терминала из-заSIGTTIN. Это позволяет оболочке переместить задачу из фонового режима на передний план, информируя терминал о новой группе процессов переднего плана. Механизм называется управлением задачами и может быть отключен. В скриптах он отключен по умолчанию.
  • В любом случае процесс в фоновом режиме не может читать с терминала. Это означает, что мы не должны работать my_programв фоновом режиме в случае, если stdin — это терминал и my_programнужно читать с него.
  • К сожалению, в некоторых оболочках мы также не должны запускать другой процесс в фоновом режиме. Некоторые оболочки используют отдельные группы процессов, даже если управление заданиями отключено. Другой процесс, не находящийся в группе процессов переднего плана, не получит SIGINT при Ctrl+ c, поэтому он не сможет «преобразовать» его в SIGKILL.
  • В моем Debian 10 poshесть оболочка, которая запускает все в одной группе процессов.

Это приводит к следующей обертке:

#!/usr/bin/env posh

( trap 'kill -s KILL 0' INT
  while kill -s 0 "$$" 2>/dev/null; do sleep 1; done
) &
exec "$@"

exec "$@"запускается my_program(или что-то еще, что вы укажете), возможно, с аргументами. Благодаря exec my_programзаменит оболочку. Он не только сможет читать из stdin, который может быть терминалом; его PID будет таким же, как у замененной оболочки. В этом смысле оболочка прозрачна. Кроме того, у этого есть приятная особенность: PID my_programизвестен до my_programзапуска, и мы можем легко использовать его в другом процессе (в данном случае в подоболочке в фоновом режиме), чтобы определить, когда он my_programдействительно завершится.

trap"Преобразует" SIGINT в SIGKILL. Примечание отправляет kill -s KILL 0SIGKILL всей группе процессов, включая потомков , my_programесли они находятся в группе (если вы хотите убить my_programтолько , то используйте kill -s KILL "$$"вместо этого ). Тем не менее kill -s 0 "$$"проверяет существование my_programтолько .

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

#!/bin/sh -

exec 9>&1
( "$@"; kill -s TERM 0 ) >&9 9>&- | (
  trap 'kill -s KILL 0' INT
  while :; do sleep 1; done
)

В этом варианте my_programне будет заменяться оболочка. Второй kill— «преобразовать» SIGINT в SIGKILL. Первый kill— завершить цикл в случае my_programвыхода в обстоятельствах, когда цикл в противном случае выжил бы.

Есть несколько сценариев, которые могут работать не так, как вы ожидаете (с любой оберткой). Среди них:

  • Если my_programразветвляется и выходит, оставляя настоящую работу потомку. Проблемная программа, о которой идет речь, скорее всего, не ведет себя так, поскольку вы пытались ее + Ctrl. cНо в целом это может быть так.
  • Если my_programпорождаются дочерние процессы в другой группе(ах) процессов, и вы хотите завершить их вместе с их родителем.
  • Если my_programнастраивает терминал так, чтобы он не отправлял SIGINT при Ctrl+ c. Если вы подозреваете, что это происходит, улучшите обертку, поместив stty -F /dev/tty intr ^Cмежду sleep 1и done. В этом случае программа может даже не игнорировать SIGINT, она может просто убедиться, что не получит его от терминала. Так что, возможно, SIGKILL — это излишество; возможно, достаточно восстановить эту функциональность терминала, и Ctrl+ cначнет работать.

Из комментария:

Программы, игнорирующие SIGINT, обычно делают это по очень веской причине.

Правда. Попробуйте SIGTERM или SIGHUP вместо SIGKILL. Возможно, рассматриваемая программа не игнорирует хотя бы один из них и завершается корректно с надлежащей очисткой.

Связанный контент