Процесс-демон. Определение и свойства

Процесс-де́мон — процесс специального назначения, создаваемый и управляемый системой. В отличие от других процессов является долгоживущим и запускается в фоновом режиме. Не имеет связанного с ним терминала для ввода и вывода.

Примерами служат syslogd, который записывает сообщения в системный журнал, и httpd, который обслуживает веб-страницы посредством работы протокола HTTP.

Свойства процессов-демонов:

  • Имеют длинный жизненный цикл. Часто создаются во время загрузки системы и работают до момента ее отключения;
  • Выполняются в фоновом режиме и не имеют котролируемого терминала. Это значит, что ядро не сможет генерировать для такого процесса никаких сигналов, связанных с терминалом или управлением заданиями(SIGINT, SIGTSTP, SUGHUP);

Обычно демоны создаются для специфических задач:

  • cron — выполняет команды в запланированное время;
  • sshd — демон защищенной командной оболочки, который позволяет входить в систему с удаленных компьютеров;
  • httpd — демон HTTP-сервера для обслуживания веб-страниц;
  • inetd — демон IP-службы, который ожидает входящих сетевых подключений на заданных портах и запускает соответствующие серверные программы для их обслуживания;

Создание процесса-демона

Для создания демона нужно выполнить следующие шаги:

  1. Сделать вызов fork(), после которого процесс родителя завершается, а потомок продолжает работать. В итоге он становится потомком процесса init. Делается это по следующим причинам:
    • Исходя из того, что демон был запущен в командной строке, завершение родителя будет обнаружено командной оболочкой, которая вслед за этим выведет новое приглашение и позволит потомку выполняться в фоновом режиме;
    • Потомок гарантированно не станет лидером группы процессов, поскольку он наследует PGID от своего родителя и получает свой уникальный идентификатор, который отличается от унаследованного PGID. Это необходимо для успешного выполнения следующего шага.
  2. Дочерний процесс вызывает setsid(), чтобы начать новую сессию и разорвать любые связи с контролирующим терминалом.

  3. Если после этого демон больше не открывает никаких терминальных устройств, мы можем не волноваться о том, что он восстановит соединение с контролирующим терминалом. В противном случае нам необходимо сделать так, чтобы терминальное устройство не стало контролирующим. Это можно сделать двумя способами:
    • Указывать флаг O_NOCTTY для любых вызовов open(), которые могут открыть терминальное устройство;
    • Есть более простой вариант: после setsid() можно еще раз сделать вызов fork(), опять позволив родителю завершиться, а потомку (правнуку) — продолжить работу. Это гарантирует, что потомок не станет лидером сессии, что делает невозможным повторное соединение с контролирующим терминалом.
  4. Очистить атрибут umask процесса, чтобы файл и каталоги, созданные демоном, имели запрашиваемые права.

  5. Поменять текущий рабочий каталог процесса (обычно на корневой — /). Это необходимо, поскольку демон обычно выполняется вплоть до выключения системы. Если файловая система, в которой находится его текущий рабочий каталог, не является корневой, то она не может быть отключена. Как вариант, в качестве рабочего каталога демон может задействовать то место, где он выполняет свою работу, или воспользоваться значением в конфигурационном файле. Главное, чтобы файловая система, в которой находится этот каталог, никогда не нуждалась в отключении. Например, cron применяет для этого /var/spool/cron.

  6. Закрыть все открытые файловые дескрипторы, которые демон унаследовал от своего родителя (возможно, некоторые из них необходимо оставить открытыми, поэтому данный шаг является необязательным и может быть скорректирован). Это делается по целому ряду причин. Поскольку демон потерял свой контролирующий терминал и работает в фоновом режиме, ему больше не нужно хранить дескрипторы с номерами 0, 1 и 2 (если они ссылаются на терминал). Кроме того, мы не можем отключить файловую систему, в которой долгоживущий демон удерживает открытыми какие-либо файлы. И, следуя обычным правилам, мы должны закрывать неиспользуемые файловые дескрипторы, поскольку их число ограничено.

  7. Закрыв дескрипторы с номерами 0, 1 и 2, демон обычно перенаправляет их в предварительно открытый файл /dev/null, используя вызов dup2() (или похожий). Это делается по двум причинам:
    • Это позволяет избежать ошибки при вызове библиотечных функций, которые выполняют операции ввода/вывода с этими дескрипторами;
    • Это исключает возможность повторного открытия демоном файлов с помощью дескрипторов 1 или 2, так как библиотечные функции, которые записывают в них данные, ожидают, что эти дескрипторы указывают на потоки stdout (стандартный вывод) и stderr (стандартный вывод ошибок).

Листинг процесса-демона

become_daemon.c:

#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <unistd.h>

#include "become_daemon.h"

int becomeDaemon(int flags) {
	int maxfd, fd;

	switch (fork()) {
		case -1:
			return -1;
		case 0:
			break;
		default:
			exit(EXIT_SUCCESS);
	}

	if (setsid() == -1) {
		return -1;
	}

	switch (fork()) {
		case -1:
			return -1;
		case 0:
			break;
		default:
			exit(EXIT_SUCCESS);
	}

	if (!(flags & BD_NO_UMASK0))
		umask(0);

	if (!(flags & BD_NO_CHDIR))
		chdir("/");

	if (!(flags & BD_NO_CLOSE_FILES)) {
		maxfd = sysconf(_SC_OPEN_MAX);
		if (maxfd == -1)
			maxfd = BD_MAX_CLOSE;

		for (fd = 0; fd < maxfd; fd++)
			close(fd);
	}

	if (!(flags & BD_NO_REOPEN_STD_FDS)) {
		close(STDIN_FILENO);

		fd = open("/dev/null", O_RDWR);
		if (fd != STDIN_FILENO)
			return -1;
		if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO)
			return -1;
		if (dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO)
			return -1;
	}

	return 0;
}

become_daemon.h:

#ifndef BECOME_DAEMON_H
#define BECOME_DAEMON_H

#define BD_NO_CHDIR		        01 // Do not call chdir("/")
#define BD_NO_CLOSE_FILES	    02 // Do not close all opend files
#define BD_NO_REOPEN_STD_FDS	04 // Do not forward stdin, stdout and stderr to /dev/null

#define BD_NO_UMASK0		   010 // Do not call umask(0)

#define BD_MAX_CLOSE		  8192 /* Maximum number of close file descriptors,
								    if sysconf(_SC_OPEN_MAX) does not defined */

int becomeDaemon(int flags);

#endif

Source

Linux API - Майкл Керриск. Глава 2. Основные понятия.
Linux API - Майкл Керриск. Глава 37. Демоны.

Tags: ,

Categories:

Updated: