Почему выполнение фоновой задачи по ssh завершается неудачей, если выделяется псевдо-tty?
недавно я столкнулся с некоторым немного странным поведением при выполнении команд по ssh. Мне было бы интересно услышать какие-либо объяснения поведения ниже.
под управлением ssh localhost 'touch foobar &'
создает файл с именем foobar
как и ожидалось:
[bob@server ~]$ ssh localhost 'touch foobar &'
[bob@server ~]$ ls foobar
foobar
однако выполняется та же команда, но с -t
возможность принудительного выделения псевдо-tty не удается создать foobar
:
[bob@server ~]$ ssh -t localhost 'touch foobar &'
Connection to localhost closed.
[bob@server ~]$ echo $?
0
[bob@server ~]$ ls foobar
ls: cannot access foobar: No such file or directory
моя текущая теория заключается в том, что, поскольку процесс касания псевдо-tty-это распределенного и нераспределенного перед процессом есть шанс бежать. Конечно, добавление одного второго сна позволяет touch работать так, как ожидалось:
[bob@pidora ~]$ ssh -t localhost 'touch foobar & sleep 1'
Connection to localhost closed.
[bob@pidora ~]$ ls foobar
foobar
если у кого-то есть окончательное объяснение, мне было бы очень интересно его услышать. Спасибо.
2 ответов
О, это хорошо.
это связано с тем, как работают группы процессов, как bash ведет себя при вызове в качестве неинтерактивной оболочки с -c
, и эффект &
в ввода команд.
ответ предполагает, что вы знакомы с тем, как работает управление заданиями в UNIX; если нет, вот представление высокого уровня: каждый процесс принадлежит к группе процессов (процессы в той же группе часто помещаются туда как часть командного конвейера, например cat file | sort | grep 'word'
будет место запущенные процессы cat(1)
, sort(1)
и grep(1)
в той же группе процессов). bash
это процесс, как и любой другой, и он также принадлежит к группе процессов. Группы процессов являются частью сеанса (сеанс состоит из одной или нескольких групп процессов). В сеансе существует не более одной группы процессов, называемой группой процессов переднего плана, и, возможно, много фоновых групп процессов. Группа процессов переднего плана управляет терминалом (если к терминалу присоединен управляющий терминал session); руководитель сеанса (bash) перемещает процессы с фона на передний план и с переднего плана на фон с помощью tcsetpgrp(3)
. Сигнал, отправленный в группу процессов, доставляется каждому процессу в этой группе.
если концепция групп процессов и управления работой для вас совершенно новая, я думаю, вам нужно будет прочитать об этом, чтобы полностью понять этот ответ. Большой ресурс, чтобы узнать это Глава 9 расширенное программирование в среде UNIX (3-й издание.)
это, как говорится, давайте посмотрим, что происходит здесь. Мы должны собрать воедино каждый кусочек головоломки.
в обоих случаях удаленная сторона ssh вызывает bash(1)
С -c
. The -c
флаг причин bash(1)
для запуска в качестве неинтерактивной оболочки. Из manpage:
интерактивная оболочка началось без аргументами и без опции-c, стандартный ввод и ошибка которой являются обоими подключить к клеммам (а определяется isatty(3)), или один запущен с опцией-i. PS1 установлен и $ - включает i, Если bash интерактивный, позволяющий скрипту оболочки или файлу запуска проверить это государство.
кроме того, важно знать, что управление заданиями отключается при запуске bash в неинтерактивном режиме. Это означает, что bash не будет создавать отдельную группу процессов для выполнения команды, так как управление заданиями отключено, не будет необходимости перемещать эту команду между передний план и фон, поэтому он может просто оставаться в той же группе процессов, что и bash. Это произойдет независимо от того, принудительно ли вы выделили Pty на ssh с -t
.
однако использование &
имеет побочный эффект, заставляющий оболочку не ждать завершения команды (даже если управление заданием отключено). Из manpage:
если команда завершается оператором управления&, оболочка выполняет команду в фоновом режиме в подоболочка. Панцирь не ждите завершения команды, и состояние возврата равно 0. Команды, разделенные a; выполняются последовательно; оболочка ожидает для каждой команды, чтобы завершить в свою очередь. Статус возврата-выход состояние последней выполненной команды.
таким образом, в обоих случаях bash не будет ждать выполнения команды, и touch(1)
будет выполняться в той же группе процессов, что и bash(1)
.
Теперь рассмотрим, что происходит, когда сессия лидер уходит. Цитирую setpgid(2)
manpage:
если сеанс имеет управляющий терминал и флаг CLOCAL для этого терминал не установлен, и происходит зависание терминала, затем сеанс лидер посылается сигнал SIGHUP. если руководитель сеанса выходит, то вздох сигнал также будет отправлен каждому процессу в процессе переднего плана группа управляющего терминала.
(выделено мной)
когда вы не использовать -t
когда вы не используете -t
, на удаленной стороне нет выделения PTY, поэтому bash не является лидером сеанса и фактически не создается новый сеанс. Поскольку sshd работает как демон, процесс bash, который разветвлен + exec () ' d, не будет иметь управляющего терминала. Таким образом, хотя оболочка завершается очень быстро (возможно, раньше touch(1)
), нет SIGHUP
отправлено в группу процессов, потому что bash не был лидером сеанса (и нет управляющий терминал.) Так что все работает.
при использовании -t
-t
принудительное распределение PTY, что означает, что удаленная сторона ssh вызовет setsid(2)
, выделить псевдо-терминал + вилка новый процесс с forkpty(3)
, подключите вход и выход главного устройства PTY к конечным точкам сокета, которые ведут к вашей машине, и, наконец, выполните bash(1)
. forkpty(3)
открывает ведомую сторону PTY в раздвоенном процессе, который станет bash; так как нет управляющего терминала для текущего сеанса, и открывается терминальное устройство, устройство PTY становится управляющим терминалом для сеанса, а bash становится лидером сеанса.
то же самое происходит снова: touch(1)
выполнена в той же группе процессов, и т. д. да, да. Дело в том,это время is руководитель сеанса и управляющий терминал. Итак, поскольку bash не беспокоит ожидание из-за &
, когда он выйдет,SIGHUP
поставляется в группу процессов и touch(1)
умирает преждевременно.
о nohup
nohup(1)
здесь не работает, потому что есть еще гонки. Если bash(1)
завершается перед nohup(1)
имеет возможность настроить необходимую обработку сигналов и перенаправление файлов, это не будет иметь никакого эффекта (что, вероятно, и происходит)
возможным исправление
сильн повторное включение управления заданиями исправляет это. В bash, вы, что с set -m
. Это работает:
ssh -t localhost 'set -m ; touch foobar &'
или заставить Баш ждать touch(1)
в комплекте:
ssh -t localhost 'touch foobar & wait `pgrep touch`'
ключ отделяет потоки stdin / stdout/stderr дочернего процесса от исходного сеанса bash/ssh; затем псевдо-распределение tty (ssh -t
) больше не требуется, чтобы позволить ребенку пережить прекращение ssh-соединения. См.здесь для полного ответа...