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

Введение: что такое демон
Демоны — это процессы, которые не управляются напрямую активным пользователем и работают в фоне. Обычно они запускаются при старте системы и работают непрерывно до выключения. Главное практическое отличие от обычных процессов — отсутствие обмена с управляющим терминалом (не выводят интерактивно сообщения на экран).
Примеры распространённых демонов в Linux:
- crond — выполняет задачи по расписанию (cron jobs)
- sshd — обеспечивает удалённый вход в систему по SSH
- httpd/nginx — обслуживает веб-запросы
- nfsd — обеспечивает сетевой доступ к файлам
Часто имя демона оканчивается на букву «d» (например, sshd), но это лишь конвенция, а не требование.
Что происходит при демонизации: краткое описание процесса
Перед тем как стать демоном, процесс обычно выполняет начальные операции: чтение конфигурации, попытки получить ресурсы и валидацию. Это нужно, чтобы при ошибках система могла корректно сообщить о них и завершить процесс с кодом ошибки.
Стандартный путь демонизации включает следующие шаги:
- Выполнение первичной инициализации (чтение конфигурации, проверка прав). Если что-то не так — сообщить и завершиться.
- fork: создать дочерний процесс и завершить родительский — это даёт дочернему процессу init (systemd) в качестве родителя.
- setsid: создать новую сессию и отделиться от управляющего терминала.
- (Опционально) второй fork, чтобы не быть лидером сессии и предотвратить получение управляющего терминала в будущем.
- Сброс umask (обычно в 0) и переход в корневую директорию или другую безопасную рабочую директорию.
- Закрытие всех наследованных файловых дескрипторов.
- Перенаправление stdin, stdout, stderr в /dev/null или в лог-файлы.
- (Рекомендуется) запись PID-файла и установка обработчиков сигналов (SIGHUP, SIGTERM).
Ниже подробно рассмотрим сессии и группу процессов, а затем перейдём к коду и практическим рекомендациям.
Что такое сессии и группы процессов
После входа в систему через терминал пользователь запускает приложения через оболочку. Операционная система группирует процессы в группы процессов (process groups) и сессии, чтобы корректно управлять вводом/выводом и сигналами.
Каждая сессия состоит из одной или нескольких групп процессов. Контролирующий терминал (controlling terminal) связан только с одной сессией в каждый момент времени.
Идентификаторы сессии и группы процессов — это PID лидеров соответствующих групп. Дочерний процесс наследует группу от родителя. Когда несколько процессов связаны через пайп, первый процесс становится лидером группы.
Пример: простая демонизация (пошагово с кодом)
В исходной статье приведён пример из двух файлов: test.c и daemon.c. Ниже сохранены и прокомментированы эти фрагменты с объяснениями.
// test.c
#include
int _daemon(int, int);
int main()
{
getchar();
_daemon(0, 0);
getchar();
return 0;
} Файл демонстрирует точку вызова функции демонизации _daemon. Первый getchar() позволяет увидеть состояние процесса до демонизации.
// 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;
} В этом простейшем варианте дочерний процесс остаётся в фоне, поскольку родитель завершился. Однако до вызова setsid и закрытия дескрипторов демон не полностью отделён от управляющего терминала.
Скомпилируйте и запустите так:
gcc -o test test.c daemon.c
./testИспользуйте ps для просмотра состояния процесса до и после вызова _daemon.
Пример вывода до демонизации (когда _daemon ещё не вызван):
ps -C test -o "pid ppid pgid sid tty stat command"
# Output
PID PPID PGID SID TT STAT COMMAND
10296 5119 10296 5117 pts/2 S+ ./testПоле STAT показывает состояние процесса. Обозначения:
| Аббревиатура | Значение |
| S | Ожидание события (спящий) |
| T | Процесс остановлен |
| s | Лидер сессии |
| + | Процесс находится на переднем плане |
Родителем вашего приложения на этом этапе является оболочка:
ps -jp 5119
# Output
PID PGID SID TTY TIME CMD
5119 5119 5117 pts/2 00:00:02 zshПосле вызова _daemon и завершения родительского процесса:
ps -C test -o "pid ppid pgid sid tty stat command"
# Output
PID PPID PGID SID TT STAT COMMAND
22504 1 22481 5117 pts/2 S ./testРодителем процесса стал PID 1 (systemd):
ps -jp 1
# Output
PID PGID SID TTY TIME CMD
1 1 1 ? 00:00:01 systemdЧтобы окончательно отделиться от терминала и создать новую сессию, нужно вызвать setsid:
if (setsid() == -1)
return -1;После вызова setsid вы увидите, что процесс больше не связан с tty (поле TT будет «?»), а он становится лидером сессии.
Далее обычно делают chdir(“/“) и закрывают дескрипторы. Пример добавления chdir:
if (!nochdir) {
if (chdir("/") == -1)
return -1;
}Закрытие файловых дескрипторов и перенаправление stdin/stdout/stderr:
#define NR_OPEN 1024
if (!noclose) {
for (i = 0; i < NR_OPEN; i++)
close(i);
open("/dev/null", O_RDWR);
dup(0);
dup(0);
}После этого дескрипторы 0, 1 и 2 будут указывать на /dev/null, и любые printf попадут в соответствующий поток, а не в терминал.
Полный пример простой функции _daemon:
#include
#include
#include
#include
#include
#include
#include
#include
#include
int _daemon(void) {
// PID: Process ID
// SID: Session ID
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 child
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);
} Пример использования системного вызова daemon(3) (в libc) — упрощённый способ демонизации, но не всегда предпочтителен:
if (!(debug_flag || inetd_flag || no_daemon_flag)) {
int fd;
if (daemon(0, 0) < 0)
fatal("daemon() failed: %.200s", strerror(errno));
/* Disconnect from the controlling tty. */
fd = open(_PATH_TTY, O_RDWR | O_NOCTTY);
if (fd >= 0) {
(void) ioctl(fd, TIOCNOTTY, NULL);
close(fd);
}
}Важно: многие современные дистрибутивы и best practices рекомендуют НЕ демонизировать процессы из кода, а запускать их в foreground и управлять ими через systemd или другой инициализатор. Если программа демонизируется внутри себя, systemd не сможет корректно отслеживать её состояние и управлять рестартами (если только не настроена Type=forking и правильно настроен PIDFile).
Улучшенные практики: что добавить к базовой демонизации
Ниже — набор рекомендаций и шаблонов, которые обычно используют в реальных демонах.
- Umask: сбросьте umask, например umask(0), чтобы файлы/директории создавались с предсказуемыми правами (контролируйте сами в момент создания).
- Второй fork: выполните второй fork после setsid, чтобы процесс не оставался лидером сессии. Это предотвращает повторное получение управляющего терминала.
- PID-файл: создавайте /var/run/your-daemon.pid (или /run/…) и записывайте в него PID процесса. Проверяйте существующий PID и жив ли процесс, чтобы предотвратить двойной запуск.
- Логи: используйте syslog или выход в файл. Для systemd лучше логировать в stdout/stderr и позволять systemd-journald собирать логи.
- Обработчики сигналов: установите обработчики для SIGTERM (корректное завершение), SIGHUP (перечитать конфиг) и SIGCHLD (если есть дочерние процессы).
- Проверка привилегий: если демону не нужны root-привилегии — сделайте drop privileges (setuid/setgid) после открытия необходимых ресурсов.
- Безопасность: ограничьте доступ к PID и к директориям конфигурации, используйте chroot по необходимости и capabilities, если требуется.
Примерная последовательность действий (мини‑методология):
- Выполнить начальную проверку конфигурации и ресурсов в foreground.
- Заблокировать файл устройства/порт при необходимости (например, bind на порт).
- Fork -> родитель выходит, дочерний вызывает setsid.
- Второй fork (опционально).
- chdir(“/“) и umask(0).
- Закрыть дескрипторы, перенаправить 0/1/2 на /dev/null или на лог.
- Записать PID-файл и установить корректные права.
- Установить обработчики сигналов и начать главный цикл.
systemd против классической демонизации
Современные системные менеджеры (systemd) предпочитают, чтобы сервисы работали в foreground и не демонизировались самостоятельно. Для systemd существуют типы юнитов:
- Type=simple — сервис запускается и остаётся в foreground (рекомендуется для большинства приложений).
- Type=forking — процесс форкается в фон (используется для старых демонов; systemd ожидает, что процесс запишет PID-файл).
Если вы планируете поддержку systemd, лучше:
- Не демонизируйте процесс внутри кода; добавьте флаг –no-daemon чтобы выключить демонизацию.
- Пусть systemd контролирует рестарт и логирование.
Пример простого unit-файла systemd (пример):
[Unit]
Description=My Example Daemon
After=network.target
[Service]
Type=simple
User=myuser
ExecStart=/usr/bin/my-daemon --no-daemon
Restart=on-failure
[Install]
WantedBy=multi-user.targetКритерии приёмки (общие тесты)
- Процесс корректно запускается и переходит в фон (или остаётся в foreground, если требуется).
- PID-файл создаётся и содержит действительный PID, при остановке PID-файл удаляется.
- При SIGTERM процесс завершает работу в пределах ожидаемого времени.
- При SIGHUP конфигурация перечитывается без утечек ресурсов.
- Логирование осуществляется в указанном месте; нет вывода в управляющий tty.
- При попытке запустить второй экземпляр — корректный отказ или использование механизма блокировки.
Контроль качества, тестовые кейсы
- Функциональный тест: запуск/останов/перезапуск сервиса.
- Нагрузочный тест: сервис устойчив при пиковых запросах и не порождает утечек памяти.
- Тест устойчивости: симулировать потерю сети, внешних зависимостей и убедиться, что сервис ведёт себя предсказуемо.
- Безопасность: проверить права на PID-файлы, доступ к логам, запуск не от root.
Когда демонизация не нужна — контрпример
- Есть systemd (или другой современный менеджер) — предпочтительнее запуск в foreground.
- Приложение должно быть контейнеризовано (Docker) — там ожидают foreground-процесс.
- Для короткоживущих задач демонизация усложняет отладку и мониторинг.
Риски и рекомендации по безопасности
- Демонизация под root без drop-privileges увеличивает риск компрометации. Открывайте привилегированные ресурсы как root, затем переключайтесь на непривилегированный пользователь.
- Ведение логов в файлы может раскрывать секреты; ограничьте доступ и используйте журналирование с ротацией.
- Корректно обрабатывайте сигналы, чтобы избегать неконсистентных состояний при завершении.
Чек-листы по ролям
Разработчик:
- Добавить флаг –no-daemon для запуска в foreground
- Реализовать запись PID-файла и проверку уже работающего процесса
- Поддержать корректные обработчики SIGHUP/SIGTERM
- Предусмотреть логирование в stdout/stderr или syslog
Системный администратор:
- Писать systemd unit для управления сервисом
- Контролировать права на PID/лог-файлы
- Настроить ротацию логов (logrotate или journald)
Оператор DevOps:
- Настроить мониторинг и alerting (SLO/SLI по доступности и времени отклика)
- Запланировать обновления и безболезнственные перезапуски
Шаблон: упрощённый decision flow (Mermaid)
flowchart TD
A[Запуск сервиса] --> B{Нужна ли демонизация в коде?}
B -- Да --> C[Реализовать демонизацию: fork, setsid, chdir, close fds]
B -- Нет --> D[Запускать в foreground; настроить systemd unit]
C --> E{Записывать PID-файл?}
E -- Да --> F[Создать /run/your.pid и контролировать блокировку]
E -- Нет --> G[systemd Type=forking или нет PID]
D --> H[Type=simple, Restart политики, логирование в stdout]Короткая сводка: что важно помнить
- Демонизация — это не только fork, но и отделение от сессии, закрытие дескрипторов и корректное управление правами и логами.
- Для новых сервисов предпочтительнее работать в foreground и доверить систему управления (systemd).
- Всегда обрабатывайте сигналы и реализуйте безопасное завершение и перезапуск.
Краткий глоссарий (1 строка на термин)
- Демон: фоновой процесс, не привязанный к управляющему терминалу.
- PID-файл: файл с PID запущенного процесса, обычно в /run или /var/run.
- setsid: системный вызов для создания новой сессии и отделения от tty.
- umask: маска создания прав для новых файлов/директорий.
Быстрые рекомендации по отладке
- Запускайте с –no-daemon и используйте gdb/strace для отладки.
- Логи переправляйте в stdout при работе с systemd, чтобы использовать journalctl.
- Проверяйте все возвращаемые коды системных вызовов и логируйте ошибки при старте.
Заключение
Понимание демонизации важно для системного программирования и администрирования Linux. Хотя классический алгоритм демонизации остаётся полезным для изучения устройства системы и старых демонов, современные практики рекомендуют делегировать управление процессами systemd и писать сервисы, которые корректно работают в foreground и оставляют оркестрацию системе.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone