Демоны в Linux
Демон - это процесс, обладающий длинным жизненным циклом. Часто демоны создаются на этапе загрузки системы и работают до момента ее выключения. Выполняется они в фоновом режиме, не имеют контролирующего терминала и обычно не привязаны к конкретной пользовательской сессии.
Примеры системных демонов
У каждого системного демона есть своя микроцель существования. Одни контролируют сетевые соединения, другие отвечают за взаимодействие между приложениями по системе dbus. Примерами популярных демонов являются:
1) cron — демон, который выполняет команды в запланированное время;
2) sshd — демон, который отвечает за обработку SSH-подключений;
3) httpd — демон HTTP-сервера (Apache), который обслуживает веб-страницы;
Вы можете и сами "поймать" демонов через различные утилиты: ps, top, pstree. Базовыми отличительными параметрами таких процессов являются имя, которое заканчивается на 'd', родительский процесс init и отсутствующий терминал:
$ ps -o pid,ppid,cmd
PID PPID CMD
1239 1 /usr/lib/snapd/snapd
1250 1 /usr/libexec/udisks2/udisksd
Дополнительно хочется отметить, что процессам-демонам присуща особенность, которая гарантирует, что ядро не сможет генерировать для них никаких сигналов, связанных с терминалом (
SIGINT,
SIGTSTP и
SIGHUP).
Создание демона
Глобально существует 2 типа демонов: "
SysV Daemons" и "
New-Style Daemons". Первый тип является традиционным и преимущественно использовался до появления systemd. Второй, в свою очередь, опирается на инфраструктуру systemd и является сервисом.
Сейчас не будем вдаваться в принцип работы сервисов и рассмотрим инициализацию каноничного SysV демона. Для того, чтобы стать демоном, программа должна выполнить следующие шаги:
1. Сделать вызов
fork(), после которого родитель завершается, а потомок продолжает работать. Это нужно для отделения демона от терминала, из которого он был запущен. В результате, процесс становится потомком для
init:
pid_t pid = fork();
if (pid < 0)
exit(EXIT_FAILURE);
if (pid > 0)
exit(EXIT_SUCCESS);
2. Дочерний процесс вызывает
setsid(), чтобы начать новую сессию, стать ее лидером и разорвать любые связи с контролирующим терминалом:
if (setsid() < 0)
exit(EXIT_FAILURE);
3. Проигнорировать сигнал
SIGHUP для того, чтобы не завершиться при закрытии терминала, внутри которого был воспроизведен запуск:
signal(SIGHUP, SIG_IGN);
4. Повторно выполнить
fork(). Этот шаг иногда выполняется для предотвращения возможности захвата вновь созданного демона новым управляющим терминалом.
5. Очистить атрибут
umask, чтобы файлы и каталоги, созданные демоном, имели запрашиваемые права доступа, указанные в вызовах
open() и
mkdir():
umask(0);
6. Поменять текущий рабочий каталог процесса (обычно на корневой). Это необходимо для исключения блокировки файловой системы и возможности, в случае необходимости, сделать для нее
unmount:
chdir("/");
7. Закрыть все открытые файловые дескрипторы, которые демон унаследовал от своего родителя. Поскольку демон потерял свой контролирующий терминал и работает в фоновом режиме, ему больше не нужно хранить дескрипторы с номерами 0, 1 и 2, их тоже закрываем:
for (int x = sysconf(_SC_OPEN_MAX); x>=0; x--)
close (x);
8. Переоткрыть дескрипторы
STDIN,
STDOUT,
STDERR и перенаправить стандартные потоки в виртуальное устройство "
/dev/null". Данный шаг необходим по нескольким причинам.
Во-первых, вновь открытые файлы неизбежно возьмут себе минимально доступный порядковый номер (0, 1, 2 ...), что может привести к нежелательным записям со стороны функций, которые работают с этими дескрипторами.
Во-вторых, данное действие позволяет избежать ошибок при вызове библиотечных функций, которые выполняют операции ввода/вывода с этими дескрипторами:
int fd0 = open("/dev/null", O_RDWR);
int fd1 = dup(0);
int fd2 = dup(0);
9. Запустить основной цикл, в котором демон будет выполнять свою работу:
while (1) {
}
Linux++ | IT-Образование