Terminal FG process group: 1204
Process 1228 (1) received signal 18 (Continued)
$ kill %1 %2
[1]- Terminated./job_mon |./job_mon
[2]+ Terminated./job_mon |./job_mon |./job_mon
34.7.3. Обрабатываем сигналы, связанные с управлением заданиями
Поскольку в большинстве приложений процедура управления заданиями является прозрачной, для работы с сигналами, которые с ней связаны, не нужно предпринимать дополнительных действий. Исключение составляют программы, которые работают с экраном, — например, vi или less. Они отвечают за точное позиционирование текста в терминале, изменяя различные его параметры, в том числе параметр, который позволяет терминалу считывать отдельные символы (вместо целых строк). Этот и другие параметры будут описаны в главе 58.
Программы, работающие с экраном, должны обрабатывать сигнал остановки, отправляемый терминалом (SIGTSTP). Обработчик должен переключить терминал в исходный режим ввода (построчный) и поместить курсор в нижний левый угол. При возобновлении работы программа снова переключает терминал в нужный ей режим, проверяет размер окна терминала (который за это время мог измениться) и заново перерисовывает содержимое экрана.
Если приостановить или завершить программу, работающую с терминалом (такую как vi в xterm или другом эмуляторе терминала), терминал, как правило, перерисовывается с помощью текста, который был видим до запуска этой программы. Это достигается за счет перехватывания двух символьных последовательностей, которые программа, использующая пакеты terminfo или termcap, обязана вывести при получении и возврате контроля над содержимым экрана. Первая из этих последовательностей, которую называют smcup (обычно это клавиша Esc в сочетании с символами [?1049h), заставляет эмулятор терминала переключиться на «альтернативный» экран. Вторая последовательность, rmcup (обычно это клавиша Esc в сочетании с символами [?1049l), переключает терминал обратно на стандартный экран, что приводит к возвращению текста, который отображался до того, как программа захватила контроль над терминалом.
При обработке сигнала SIGTSTP нужно учитывать определенные тонкости. Впервые это отмечалось в подразделе 34.7.2: если сигнал SIGTSTP перехвачен, он не выполняет своего стандартного действия — то есть не останавливает процесс. В листинге 34.5 мы решали эту проблему, генерируя в обработчике SIGTSTP сигнал SIGSTOP. SIGSTOP нельзя перехватить, заблокировать или проигнорировать, и он гарантированно приводит к немедленной остановке процесса. Однако такой подход является не совсем корректным. В подразделе 26.1.3 мы видели, что родительский процесс может использовать статус ожидания, возвращенный вызовами wait() или waitpid(), чтобы определить, какой сигнал вызвал остановку его потомка. Если сгенерировать сигнал SIGSTOP в обработчике SIGTSTP, родитель (ошибочно) посчитает, что причиной остановки потомка стал именно этот сигнал.
Правильный подход в этой ситуации — сгенерировать в обработчике SIGTSTP еще один сигнал SIGTSTP, который и остановит процесс. Это делается следующим образом.
1. Обработчик сбрасывает действие сигнала SIGTSTP к значению по умолчанию (SIG_DFL).
2. Обработчик генерирует SIGTSTP.
3. Поскольку сигнал SIGTSTP был заблокирован на входе в обработчик (если только не был указан флаг SA_NODEFER), тот его разблокирует. После этого ожидающий сигнал SIGTSTP, сгенерированный в предыдущем шаге, выполняет свое стандартное действие: немедленно приостанавливает работу процесса.
4. Позже, при получении сигнала SIGCONT, процесс возобновит свою работу. На этом этапе выполнение обработчика продолжается.
5. Перед началом работы обработчик заново блокирует сигнал SIGTSTP и готовится перехватить следующий его экземпляр.
Повторное блокирование сигнала SIGTSTP нужно для того, чтобы избежать рекурсивного вызова обработчика, если после его установки (но до возвращения) был доставлен еще один экземпляр SIGTSTP. Как отмечалось в разделе 22.7, рекурсивные вызовы обработчиков сигналов могут привести к переполнению стека, если сигналы доставляются слишком быстро. Для обхода этой проблемы также можно прибегнуть к блокированию сигнала, если между установкой и возвращением обработчику больше не нужно выполнять никаких других действий (например, сохранение или восстановление значений из глобальных переменных).
Пример программы
Обработчик из листинга 34.6 обеспечивает корректное обращение с сигналом SIGTSTP, выполняя шаги, описанные выше (еще один пример обработки этого сигнала будет представлен в листинге 58.4). Установив обработчик SIGTSTP, главная функция этой программы входит в цикл и ждет появления сигнала. Пример того, что можно увидеть при запуске данного кода, показан ниже:
$ ./handling_SIGTSTP