Как создать демон в Linux
Демон — это фоновый процесс, не связанный с управляющим терминалом. В статье показано, как вручную превратить процесс в демон: fork, setsid, смена рабочей директории, закрытие дескрипторов и перенаправление stdin/stdout/stderr. Также рассмотрены альтернативы (systemd, supervisord), чеклисты, отладка и требования приёмки.

Краткое введение в создание демонов
Демоны — это процессы, которые не работают под прямым контролем интерактивного пользователя и выполняют фоновые задачи. Обычно они запускаются при старте системы и работают непрерывно до её выключения. Главное практическое отличие демонов от обычных процессов — они не взаимодействуют с консолью или управляющим терминалом.
Типичные демоны в Linux:
- crond — запускает задачи по расписанию.
- sshd — принимает удалённые подключения по SSH.
- httpd / apache — обслуживает веб-запросы.
- nfsd — обеспечивает файловый обмен по сети.
По исторической традиции имена демонов часто оканчиваются на букву «d». Это удобно, но не обязательно.
Короткая дефиниция: демон — фоновый процесс без управляющего терминала, обычно инициируемый системой и управляемый менеджером сервисов.
Почему важна правильная реализация демона
Неправильно реализованный демон может:
- держать файловые дескрипторы открытыми, мешая отмонтированию дисков;
- конфликтовать с управляющим терминалом и вызывать неожиданные выводы в консоль;
- быть уязвим для сигналов, оставляя ресурсы в некорректном состоянии;
- неправильно логировать сообщения, направляя их в /dev/null.
Поэтому существуют отработанные шаги для безопасного превращения процесса в демон. Ниже мы переведём и разберём эти шаги на примерах кода.
Сессии демонов и управление группами процессов
После входа в систему через терминал, оболочка создаёт сессию и группирует процессы в группы процессов. Сессия — это набор групп процессов. Управляющий терминал (controlling terminal) может быть связан только с одной сессией. При демонстрации состояния процесса через ps вы видите столбцы PID, PPID, PGID, SID, TTY, STAT.
Ключевые понятия:
- PID — идентификатор процесса.
- PPID — идентификатор родительского процесса.
- PGID — идентификатор группы процессов; первый процесс в группе — лидер группы.
- SID — идентификатор сессии; лидер сессии — процесс с SID равным его PID.
- TTY — управляющий терминал процесса; если значение — ?, значит нет привязки к терминалу.
- STAT — состояние процесса (S — спящий, T — остановлен, s — лидер сессии, + — в foreground process group).
Эти поля помогают понять, как процесс был отделён от управления терминалом и стал демоном.
Пример: минимальная функция _daemon
Ниже приведён иллюстративный пример небольшого приложения test.c и вспомогательной функции в daemon.c, которые показывают базовые шаги превращения процесса в демон.
//test.c
#include
int _daemon(int, int);
int main()
{
getchar();
_daemon(0, 0);
getchar();
return 0;
} //daemon.c (фрагмент)
#include
#include
#include
#include
#include
#include
#include
#include
int _daemon(int nochdir, int noclose) {
pid_t pid;
pid = fork(); // Fork off the parent process
if (pid < 0) {
exit(EXIT_FAILURE);
}
if (pid > 0) {
exit(EXIT_SUCCESS);
}
return 0;
} Объяснение первых шагов:
- fork() создаёт дочерний процесс. Родитель завершается (exit), а дочерний продолжается. Это даёт новому процессу родителя init (или systemd), так как родитель был завершён.
- После fork() рекомендуется вызывать setsid(), чтобы создать новую сессию и отделиться от управляющего терминала.
- Часто делают ещё второй fork(), чтобы предотвратить возможность вновь получить управляющий терминал (двойной fork — классическая техника демонизации).
Последовательность стандартных шагов для демонизации
- Выполнить начальную настройку и проверить конфигурационные файлы (чтобы ошибки сообщить в консоль до демонизации).
- fork() и завершить родителя.
- вызвать setsid() — новая сессия и отсутствие управляющего терминала.
- (опционально) второй fork() — чтобы гарантировать, что процесс не может вновь стать лидером сессии.
- chdir(“/“) — сменить рабочую директорию на корень, чтобы не мешать отмонтированию.
- Закрыть все открытые файловые дескрипторы, перенаправить stdin/stdout/stderr в /dev/null или в файлы логов.
- Настроить обработчики сигналов и запуск основной петли.
Компиляция и наблюдение за процессом
Скомпилируйте пример так:
gcc -o test test.c daemon.cЗапустите в одном терминале:
./testВ другом терминале посмотрите состояние процесса:
ps -C test -o "pid ppid pgid sid tty stat command"
# Пример вывода
# PID PPID PGID SID TT STAT COMMAND
# 10296 5119 10296 5117 pts/2 S+ ./testСимвол + в STAT означает, что процесс в группе foreground. После вызова _daemon вы увидите отсутствие + и PPID равный 1 (systemd или init):
ps -C test -o "pid ppid pgid sid tty stat command"
# Пример вывода после _daemon
# PID PPID PGID SID TT STAT COMMAND
# 22504 1 22481 5117 pts/2 S ./testЗатем после setsid() и chdir(“/“) TTY станет ? и SID == PID:
# PID PPID PGID SID TT STAT COMMAND
# 25494 1 25494 25494 ? Ss ./testПолная реализация простого демона
Ниже — сокращённый и понятный пример окончательной версии функции демонизации.
#include
#include
#include
#include
#include
#include
#include
#include
#include
int _daemon(void) {
pid_t pid, sid;
pid = fork(); // Fork off the parent process
if (pid < 0) {
exit(EXIT_FAILURE);
}
if (pid > 0) {
exit(EXIT_SUCCESS);
}
// Create a SID for the child process
sid = setsid();
if (sid < 0) {
// FAIL
exit(EXIT_FAILURE);
}
if ((chdir("/")) < 0) {
// FAIL
exit(EXIT_FAILURE);
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
while (1) {
// Some Tasks
sleep(30);
}
exit(EXIT_SUCCESS);
} Комментарии к коду:
- Использование syslog для логирования вместо printf — стандартная практика для демонов.
- Закрытие первых трёх дескрипторов и перенаправление на /dev/null защищает от непреднамеренного вывода в консоль.
- Бесконечный цикл — заглушка; в реальной программе нужно правильно обрабатывать сигналы и корректно завершаться.
Альтернатива ручной демонизации: systemd, daemon() и другие
В современных дистрибутивах ручная демонизация часто не рекомендуется для новых сервисов. Вместо этого:
- systemd — предпочтительный менеджер сервисов: пишете unit-файл, systemd управляет форками, перезапусками и логированием.
- daemon(3) — библиотечная функция, которая выполняет базовые шаги демонизации; используется в старых проектах.
- supervisord, runit, s6 — альтернативные менеджеры процессов для контейнеров и простых развёртываний.
Пример использования daemon() в коде sshd (фрагмент):
...
if (!(debug_flag || inetd_flag || no_daemon_flag)) {
int fd;
if (daemon(0, 0) < 0)
fatal("daemon() failed: %.200s", strerror(errno));
fd = open(_PATH_TTY, O_RDWR | O_NOCTTY);
if (fd >= 0) {
(void) ioctl(fd, TIOCNOTTY, NULL);
close(fd);
}
}
...Когда нужно избегать ручной демонизации:
- Если служба разворачивается на systemd — предпочтительнее написать unit-файл и не демонизировать процесс внутри приложения.
- В контейнерах (Docker) обычно запущенный процесс должен оставаться в foreground; управляющая оболочка контейнера обрабатывает управление.
Решение задач разными подходами — сравнение
- Ручная демонизация (fork+setsid+chdir+закрытие дескрипторов): даёт полный контроль и совместимость со старыми системами.
- daemon(3): упрощает реализацию, но скрывает детали.
- systemd unit: делегирует все аспекты управления, логирования, перезапуска и зависимости системе и рекомендуется для современных дистрибутивов.
Чеклист для разработчика и системного администратора
Чеклист разработчика перед тем, как отправлять демон в продакшен:
- Проверить конфигурацию до демонизации и корректно сообщить об ошибках в stdout/stderr.
- Вызвать fork() и завершить родителя.
- Вызвать setsid() и при необходимости второй fork().
- Сменить рабочую директорию (обычно на “/“).
- Закрыть все ненужные дескрипторы и перенаправить 0/1/2 в /dev/null или в файлы логов.
- Установить безопасные права доступа (umask) и проверить UID/GID.
- Настроить обработку сигналов (SIGTERM, SIGHUP, SIGINT) и корректное завершение.
- Реализовать удобный способ логирования (syslog, journald, файл).
Чеклист администратора при разворачивании:
- Решить, будет ли сервис управляться systemd или запускаться вручную.
- Если systemd — подготовить unit-файл и задать Restart=on-failure/always по необходимости.
- Убедиться, что файлы логов ротацируются (logrotate) или используются journald.
- Настроить права доступа и SELinux/AppArmor при необходимости.
Критерии приёмки
- Процесс запускается и работает в фоне без управляющего терминала (TTY == ?).
- PPID процесса — 1 (systemd/init) или systemd управляет процессом через unit.
- PID, PGID и SID установлены корректно (если вы намеренно делаете лидер сессии — SID == PID).
- Нет незакрытых дескрипторов, мешающих отмонтированию критичных FS.
- Логирование поступает в корректный канал (syslog/journald/файл) и не теряется.
- Сигналы SIGTERM и SIGHUP корректно обрабатываются.
Тестовые сценарии и приёмочные тесты
- Запуск и нормальное завершение при SIGTERM.
- Обработка неожиданного исключения без утечек дескрипторов.
- Проверка, что printf/puts не выводят данные в управляющий терминал.
- Проверка поведения при невозможности сделать chdir(“/“) или открыть /dev/null.
- Тест на повторный запуск (pidfile, блокировка) — поведение при двойном старте.
Отладка и распространённые ошибки
- Симптом: демон пишет в консоль — причина: не закрыты stdout/stderr.
- Симптом: нельзя отмонтировать диск — причина: демон держит файловый дескриптор в рабочей директории.
- Симптом: демон неожиданно умирает — причина: неверная обработка сигналов или ошибки при инициализации после демонизации.
- Рекомендация: временно не демонизируйте процесс в среде разработки, чтобы видеть сообщения об ошибках.
Безопасность и права
- Запускайте демон от непривилегированного пользователя, если это возможно.
- Проверяйте open/creat файлов на уязвимости TOCTOU и race conditions.
- Настройте umask и ограничьте права создаваемых файлов.
- При необходимости используйте механизм sandboxing (namespace, seccomp, AppArmor/SELinux).
Миграция на systemd
Если вы внедряете новую службу и целитесь на современные дистрибутивы, рассмотрите перевод на systemd. Простой unit-файл:
[Unit]
Description=My Demo Service
After=network.target
[Service]
Type=forking
ExecStart=/usr/bin/mydaemon --config /etc/mydaemon.conf
PIDFile=/var/run/mydaemon.pid
Restart=on-failure
[Install]
WantedBy=multi-user.targetПояснения:
- Type=forking указывает systemd, что процесс демонизируется самостоятельно (fork).
- Если приложение не демонизируется (остается в foreground), используй Type=simple и не делай fork в коде.
- systemd предоставляет журналы через journalctl, управление зависимостями и восстановление.
Когда ручная демонизация не подойдёт
- В контейнерных окружениях: PID 1 обычно принадлежит процессу в контейнере; в контейнере часто не используют демонизацию.
- При развёртывании через systemd: дублирование демонизации усложнит диагностику.
- Если нужна тонкая интеграция с менеджером сервисов (healthchecks, socket activation) — используйте возможности менеджера.
Быстрая шпаргалка команд
- Посмотреть процессы: ps -o pid,ppid,pgid,sid,tty,stat,cmd -C
- Посмотреть журнал systemd: journalctl -u
- Проверить открытые дескрипторы: lsof -p
- Проверить родителя процесса: ps -o pid,ppid,cmd -p
Мини-методология разработки демона
- Реализовать сервис как foreground-приложение и добавить подробное логирование.
- Провести все проверки конфигурации и доступности ресурсов в foreground режиме.
- Добавить опцию –daemonize/–no-daemon, чтобы управлять поведением при развертывании.
- Обеспечить корректную обработку сигналов и graceful shutdown.
- Подготовить unit-файл для systemd и проверить интеграцию с journald.
Короткий глоссарий
- Демон — фоновый процесс без управляющего терминала.
- setsid — системный вызов, создающий новую сессию и отделяющий процесс от терминала.
- fork — системный вызов клонирования процесса.
- PID — идентификатор процесса.
- TTY — обозначение управляющего терминала.
Итоги
Демонизация — устоявшийся набор шагов (fork, setsid, chdir, закрытие дескрипторов), но во многих современных сценариях предпочтительнее делегировать управление systemd или другому менеджеру процессов. При разработке демона придерживайтесь чеклистов по безопасности, логированию и обработке сигналов, покрывайте поведение тестами и не забывайте про удобство отладки в foreground-режиме.
Важно помнить: автоматизированные менеджеры сервисов упрощают эксплуатацию и мониторинг — выбирайте подход, соответствующий окружению развертывания.