Ловушка: когда обрабатывается сигнал и почему некоторая информация зависает?
Откройте терминал с именем "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; это никогда не показывается в 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:
показано в терм.
Почему информация please input something for foo:
заморозить?
3 ответа
Прежде чем объяснить вашу проблему, немного контекста о том, как 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-\
действительно ли программа выходила. В зависимости от вашего 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
Вывод подтвердил, что когда 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:
показано в терм. Почему информацияplease input something for foo:
заморозить?
Это не замерзает. Просто нажмите Enter, и он появится снова.
Это просто
а) bash
перезапустит read
встроенный на месте, когда прерван сигналом - он не вернется и не пройдет снова через while
петля
б) он не будет повторно отображать подсказку, установленную с -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
), любой обработанный сигнал фактически прервет read
встроенный. В приведенном выше примере оболочка напечатает you entered ''
и выйти после первого ^C, и если read
вызывается из цикла, цикл будет перезапущен.
Терма не замерзает. Это просто отображение обратного вызова в поле ввода. Просто нажмите Enter
чтобы продолжить ввод.