Учитывая процесс оболочки (например, sh
) и его дочерний процесс (например cat
), как я могу имитировать поведение Ctrl+, Cиспользуя идентификатор процесса оболочки?
Вот что я пробовал:
Запуск sh
и затем cat
:
[user@host ~]$ sh
sh-4.3$ cat
test
test
Отправка SIGINT
на cat
другой терминал:
[user@host ~]$ kill -SIGINT $PID_OF_CAT
cat
получил сигнал и прекратил (как и ожидалось).
Отправка сигнала родительскому процессу, похоже, не работает. Почему сигнал не распространяется cat
при отправке его родительскому процессу sh
?
Это не работает:
[user@host ~]$ kill -SIGINT $PID_OF_SH
Ответы:
Как CTRL+ Cработает
Прежде всего, чтобы понять, как работает CTRL+ C.
Когда вы нажимаете CTRL+ C, ваш эмулятор терминала отправляет символ ETX (конец текста / 0x03).
TTY настроен так, что, когда он получает этот символ, он отправляет SIGINT в группу процессов переднего плана терминала. Эту конфигурацию можно посмотреть, выполнив
stty
и посмотревintr = ^C;
. В спецификации POSIX говорится, что при получении INTR он должен отправить SIGINT в группу процессов переднего плана этого терминала.Что такое группа процессов переднего плана?
Итак, теперь вопрос в том, как вы определяете, что такое группа процессов переднего плана? Группа процессов переднего плана - это просто группа процессов, которые будут получать любые сигналы, генерируемые клавиатурой (SIGTSTOP, SIGINT и т. Д.).
Простейшим способом определения идентификатора группы процессов является использование
ps
:Второй столбец будет идентификатором группы процессов.
Как отправить сигнал группе процессов?
Теперь, когда мы знаем, что такое идентификатор группы процессов, нам нужно смоделировать поведение POSIX при отправке сигнала всей группе.
Это можно сделать
kill
, поставив-
перед идентификатором группы.Например, если идентификатор группы процессов равен 1234, вы должны использовать:
Имитация CTRL+ Cс использованием номера терминала.
Таким образом, выше описано, как имитировать CTRL+ Cкак ручной процесс. Но что, если вы знаете номер TTY и хотите имитировать CTRL+ Cдля этого терминала?
Это становится очень легко.
Предположим,
$tty
это терминал, на который вы хотите ориентироваться (вы можете получить его, запустивtty | sed 's#^/dev/##'
в терминале).Это отправит SIGINT любой группе процессов переднего плана
$tty
.источник
kill
команда может завершиться ошибкой.sends a SIGINT to the foreground process group of the terminal.
fork
. Пример минимального запуска C: unix.stackexchange.com/a/465112/32558Как говорит vinc17, нет причин для этого. Когда вы набираете последовательность ключей, генерирующих сигнал (например, Ctrl+ C), сигнал отправляется всем процессам, которые подключены (связаны с) к терминалу. Не существует такого механизма для сигналов, генерируемых
kill
.Тем не менее, команда как
отправит сигнал всем процессам в группе процессов 12345; см. kill (1) и kill (2) . Дочерние элементы оболочки обычно находятся в группе процессов оболочки (по крайней мере, если они не асинхронны), поэтому отправка сигнала на отрицательный идентификатор PID оболочки может сделать то, что вы хотите.
ой
Как указывает vinc17, это не работает для интерактивных оболочек. Вот альтернатива, которая может работать:
ps -pPID_of_shell
получает информацию о процессе в оболочке.o tpgid=
говоритps
выводить только идентификатор группы процессов терминала без заголовка. Если это меньше, чем 10000,ps
будет отображаться с пробелом;$(echo …)
быстрый трюк , чтобы сдирать ведущих (и конечные) пространства.Я сделал это, чтобы работать в кратком тестировании на компьютере Debian.
источник
Вопрос содержит свой ответ. Посылая
SIGINT
кcat
процессу сkill
идеальным моделированием того , что происходит при нажатии кнопки^C
.Чтобы быть более точным, символ прерывания (
^C
по умолчанию) отправляетсяSIGINT
каждому процессу в группе процессов переднего плана терминала. Если бы вместоcat
вас выполнялась более сложная команда, включающая несколько процессов, вам пришлось бы убить группу процессов, чтобы добиться того же эффекта, что и^C
.Когда вы запускаете любую внешнюю команду без
&
оператора фона, оболочка создает новую группу процессов для команды и уведомляет терминал о том, что эта группа процессов теперь находится на переднем плане. Оболочка все еще находится в собственной группе процессов, которая больше не находится на переднем плане. Затем оболочка ожидает команды на выход.Вот где вы, кажется, стали жертвой из-за распространенного заблуждения: идея о том, что оболочка что-то делает для облегчения взаимодействия между дочерним процессом (процессами) и терминалом. Это просто неправда. Как только он выполнил работу по настройке (создание процесса, настройка режима терминала, создание каналов и перенаправление других файловых дескрипторов и выполнение целевой программы), оболочка просто ждет . То, что вы вводите,
cat
не проходит через оболочку, будь то обычный ввод или специальный символ, генерирующий сигнал^C
.cat
Процесс имеет прямой доступ к терминалу через его собственных файловых дескрипторов, и терминал имеет возможность передавать сигналы непосредственно вcat
процессе , потому что это на переднем плане группы процессов.После того, как
cat
процесс умирает, оболочка будет уведомлена, потому что это родительскийcat
процесс. Затем оболочка становится активной и снова ставит себя на передний план.Вот упражнение, чтобы улучшить ваше понимание.
В приглашении оболочки в новом терминале выполните эту команду:
exec
Ключевое слово вызывает оболочку для выполненияcat
без создания дочернего процесса. Оболочка заменена наcat
. PID, который раньше принадлежал оболочке, теперь является PIDcat
. Проверьте это сps
помощью другого терминала. Наберите несколько случайных строк и посмотрите, чтоcat
они вам возвращаются, доказывая, что он по-прежнему ведет себя нормально, несмотря на отсутствие процесса оболочки в качестве родителя. Что будет, когда вы нажмете^C
сейчас?Ответ:
источник
exec cat
нажатия^C
не попал бы^C
в кошку. Почему бы это прекратить тот,cat
который сейчас заменил оболочку? Поскольку оболочка была заменена, оболочка - это то, что реализует логику отправки SIGINT его дочерним элементам при получении^C
.Там нет причин для размножения
SIGINT
ребенка. Более того,system()
спецификация POSIX гласит: «Функция system () должна игнорировать сигналы SIGINT и SIGQUIT и должна блокировать сигнал SIGCHLD, ожидая завершения команды».Если оболочка распространит полученное
SIGINT
, например, после реального нажатия Ctrl-C, это будет означать, что дочерний процесс получитSIGINT
сигнал дважды, что может привести к нежелательному поведению.источник
system()
. Но вы правы: если он ловит сигнал (очевидно, он это делает), то нет причин распространять его вниз.setpgid
Минимальный пример группы процессов POSIX CЭто может быть легче понять с помощью минимального работоспособного примера базового API.
Это показывает, как сигнал действительно отправляется ребенку, если ребенок не изменил свою группу процессов с помощью
setpgid
.main.c
GitHub вверх по течению .
Компилировать с:
Бегать без
setpgid
Без каких-либо аргументов CLI
setpgid
не обойтись:Возможный результат:
и программа зависает.
Как мы видим, pgid обоих процессов одинаков, поскольку он наследуется
fork
.Тогда всякий раз, когда вы нажмете:
Это выводит снова:
Это показывает, как:
kill(-pgid, SIGINT)
Выйдите из программы, отправив разные сигналы обоим процессам, например, SIGQUIT с помощью
Ctrl + \
.Бежать с
setpgid
Если вы запускаете с аргументом, например:
потом потомок меняет свой pgid, и теперь каждый раз из одного родителя печатается только один sigint:
А теперь, когда вы нажмете:
только родитель также получает сигнал:
Вы по-прежнему можете убить родителя, как и раньше, используя SIGQUIT:
однако у ребенка теперь есть другой PGID, и он не получает этот сигнал! Это видно из:
Вам придется убить его явно с помощью:
Это проясняет, почему существуют группы сигналов: в противном случае мы бы получили кучу процессов, которые будут очищаться вручную все время.
Проверено на Ubuntu 18.04.
источник