Гид по технологиям

Как создать демон в Linux

8 min read Системное программирование Обновлено 17 Dec 2025
Создание демона в Linux: руководство
Создание демона в Linux: руководство

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

Демон в терминале Linux, показывающий запущенный фоновый процесс

Краткое введение в создание демонов

Демоны — это процессы, которые не работают под прямым контролем интерактивного пользователя и выполняют фоновые задачи. Обычно они запускаются при старте системы и работают непрерывно до её выключения. Главное практическое отличие демонов от обычных процессов — они не взаимодействуют с консолью или управляющим терминалом.

Типичные демоны в 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;
}

Объяснение первых шагов:

  1. fork() создаёт дочерний процесс. Родитель завершается (exit), а дочерний продолжается. Это даёт новому процессу родителя init (или systemd), так как родитель был завершён.
  2. После fork() рекомендуется вызывать setsid(), чтобы создать новую сессию и отделиться от управляющего терминала.
  3. Часто делают ещё второй fork(), чтобы предотвратить возможность вновь получить управляющий терминал (двойной fork — классическая техника демонизации).

Последовательность стандартных шагов для демонизации

  1. Выполнить начальную настройку и проверить конфигурационные файлы (чтобы ошибки сообщить в консоль до демонизации).
  2. fork() и завершить родителя.
  3. вызвать setsid() — новая сессия и отсутствие управляющего терминала.
  4. (опционально) второй fork() — чтобы гарантировать, что процесс не может вновь стать лидером сессии.
  5. chdir(“/“) — сменить рабочую директорию на корень, чтобы не мешать отмонтированию.
  6. Закрыть все открытые файловые дескрипторы, перенаправить stdin/stdout/stderr в /dev/null или в файлы логов.
  7. Настроить обработчики сигналов и запуск основной петли.

Компиляция и наблюдение за процессом

Скомпилируйте пример так:

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 при необходимости.

Критерии приёмки

  1. Процесс запускается и работает в фоне без управляющего терминала (TTY == ?).
  2. PPID процесса — 1 (systemd/init) или systemd управляет процессом через unit.
  3. PID, PGID и SID установлены корректно (если вы намеренно делаете лидер сессии — SID == PID).
  4. Нет незакрытых дескрипторов, мешающих отмонтированию критичных FS.
  5. Логирование поступает в корректный канал (syslog/journald/файл) и не теряется.
  6. Сигналы 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

Мини-методология разработки демона

  1. Реализовать сервис как foreground-приложение и добавить подробное логирование.
  2. Провести все проверки конфигурации и доступности ресурсов в foreground режиме.
  3. Добавить опцию –daemonize/–no-daemon, чтобы управлять поведением при развертывании.
  4. Обеспечить корректную обработку сигналов и graceful shutdown.
  5. Подготовить unit-файл для systemd и проверить интеграцию с journald.

Короткий глоссарий

  • Демон — фоновый процесс без управляющего терминала.
  • setsid — системный вызов, создающий новую сессию и отделяющий процесс от терминала.
  • fork — системный вызов клонирования процесса.
  • PID — идентификатор процесса.
  • TTY — обозначение управляющего терминала.

Итоги

Демонизация — устоявшийся набор шагов (fork, setsid, chdir, закрытие дескрипторов), но во многих современных сценариях предпочтительнее делегировать управление systemd или другому менеджеру процессов. При разработке демона придерживайтесь чеклистов по безопасности, логированию и обработке сигналов, покрывайте поведение тестами и не забывайте про удобство отладки в foreground-режиме.

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

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Как ускорить браузер на Kindle
Гаджеты

Как ускорить браузер на Kindle

Размытие лиц в Python с OpenCV
Компьютерное зрение

Размытие лиц в Python с OpenCV

Не забывать важные сообщения в Slack
Продуктивность

Не забывать важные сообщения в Slack

Геотегирование на Flickr: быстрый гид
Фотография

Геотегирование на Flickr: быстрый гид

Trello на Android: исправить синхронизацию
Продуктивность

Trello на Android: исправить синхронизацию

Блокировать обновление Windows 10 v1809
Windows

Блокировать обновление Windows 10 v1809