company_banner

Кунг-фу стиля Linux: организация работы программ после выхода из системы

Автор оригинала: Al Williams
  • Перевод
Если вы пользуетесь Linux с ранних дней появления этой ОС (или если, вроде меня, начинали с Unix), то вам не надо очень быстро и в больших количествах изучать то новое, что появляется в системе по мере её развития и усложнения. Вы можете разбираться с новым постепенно, в режиме обычной работы. Но если вы только начинаете знакомство с Linux, то вам будет непросто сразу в ней разобраться, сразу понять её особенности. Среди тех, кому приходится изучать Linux с нуля, те, кто пользуется Raspberry Pi, те, кого расстроило то, что Microsoft забросила Windows XP, те, кто развернул облачную среду для своего IoT-проекта, похожего на Skynet.

Недавно сын спросил меня о том, как сделать так, чтобы что-то работало бы на Linux-компьютере даже тогда, когда осуществлён выход из системы. Я подумал, что это — хороший вопрос, и что на него, в зависимости от того, о чём именно идёт речь, может и не быть простого ответа.



Есть четыре ситуации, которые можно рассмотреть, отвечая на этот вопрос:

  1. Нужно запустить некую программу, выполнение которой, как заранее известно, займёт много времени.
  2. Была запущена некая программа, а потом стало понятно, что её выполнение займёт много времени. После этого решено было выйти из системы, не прерывая работу этой программы.
  3. Нужно написать скрипт или другую программу, которая может отключиться от терминала и работать сама по себе (такие программы называют демонами).
  4. Нужно, чтобы некая программа работала бы всё время, даже сразу после перезагрузки системы, когда в неё ещё не входили.

Одна из особенностей Linux-подобных систем, усложняющих работу с ними, заключается в том, что они дают пользователю множество возможностей. В результате то, что работает в одной системе, в другой может и не работать. Если же говорить об одном и том же дистрибутиве, то тут шанс нахождения более или менее универсальных методик работы повышается. Я, чтобы не отвлекаться, собираюсь тут рассказать, в основном, о первых двух пунктах вышеприведённого списка. Я, кроме того, исхожу из предположения о том, что мы говорим о программах, работающих в режиме командной строки. Если вам нужно, чтобы после выхода из системы работали бы программы с графическим интерфейсом, то по этому поводу у меня возникает много вопросов. Это, определённо, возможно, но это странное желание, так как графическое окружение пользователя исчезнет после выхода из системы (правда, для создания постоянных рабочих столов можно воспользоваться VNC или Nx).

Я сосредоточусь на первых двух пунктах списка, но дам некоторые подсказки касательно двух других вариантов. Итак, программа, которая сама отключается от терминала, это — демон. Этот механизм можно создать самостоятельно, а можно и у кого-нибудь позаимствовать. Сделать так, чтобы программа выполнялась бы всё время, может быть сложно, а может быть и не очень сложно. В большинстве Linux-дистрибутивов есть файл /etc/rc.local, который запускается с root-правами при запуске системы (как минимум — при нормальном запуске). Сюда можно добавлять команды для автозапуска каких-нибудь программ. Если же требуется, например, создавать собственные сервисы, то тут нужно учитывать особенности системы, знать о том, что именно в ней используется — SystemV, Upstart, OpenRC или Systemd. Возможно, придётся столкнуться и с чем-то другим. Но это — отдельная большая тема.

Обеспечение работы программ после выхода из системы


Вернёмся к первым двум пунктам нашего списка. Представьте, что вам надо запустить программу remote_backup, и вы знаете о том, что вы её запустите (возможно, войдя в систему по ssh), а потом отключитесь, но при этом она должна продолжать работать. Или вы, возможно, хотите обезопасить себя и сделать так, чтобы она продолжала бы работать даже в том случае, если вы случайно выйдете из системы. В любом случае, при обычном запуске программы из командной строки выход из системы означает остановку программы. Или не означает?

Для этого нужно просто запустить программу так, чтобы она не зависела бы от текущей сессии пользователя. Если вы используете bash и всё у вас настроено правильно, то сделать это можно очень просто. Если для запуска программы в фоновом режиме используется &, то работать она будет и после выхода из системы. То же самое касается и приостановки работающей программы (CTRL + Z) с последующим переводом её в фоновый режим с помощью команды bg. Тут мы видим очередной пример гибкости Linux, возможности решить одну и ту же задачу множеством способов.

Если вы работаете с bash, это значит, что вы можете настраивать оболочку, в том числе — параметр huponexit. Попробуйте выполнить следующую команду:

shopt | grep huponexit

Если окажется, что опция huponexit выключена, это значит, что обычный перевод программы в фоновый режим позволит ей пережить завершение вашей сессии. Конечно, если то же самое попробовать в какой-нибудь другой системе, это может не сработать и вам придётся выяснять причины такого поведения системы.

Если вам известно о том, что вы пользуетесь bash с выключенной опцией huponexit, это значит, что запустить программу в фоне вы можете, просто добавив после команды запуска знак &:

remote_backup &

Но надёжнее будет явным образом выразить намерение, касающееся работы программы после выхода из системы. Сделать так может понадобиться хотя бы из-за того, что при использовании только что описанного метода после выхода из системы нельзя будет видеть никаких выходных данных программы. Если же вы достаточно предусмотрительны, то программу вы можете запустить с использованием nohup:

nohup remote_backup

Если вы хотите передать nohup ещё и какие-то аргументы — перечислите их в конце команды, как это обычно делается при работе с другими программами. Nohup решает следующие задачи:

  1. Перенаправляет stderr в stdout.
  2. Перенаправляет stdout в nohup.out (местоположение этого файла в разных версиях nohup может различаться, в частности, он может находиться по адресу ~/nohup.out).
  3. Перенаправляет stdin в нечитаемый файл.
  4. Запускает программу и возвращает управление командной оболочке.

В итоге программа будет работать, но не сможет принимать никаких входных данных и при этом то, что она выводит, будет попадать в nohup.out. Если в nohup.out уже что-то есть, новые данные будут добавлены в конец файла.

Причина, по которой выполняются все эти перенаправления, заключается в том, что nohup обнаруживает то, что каждый из потоков подключён к терминалу. Если что-то уже перенаправлено в файл, nohup не будет это менять. То есть, например, можно поступить так:

nohup remote_backup >/tmp/backupstatus.log &

Или так:

nohup bash -c 'echo y | remote_backup >tmp/backupstatus.log' &

В последнем случае входные данные будут поступать из конвейера, поэтому nohup это менять не будет. А весь вывод программы (stdout и stderr) попадёт в /tmp/backupstatus.log.

Исследование nohup


Если вы хотите своими глазами увидеть то, как nohup воздействует на программы, войдите на Linux-сервер по ssh и выполните следующие команды:

echo '#!/bin/bash' >~/huptest
echo sleep 60 >>~/huptest
echo 'date >/tmp/test.txt' >>~/huptest
chmod +x ~/huptest
~/huptest

До тех пор, пока не истекут 60 секунд, нажмите клавишу клавиатуры, вводящую символ «тильда» (~). Затем нажмите клавишу, вводящую точку. В SSH тильда — это экранирующий символ (если он находится в начале строки). Воспользуйтесь командой ~? если хотите узнать подробности об этом. Теперь сделайте небольшой перерыв, выпейте чего-нибудь, и вернитесь к компьютеру через несколько минут. Войдите в систему. Файла /tmp/test.txt вы не найдёте (если только он уже не был создан, но и в таком случае его содержимое позволит сделать правильные выводы). Это говорит нам о том, что завершение сессии остановило программу, ожидающую истечения 60 секунд для продолжения работы.

А теперь попробуйте такую конструкцию:

~/huptest &

Она сообщит оболочке о том, что ей не надо ждать завершения программы. Если вы пользуетесь bash, то это будет работать в том случае, если опция huponexit выключена. Вы можете поэкспериментировать, включая (shopt -s huponexit) и выключая (shopt -u huponexit) эту опцию.

И, наконец, можно выполнить ту же команду с использованием nohup:

nohup ~/huptest &

