Демон

Демон — фоновый процесс операционной системы, не обладающий даже интерфейсом для работы с участием пользователя (человека). То же: даймон, daemon, служба, service.

В линуксе названия таких процессов часто заканчивается на букву -d, чтобы подчеркнуть суть: sshd, httpd, crond, epmd и др.

Хотя первоначально программистов на такое название вдохновил “демон Максвелла” (Maxwell’s demon), впоследствии в англоязычной литературе возникла тяга к другой культурной отсылке — дохристианским демонам из платонизма (daemon, произносится через «а» и «э краткое»). Эти демоны-духи считались хорошими или нейтральными к человеку и были посредниками между ним и богами).

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

Другими словами, демоны работают автономно. Это проявляется в т.ч. в том, что при включении компьютера демоны обычно запускаются автоматически, а при выключении — останавливаются. И запуск, и останов демона должны пройти, конечно, корректно: важные данные должны сохранить целостность, валидность, непротиворечивость.

В линуксе на современном этапе для автоматического запуска и останова демонов обычно используют systemd — системный демон, который имеет идентификатор процесса под номером 1 и управляет другими демонами.

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

Как создать демона из Эрланг-программы?

Для начала надо иметь эту самую программу, а точнее приложение. Когда systemd пошлёт нашему демону сигнал завершения работы, BEAM автоматически запустит init:stop(). Каждое запущенное приложение (и только они, не отдельные акторы) получит сигнал о завершении с помощью application:stop(App).

Придётся создать минимальное приложение, которое:

Пусть у нас приложение называется pril, и в нём будет работать единственный модуль proba. В нём должна быть запускающая функция start/2. Сначала создадим минимальный конфигурационный файл приложения pril.app.

{application, pril, [
    {mod, {'proba', []}}
]}.

Затем модуль proba:

-module(proba).
-export([start/2, init/0]).

start(_,_) ->
    io:format("Приложение запустилось.~n"),
    Pid = spawn_link(?MODULE, init, []),
    register(?MODULE, Pid),
    {ok,Pid}. % Именно так должно заканчиваться, чтобы BEAM знала, кому посылать сигналы.

init() ->
    io:format("Инициализация актора.~n"),
    process_flag(trap_exit, true),
    loop(),
    io:format("Приложение остановилось. До новых встреч.~n").

loop() ->
    receive
        Some ->
            io:format("Получено сообщение: ~p~n", [Some])
    after 60_000 ->
              io:format("Прошла ещё одна минута.~n"),
              loop()
    end.

Перед тем, как делать из приложения демона, надо, конечно, сначала хорошо всё протестировать в интерактивном режиме. Программа должна выполнять заложенный в неё полезный функционал и корректно завершаться. В нашем случае под корректным завершением будем понимать то, что приложение сообщает после получения сигнала останова, что оно остановилось. Для краткости я сделал, что оно будет останавливаться после получения любого сообщения на почтовый ящик, а не только {'EXIT',Pid,shutdown}.

А такое сообщение будет получено — благодаря тому, что мы установили линк на созданный актор и что внутри актора установили флаг trap_exit.

Пробуем в интерактивном режиме:

$ erl -eval "application:ensure_all_started(pril)"
Приложение запустилось.
Инициализация актора.
Прошла ещё одна минута.
1> q().
ok
Получено сообщение: {'EXIT',<0.84.0>,shutdown}
Приложение остановилось. До новых встреч.
$ 

Обратите внимание, что нам пришлось сделать приложение, но мы обошлись без надзора. Мы можем на своё усмотрение обработать сигнал {'EXIT',Pid,shutdown}.

Теперь осталось дело за малым — зарегистрировать наше приложение как демона. Для этого придётся создать юнит-файл /etc/systemd/system/pril.service с примерно таким содержимым:

[Unit]
Description=Мой первый демон на Эрланге
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/erl -noshell -name noda@a.s -eval "application:ensure_all_started(pril)"
Restart=on-failure
User=alex

[Install]
WantedBy=multi-user.target

В опции After указали, что демон должен запуститься после разворачивания сети. on-failure означает, что при каждом падении демона из-за внутренней ошибки он будет запущен снова. Если демон завершён штатно, он не будет запущен снова.

Имеет смысл указать пользователя не root: это не даст демону слишком много привилегий, а также позволит прочитать наш секретный файл .erlang.cookie, находящийся в домашнем каталоге. Последняя строка нужна для того, чтобы демон всегда запускался.

После создания юнит-файла надо сделать, чтобы systemd увидел нового демона:

systemctl daemon-reload

Чтобы демон запускался при запуске операционной системы, выполняем:

systemctl enable pril.service

И запускаем прямо сейчас:

systemctl start pril.service

Чтобы увидеть текущее состояние нашего демона, можно использовать команду:

systemctl status pril.service

Подождём несколько минут, остановим демона и проверим статус.

systemctl stop pril.service
systemctl status pril.service

Помимо прочего там будут сообщения, выводимые нашей программы с помощью io:format. Так мы можем следить за работой приложения.

окт 04 17:23:24 a.s erl[105791]: Приложение запустилось.
окт 04 17:23:24 a.s erl[105791]: Инициализация актора.
окт 04 17:24:24 a.s erl[105791]: Прошла ещё одна минута.
окт 04 17:25:24 a.s erl[105791]: Прошла ещё одна минута.
окт 04 17:26:24 a.s erl[105791]: Прошла ещё одна минута.
окт 04 17:26:31 a.s erl[105791]: =INFO REPORT==== 4-Oct-2026::17:26:31.903369 ===
окт 04 17:26:31 a.s erl[105791]: SIGTERM received - shutting down
окт 04 17:26:31 a.s erl[105791]: Получено сообщение: {'EXIT',<0.88.0>,shutdown}
окт 04 17:26:31 a.s erl[105791]: Приложение остановилось. До новых встреч.

Вот мы и создали нашего первого демона!

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

systemctl daemon-reload

Чтобы полностью удалить демона, выполняем:

$ systemctl stop pril.service 
$ systemctl disable pril.service 
$ rm /etc/systemd/system/pril.service 
$ systemctl daemon-reload 
$ systemctl reset-failed

Последняя команда очищает память от всякого возможного мусора.

Демоны и распределённая система

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

Красивым и правильным является делать максимально изнутри Эрланг-системы, а не со стороны железа. Запуском другого компьютера дистанционно мы не можем управлять из Эрланг-системы (разве что в специальных случаях). Если мы не создадим демона, нам придётся не только “грубо” запускать или перезагружать компьютер, но и вручную запускать ноду. Поэтому создавать демонов — совершенно правильно.

Благодаря им нода будет доступна всегда, когда включён компьютер с ней. Если случится внутренняя ошибка, нода-демон будет восстановлен с помощью systemd. Если вдруг возникнет потребность перезагрузить ноду, лучше это сделать, послав с помощью rpc init:restart().

Остановить удалённую ноду можно аналогично — послав init:stop(). systemd в этом случае не будет восстанавливать демона, потому что он завершит работу штатно. В этом случае, надо заметить, мы потеряем доступ изнутри Эрланг-системы к этой ноде. Придётся или как-то перезагрузить другой компьютер, или, например, с помощью ssh снова запустить демона.

Продумывать Систему надо так, чтобы она максимум времени сохраняла целостность, и так, чтобы в случае непредвиденных ситуаций совершать минимум действий для восстановления целостности.


Copyright © 2026 Алексей Карманов