Когда обрабатывается сигнал и почему какая-то информация замерзает?
-
Откройте терминал с именем "termA" и запустите созданный файл callback.sh
с помощью /bin/bash callback.sh
.
cat callback.sh
#!/bin/bash
myCallback() {
echo "callback function called at $(date)"
}
trap myCallback SIGUSR1
sleep 20
-
Откройте новый терминал с именем "termB" и запустите:
pkill -USR1 -f callback.sh
Что-то, как показано ниже, показано через 20 секунд в termA; он никогда не показывался в терминах А мгновенно:
callback function called at Mon Nov 19 08:21:52 HKT 2018
Вывод подтвердил, что, когда Bash выполняет внешнюю команду на переднем плане, он не обрабатывает никаких сигналов, полученных до завершения процесса переднего плана (см. Ловушку сигнала).
Сделайте небольшое изменение в callback.sh
:
cat callback.sh
#!/bin/bash
myCallback() {
echo "callback function called at $(date)"
}
trap myCallback SIGUSR1
while true; do
read -p "please input something for foo: " foo
done
Добавьте бесконечный цикл while и снимите sleep 20
.
-
Откройте терминал с именем "termA" и запустите созданный файл callback.sh
с /bin/bash callback.sh
; в первый раз информация всплывает мгновенно.
please input something for foo:
-
Откройте новый терминал с именем "termB" и запустите pkill -USR1 -f callback.sh
; в первый раз информация всплывает мгновенно в termA.
callback function called at Mon Nov 19 09:07:14 HKT 2018
Проблема 1: callback.sh
содержит бесконечный цикл while. Как это объясняет следующее?
он не обрабатывает сигналы, полученные до завершения процесса переднего плана
В этом случае процесс переднего плана никогда не заканчивается.
Продолжайте в termB, запустите pkill -USR1 -f callback.sh
во второй раз.
callback function called at Mon Nov 19 09:07:14 HKT 2018
Информация выше всплывает мгновенно в termA снова.
Проблема 2: Нет, please input something for foo:
показано в termA, перейдите к termB, запустите pkill -USR1 -f callback.sh
в третий раз, следующая информация снова отображается в termA.
callback function called at Mon Nov 19 09:07:24 HKT 2018
По-прежнему не please input something for foo:
показано в termA.
Почему информация please input something for foo:
замораживать?
Ответы
Ответ 1
Прежде чем объяснить вашу проблему, немного о том, как работает команда read
. Он читает входные данные из stdin
пока не встретится EOF
. Можно с уверенностью сказать, что вызов для read
команды неблокирует, когда дело доходит до чтения из файлов на диске. Но когда stdin
подключен к терминалу, команда будет блокироваться, пока пользователь не введет что-то.
Как работают обработчики сигналов?
Простое объяснение того, как работает обработка сигналов. Смотрите приведенный ниже фрагмент кода на C
который действует только на SIGINT
(он же CTRL+C
).
#include <stdio.h>
#include <signal.h>
/* signal handler definition */
void signal_handler(int signum){
printf("Hello World!\n");
}
int main(){
//Handle SIGINT with a signal handler
signal(SIGINT, signal_handler);
//loop forever!
while(1);
}
Он зарегистрирует обработчик сигнала и затем войдет в бесконечный цикл. Когда мы Ctrl-C
, мы все можем согласиться с тем, что обработчик сигнала signal_handler()
должен исполниться и "Hello World!"
выводит на экран, но программа была в бесконечном цикле. Для того чтобы напечатать "Hello World!"
Должно быть, это был тот случай, когда он разорвал цикл для выполнения обработчика сигнала, верно? Поэтому он должен выйти из цикла, а также из программы. Давай посмотрим:
gcc -Wall -o sighdl.o signal.c
./sighdl.o
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
Как показывают выходные данные, каждый раз, когда мы выпускаем Ctrl-C
, "Hello World!"
печатает, но программа возвращается в бесконечный цикл. Только после выдачи сигнала SIGQUIT
с Ctrl-\
программа фактически Ctrl-\
. В зависимости от ваших настроек ulimit
, он будет выгружать ядро или распечатывать полученный номер сигнала.
Хотя интерпретация выхода из цикла является разумной, она не учитывает основную причину обработки сигналов, то есть асинхронную обработку событий. Это означает, что обработчик сигнала действует вне стандартного потока управления программой; фактически вся программа сохраняется в контексте, и создается новый контекст только для того, чтобы обработчик сигнала мог выполнить его. Как только обработчик сигнала завершит свои действия, контекст переключается обратно и начинается нормальный поток выполнения (т.е. while(1)
).
Чтобы ответить на ваши вопросы,
Вывод подтвердил, что когда bash выполняет внешнюю команду на переднем плане, он не обрабатывает сигналы, полученные до тех пор, пока процесс на переднем плане не завершится
Ключевым моментом, на который следует обратить внимание, является внешняя команда. В первом случае, когда sleep
является внешним процессом, а во втором случае read
является встроенным в самой оболочке. Таким образом, распространение сигнала на эти два отличается в обоих этих случаях
type read
read is a shell builtin
type sleep
sleep is /usr/bin/sleep
1. Откройте терминал с именем termA и запустите созданный файл callback.sh с /bin/bash callback.sh в первый раз, информация сразу же появится.
Да, такое поведение ожидается. Потому что в настоящее время определена только функция, и обработчик прерываний зарегистрирован в функции myCallback
а сигнал еще не получен в сценарии. По мере выполнения последовательности сообщение из приглашения на read
выдается впервые.
2. Откройте новый терминал с именем termB и запустите pkill -USR1 -f callback.sh
первый раз, информация сразу появится в termA
Да, пока команда read
ожидает строку, за которой следует нажатие клавиши Enter, которая сигнализирует EOF
, она получает сигнал SIGUSR1
от другого терминала, текущий контекст выполнения сохраняется, и управление переключается на обработчик сигнала, который печатает строка с текущей датой.
Как только обработчик завершения выполнения, контекст возобновляется в while
циклы, в котором для read
команда еще ждет входной строки. Пока команда read
будет успешной, все последующие прерывания сигнала будут просто печатать строку внутри обработчика сигнала.
Перейдите в termB, запустите pkill -USR1 -f callback.sh
во второй раз.
Как и объяснено ранее, команда read
не завершается один раз в цикле while, только если она успешно читает строку, запускается следующая итерация цикла и выдается новое сообщение с подсказкой.
Источник изображения: Интерфейс программирования Linux от Michael KerrisK
Ответ 2
Вывод подтвердил, что когда bash выполняет внешнюю команду на переднем плане, он не обрабатывает никаких сигналов, полученных до тех пор, пока процесс на переднем плане не завершится.
bash
обрабатывает сигналы на уровне реализации C
/, но не будет запускать обработчики, установленные с trap
до тех пор, пока не завершится процесс переднего плана. Что требуется по стандарту:
When a signal for which a trap has been set is received while the shell is waiting for the completion of a utility executing a foreground command, the trap associated with that signal shall not be executed until after the foreground command has completed.
Обратите внимание, что стандарт не делает различий между встроенными и внешними командами; в случае read
типа "утилита", который не выполняется в отдельном процессе, неясно, происходит ли сигнал в его "контексте" или в контексте основной оболочки, и должен ли выполняться обработчик, заданный с помощью trap
до или после read
возвращается.
проблема 1: callback.sh содержит бесконечный цикл while, как объяснить, что он не обрабатывает сигналы, полученные до тех пор, пока процесс переднего плана не завершится. В этом случае процесс переднего плана никогда не завершится.
Это неправильно. bash
не выполняет while
цикл в отдельном процессе. Поскольку bash
процесса переднего плана не ожидает, он может немедленно запустить любой обработчик, установленный с trap
. Если read
были внешней командой, что и не в while
бы на переднем плане процесс.
Проблема 2: Нет, please input something for foo: shown
в termA;
зайдите в termB, запустите pkill -USR1 -f callback.sh
в третий раз.
Следующая информация снова отображается в termA: callback function called at Mon Nov 19 09:07:24 HKT 2018
Еще нет, please input something for foo:
показано в termA. Почему информация, please input something for foo:
заморозить?
Это не замерзает. Просто нажмите Enter, и он появится снова.
Это просто то
а) bash
возобновит read
предопределённого на месте, когда прерван сигналом - это не будет возвращаться и идти снова через в while
циклы
b) в этом случае он не будет повторно отображать подсказку, установленную с помощью -p
.
Обратите внимание, что bash
даже не отобразит приглашение снова в случае обработки SIGINT
с клавиатуры, даже если он отбрасывает строку, прочитанную так далеко от пользователя:
$ cat goo
trap 'echo INT' INT
echo $$
read -p 'enter something: ' var
echo "you entered '$var'"
$ bash goo
24035
enter something: foo<Ctrl-C>^CINT
<Ctrl-C>^CINT
<Ctrl-C>^CINT
bar<Enter>
you entered 'bar'
Это напечатает, что you entered 'foobar'
если сигнал был отправлен из другого окна с помощью kill -INT <pid>
вместо Ctrl-C из терминала.
Все материалы в этой последней части (как прервано read
и т.д.) Очень специфичны для bash
. В других оболочках, таких как ksh
или dash
, и даже в bash
при запуске в режиме POSIX (bash --posix
) любой обработанный сигнал фактически прервет встроенную bash --posix
read
. В приведенном выше примере оболочка выведет you entered ''
и выйдет после первого ^ C, и если read
вызывается из цикла, цикл будет перезапущен.
Ответ 3
termA не замерзает. Он просто отображает обратный вызов в поле ввода. Просто нажмите Enter
чтобы продолжить ввод.