Но даже команда nohup не идеальна. Если программа, которую мы выполняем, сама перехватывает флаг nohup, то команда nohup нам не поможет. Правда, большинство программ не перехватывают этот флаг и nohup будет работать независимо от используемой оболочки и от её настроек.

Есть множество других способов достижения того же результата. Например, можете исследовать возможности команды at, которая запускает другие программы в заданное время (скажем — через секунду после текущего момента). При запуске программ будет использоваться sh, а не bash (без выполнения некоторых действий), но они будут работать в автономном режиме.

Отсутствие планирования


Правда, существует пара ситуаций, в которых nohup нам не поможет. Первая — это когда не планировалось запускать программу в фоновом режиме. Как быть, если что-то было запущено, а потом стало ясно, что операция будет выполняться достаточно долго? Что делать, если понадобилось срочно куда-то отойти? Может случиться и так, что нужно запустить программу, вручную ввести в неё какие-то данные, а потом перевести в фоновый режим.

Возможно, вы знаете о том, что работающую программу можно приостановить, воспользовавшись комбинацией клавиш CTRL + Z. Bash сообщит, что создано задание, и даст его номер. Потом, используя команду bg, можно перевести это задание в фоновый режим. Если, например, заданию присвоен номер 3, то соответствующая команда будет выглядеть так:

bg %3

Если опция huponexit выключена, то больше ничего делать не надо. Но в общем случае нужно сообщить оболочке о том, что надо либо не отправлять заданию команды HUP, либо убрать его из таблицы заданий. Для решения обеих этих задач можно воспользоваться командой disown, встроенной в bash.

При использовании disown без опций будет осуществлено удаление именованного задания из таблицы заданий. Если вы не хотите заходить так далеко, воспользуйтесь опцией -h для того чтобы оболочка не передавала бы конкретному заданию сигнал HUP. Можно ещё воспользоваться опцией -a, позволяющей воздействовать на все задания, или опцией -r, которая позволяет обращаться ко всем работающим заданиям. Это — стандартная возможность bash (то есть — справку по ней можно найти в справке по bash), поэтому работать она будет не во всех командных оболочках. После того, как вы обработали фоновую задачу с помощью disown, вы можете спокойно выходить из системы.

Постоянные сессии


Как это обычно бывает в Linux, существует несколько способов решения одной и той же задачи. Для работы с сессиями можно воспользоваться screen или tmux. Работа с этими утилитами похожа на работу с VNC, когда можно выйти из системы, а потом, войдя в неё, обнаружить, что всё, с чем до этого работали, никуда не делось. Если вы решаете множество задач с использованием командной строки, то можете попробовать byobu (это даст вам приятный интерфейс для screen) или tmux.


Использование tmux

Если вы хотите это испытать — войдите в систему как обычно и запустите screen, tmux или byobu. Снова запустите тестовый скрипт (с & в конце команды или без использования этого символа). Потом воспользуйтесь вышеописанным фокусом ~. для «убийства» сессии. Далее, снова войдите в систему и перезапустите ту же программу (то есть — screen, tmux или byobu). Перед вами окажется экран, выглядящий таким же, каким он был в тот момент, когда вы вышли из системы. Это, в целом, довольно полезная возможность. Кроме того, она позволяет организовать работу с несколькими окнами, но в нашем случае это значения не имеет.

Проблема богатства возможностей


Мощь Linux отчасти основана на том, что у пользователей этой ОС есть множество способов решения одной и той же задачи. В этом же кроются и проблемы Linux. Причём, эти проблемы возникают не, например, тогда, когда нужно настроить миниатюрный компьютер Raspberry Pi, находящийся под полным контролем одного человека. Настоящие сложности появляются в ситуациях, когда нужно развернуть что-то на множестве компьютеров, за которыми работают разные пользователи, и на которых, возможно, даже установлены разные дистрибутивы.

Конечно, если стремиться к разработке универсальных решений, это — далеко не единственная проблема. Если нужно писать скрипты, которые смогут успешно работать в самых разных Posix-системах, можно почитать документацию GNU по autoconf. Там можно найти много полезного.

Как вы запускаете Linux-программы, которые должны работать после выхода из системы?



