Ответ 1
Кто должен убивать задания?
Обычно задания переднего и заднего плана убиваются SIGHUP
, отправленные ядром или оболочкой в разных обстоятельствах.
Когда ядро отправляет SIGHUP
?
Ядро отправляет SIGHUP
в процесс управления:
- для реального (аппаратного) терминала: при обнаружении разъединения в драйвере терминала, например. при зависании на модемной линии;
- для pseudoterminal (pty): когда последний дескриптор, ссылающийся на основную сторону pty, закрыт, например. когда вы закрываете окно терминала.
Ядро отправляет SIGHUP
в другие группы процессов:
- в группу процессов переднего плана, когда процесс управления завершается;
- to осиротевшая группа процессов, когда она становится сиротой и она остановила участников.
Процесс управления - это лидер сеанса, который установил соединение с управляющим терминалом.
Как правило, процесс управления - это ваша оболочка. Итак, подведем итог:
- Ядро
- отправляет
SIGHUP
в оболочку, когда реальный или псевдотерминал отключен/закрыт; Ядро - отправляет
SIGHUP
в группу процессов переднего плана, когда оболочка завершается; Ядро - отправляет
SIGHUP
в сиротскую группу процессов, если она содержит остановленные процессы.
Обратите внимание, что ядро не отправляет SIGHUP
в фоновый процесс, если оно не содержит остановленных процессов.
Когда bash
отправляет SIGHUP
?
Bash отправляет SIGHUP
ко всем заданиям (передним и задним):
- когда он получает
SIGHUP
, и это интерактивная оболочка (а поддержка управления заданиями включена во время компиляции); - когда он завершается, это интерактивная оболочка для входа, и опция
huponexit
установлена (и поддержка управления заданиями включена во время компиляции).
Подробнее здесь.
Примечания:
-
bash
не отправляетSIGHUP
в задания, удаленные из списка заданий, используяdisown
; - процессы начали использовать
nohup
игнорироватьSIGHUP
.
Подробнее здесь.
Как насчет других оболочек?
Обычно оболочки распространяют SIGHUP
. Генерация SIGHUP
при нормальном выходе менее распространена.
Telnet или SSH
В telnet или SSH при закрытии соединения (например, при закрытии окна telnet
на ПК) должно произойти следующее:
- клиент убит;
- сервер обнаруживает закрытие клиентского соединения;
- сервер закрывает основную сторону pty; Ядро
- обнаруживает, что master pty закрыт и отправляет
SIGHUP
вbash
; -
bash
получаетSIGHUP
, отправляетSIGHUP
на все задания и завершает работу; - каждое задание получает
SIGHUP
и завершается.
Проблема
Я могу воспроизвести вашу проблему, используя bash
и telnetd
из busybox
или dropbear
SSH-сервера: иногда фоновое задание не получает SIGHUP
(и не завершается) при закрытии клиентского соединения.
Кажется, что условие гонки происходит, когда сервер (telnetd
или dropbear
) закрывает главную сторону pty:
- обычно
bash
получаетSIGHUP
и сразу же убивает фоновые задания (как и ожидалось) и завершает; - но иногда
bash
обнаруживаетEOF
на подчиненной стороне pty перед обработкойSIGHUP
.
Когда bash
обнаруживает EOF
, он по умолчанию немедленно прекращается без отправки SIGHUP
. И фоновое задание остается запущенным!
Решение
Можно настроить bash
на отправку SIGHUP
на обычный выход (включая EOF
):
-
Убедитесь, что
bash
запущен как оболочка входа.huponexit
работает только для систем входа в систему AFAIK.Локальная оболочка включена опцией
-l
или дефис вargv[0]
. Вы можете настроитьtelnetd
на запуск/bin/bash -l
или лучше/bin/login
, который вызывает/bin/sh
в режиме оболочки входа.например:.
telnetd -l /bin/login
-
Включить опцию
huponexit
.например:.
shopt -s huponexit
Введите это в сеансе
bash
каждый раз или добавьте его в.bashrc
или/etc/profile
.
Почему происходит гонка?
bash
разблокирует сигналы только тогда, когда они безопасны, и блокирует их, когда некоторая часть кода не может быть безопасно прервана обработчиком сигнала.
Такие критические секции время от времени вызывают точки прерывания, и если сигнал принимается при выполнении критической секции, этот обработчик задерживается до тех пор, пока не произойдет следующая точка прерывания или не будет выведен критический раздел.
Вы можете начать копать из quit.h
в исходном коде.
Таким образом, кажется, что в нашем случае bash
иногда получает SIGHUP
, когда он находится в критическом разделе. SIGHUP
выполнение обработчика задерживается, а bash
читает EOF
и заканчивается перед выходом из критического раздела или вызовом следующей точки прерывания.
Ссылка
- "Управление заданиями" в официальном руководстве Glibc.
- Глава 34 "Группы процессов, сеансы и управление заданиями" в книге "Интерфейс программирования Linux".