RUVDS.com
VDS/VPS-хостинг. Скидка 10% по коду HABR

Комментарии 14

    0

    В современных версиях linux (в ubuntu точно есть) фоновые команды по умолчанию автоматически перепривязываются к init процессу. Поэтому продолжают работу.


    alekciy@alekciy-home:$ ssh srv1
    root@srv1:~# lsb_release -a
    No LSB modules are available.
    Distributor ID: Ubuntu
    Description:    Ubuntu 16.04.6 LTS
    Release:    16.04
    Codename:   xenial
    root@srv1:~# for i in `seq 1 10`; do echo $i >> /tmp/txt.log ; sleep 1 ; done &
    [1] 22590
    root@srv1:~# logout
    Connection to srv1 closed.
    alekciy@alekciy-home:$ ssh srv1
    root@srv1:~# tail -n 1 /tmp/txt.log
    10
    root@srv1:~# 
      +2

      У нас тут сейчас 2020 год, и в нем современная версия Ubuntu это 20.04 focal. И вот в современных версиях все как раз наоборот: systemd при выходе пользователя из сессии прибивает все процессы, даже фоновые и запущенные всеми перечисленными способами:



      Одно время они даже прибивали tmux, и требовали исправить его так, чтобы он соответствовал требованиям systemd: https://news.ycombinator.com/item?id=11797075


      Сейчас, вроде бы, с tmux как-то все устаканилось. Но для запуска фонового процесса, который должен остаться работать после выхода из сессии, теперь есть только один работающий метод:


      systemd-run <ваш процесс>
        0

        У нас тут в 2020-ом 16LTS версия все еще поддерживаемая. И значит имеет право быть в эксплуатации. Под рукой с ходу есть еще только Ubuntu 18LTS в которой история ровно такая же.
        В целом я просто помню, что как раз поведение по умолчанию это завершение фонового процесса при разрыве связи. И много разрабов на этом погорели думая что процесс продолжает работу. И уже после фейла узнавали screen и зачем он. И это поведение относительно недавно изменилось.


        P.S. А есть под рукой сервер с 20-ой? Что бы запустить приведенный мною пример.

        0
        А что со screen, disown и nohup?

        Вообще не очень понятно зачем прибивать фоновые процессы.
        Ведь по идее им должен просто sighup отправляться, но если у процесса родитель уже перекинут на init, то и не отправится.
          0

          Тоже не работали какое-то время.


          Пишут, что однажды что-то наворотили в d-bus и systemd, так что в гноме при выходе процессы перестали закрываться.


          "In the normal case it makes a lot of sense to kill remaining processes. The exception is stuff like screen and tmux. But it's easy enough to work around, a simple example was added to the man page in previous commit. In the long run those services should integrate with the systemd users session on their own."

            0
            Как по мне, то совершенно бредово.

            nohup command &
            Процесс отвязан от родителя, потоки перенаправлены, как вообще линукс может понять что этот процесс должен быть завершен при закрытии текущей сесиии? По UID пользователя закрывать ВСЕ его процессы?

            Или это что, никто не может просто взять и написать свой оконный менеджер (это же просто прикладное приложение), без того, чтобы коммитить в ядро?
          0
          loginctl enable-linger
          ?
            +1

            Специально только что проверил еще раз на 18.04: как не работало, так и не работает:


            • в /etc/systemd/logind.conf включил KillUserProcesses=yes (как по умолчанию в systemd upstream), перестартовал systemd-logind.service, перезашел,
            • включил sudo loginctl enable-linger <мой account>,
            • проверил пример от alekciy — процесс убило сразу при выходе

            Если вернуть "#KillUserProcesses=no", как по умолчанию в Ubuntu, то фоновый процесс опять остается, даже с выключенным linger.

        0
        tmux и нет проблем )
          0
          Почитайте комментарии к статье
            0
            зачем?
              +1
              чтобы понять, что tmux/screen больше не являются безоговорочным решением
          +1
          В большинстве Linux-дистрибутивов есть файл /etc/rc.local
          только для совместимости (главным образом, с привычками)

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое