Перевод Linux Daemon HOWTO Версия для печати   


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

Девин Ватсон (Devin Watson) dmwatson@comcast.net

Версия 1.0, май 2004
------------------------------------------------------------ ------------
В этом документе рассказывается о том, как писать демонов в Linux с использованием GCC. Для полного понимания этого документа необходимы знание Linux и языка программирования C. Права на этот документ в терминах лицензии BSD принадлежат Девину Ватсону.
------------------------------------------------------------ ------------

1. Введение: что есть демон?

2. Начинаем

3. Планирование вашего демона
* 3.1 Что необходимо сделать?
* 3.2 Насколько интерактивные?

4. Базовая структура демона
* 4.1 Ответвление от родительского процесса
* 4.2 Изменение маски файла (Umask)
* 4.3 Открытие журналов на запись
* 4.4 Создание уникального ID сессии (SID)
* 4.5 Изменение рабочего каталога
* 4.6 Закрытие стандартных файловых дескрипторов

5. Реализация кода демона
* 5.1 Инициализация
* 5.2 Большой Цикл

6. Собираем все вместе
* 6.1 Законченный пример

------------------------------------------------------------ -----------


1. Введение: что есть демон?

Демон (или служба) является фоновым процессом, который разработан специально для автономной работы, с минимальным вмешательством пользователя или вообще без него. HTTP-демон (httpd) веб-сервера Apache является одним из примеров таких процессов. Он работает в фоновом режиме, слушая специфичные порты и обслуживая страницы или выполняя скрипты, в зависимости от вида запроса.

Создание демона в Linux включает в себя соблюдение набора специфических правил в определенном порядке. Знание того как они работают поможет вам понять как демоны работают в пространстве пользователя Linux, но при этом также могут использовать вызовы ядра. На самом деле, совсем немного демонов взаимодействует с модулями ядра, которые работают с аппаратными устройствами, такими как платы внешних контроллеров, принтеров и КПК. Они являются одним из фундаментальных строительных блоков в Linux и дают невероятные гибкость и производительность системе в целом.

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


2. Начинаем

Прежде всего, для разработки демонов в вашей Linux системе должны быть установлены следующие пакеты:

* GCC 3.2.2 или выше
* библиотеки и заголовочные файлы для разработки в Linux

Если в вашей системе они еще не установлены (что вряд ли, но все равно проверьте), то они понадобятся вам для реализации примеров из этого документа. Чтобы узнать версию установленного в вашей системе GCC, скомандуйте:

$ gcc --version



3. Планирование вашего демона

3.1 Что необходимо сделать?
Демон должен делать только одну вещь и делать ее хорошо. Эта одна вещь может быть такой же сложной как управление сотнями почтовых ящиков во множестве доменов или такой же простой как формирование отчета и вызов sendmail для оправки этого отчета админу.

В любом случае вы должны хорошо представлять себе то, что должен делать ваш демон. Если планируется взаимодействие с другими демонами, разрабатываемыми и вами, и не вами, то это тоже необходимо учитывать при планировании.

3.2 Насколько интерактивные?
Демоны не должны общаться с пользователем напрямую через терминал. На самом деле, демон вообще не должен напрямую общаться с пользователем. Все общение должно производиться через определенный интерфейс (который может позволять, а может и не позволять запись), который может быть сложным как GTK+ GUI или простым как набор сигналов.


4. Базовая структура демона

Когда демон запускается, он выполняет некоторую низкоуровневую работу для подготовки себя к основной работе. Первая включает в себя несколько шагов:

* Отделение (ответвление, fork) от родительского процесса
* Изменение файловой маски (umask)
* Открытие любых журналов на запись
* Создание уникального ID сессии (SID)
* Изменение текущего рабочего каталога на безопасное место
* Закрытие стандартных файловых дескрипторов
* Переход к коду собственно демона

4.1 Отделение от родительского процесса
Демон запускается либо самой системой, либо пользователем в терминале или скрипте. Во время запуска его процесс ничем не отличается от любого другого процесса в системе. Чтобы сделать его по-настоящему автономным, нужно создать дочерний процесс, в котором будет выполняться код демона. Это называется форком и для этого используется функция fork():
pid_t pid;

/* отделяемся от родительского процесса */       
pid = fork();
if (pid < 0) {
  exit(EXIT_FAILURE);
}
/* Если с PID'ом все получилось, то родительский процесс можно завершить. */
if (pid > 0) {
  exit(EXIT_SUCCESS);
}


Отметим здесь проверку успешного завершения вызова fork(). При разработке демона необходимо делать код максимально стабильным. На самом деле, большую часть всего кода демона составляют именно проверки на ошибки.

Функция fork() возвращает либо id дочернего процесса (PID, не равный нулю), либо -1 в случае ошибки. Если процесс не может породить потомка, то демон должен завершиться прямо здесь.

Если получение PID от fork() совершилось успешно, то родительский процесс должен изящно завершиться. Это может показаться странным для всех, кто такого еще не видел, но после ответвления дочерний процесс продолжает выполнение остального кода с этого места.

4.2 Изменение файловой маски (Umask)
Чтобы иметь возможномть писать в любые файлы (включая журналы), созданные демоном, файловая маска (umask) должна быть изменена так, чтобы они могли быть записаны или прочитаны правильным образом. Это похоже на выполнение umask из командной строки, но мы прагматично делаем это здесь при помощи функции umask():
pid_t pid, sid;

/* Ответвляемся от родительского процесса */
pid = fork();
if (pid < 0) {
  /* Фиксируем ошибку (через syslog при возможности) */
  exit(EXIT_FAILURE);
}
/* Если с PID'ом все получилось, то родительский процесс можно завершить. */
if (pid > 0) {
  exit(EXIT_SUCCESS);
}

/* Изменяем файловую маску */
umask(0);


Через установку umask в 0 мы получим полный доступ к файлам, созданным демоном. Даже если вы не планируете использовать какие-либо файлы вообще, установка umask остается хорошей идеей просто на случай доступа к файлам на файловой системе.

4.3 Открытие журналов на запись
Эта действие опционально, но все-таки рекомендуется открыть где-нибудь в системе файл журнала на запись. Это можно сделать даже только для того, чтобы вы имели возможность посмотреть на отладочную информацию от демона.

4.4 Создание уникального ID сессии (SID)
С этого места для нормальной работы дочерний процесс должен получить уникальный SID от ядра. Иначе дочерний процесс станет сиротой. Тип pid_t, объявленный в предыдущем разделе, также используется для создания нового SID для дочернего процесса:
pid_t pid, sid;

/* Ответвляемся от родительского процесса */
pid = fork();
if (pid < 0) {
  exit(EXIT_FAILURE);
}
/* Если с PID'ом все получилось, то родительский процесс можно завершить. */
if (pid > 0) {
  exit(EXIT_SUCCESS);
}

/* Изменяем файловую маску */
umask(0);

/* Здесь можно открывать любые журналы */

/* Создание нового SID для дочернего процесса */
sid = setsid();
if (sid < 0) {
  /* Журналируем любой сбой */
  exit(EXIT_FAILURE);
}


Как видим, функция setsid() возвращает данные того же типа что и fork(). Чтобы проверить что функция создала SID для дочернего процесса мы можем использовать аналогичную процедуру проверки на ошибки.

4.5 Изменение рабочего каталога
Текущий рабочий каталог нужно сменить на некоторое место, гарантированно присутствующее в системе. Поскольку многие дистрибутивы Linux не полностью следуют стандарту иерархии файловой системы Linux (FHS, Filesystem Hierarchy Standard), то в системе гарантированно присутствует только корень файловой системы (/). Сменить каталог можно при помощи функции chdir():
pid_t pid, sid;

/* Ответвляемся от родительского процесса */
pid = fork();
if (pid < 0) {
  exit(EXIT_FAILURE);
}
/* Если с PID'ом все получилось, то родительский процесс можно завершить. */
if (pid > 0) {
  exit(EXIT_SUCCESS);
}

/* Изменяем файловую маску  */
umask(0);       

/* Здесь можно открывать любые журналы */        

/* Создание нового SID для дочернего процесса */
sid = setsid();
if (sid < 0) {
  /* Журналируем любой сбой */
  exit(EXIT_FAILURE);
}

/* Изменяем текущий рабочий каталог */
if ((chdir("/")) < 0) {
  /* Журналируем любой сбой */
  exit(EXIT_FAILURE);
}


И снова мы видим здесь код обработки ошибок. Функция chdir() при ошибке возвращает -1, так что не забывайте проверять возвращаемое ею значение после смены каталога.

4.6 Закрытие стандартных файловых дескрипторов
Одним из последних шагов в стартовой настройке демона является закрытие стандартных файловых дескрипторов (STDIN, STDOUT, STDERR). Поскольку демон не может использовать терминал, эти файловые дескрипторы излишни и создают угрозу безопасности. Закрыть их можно при помощи функции close():
pid_t pid, sid;

/* Ответвляемся от родительского процесса */
pid = fork();
if (pid < 0) {
  exit(EXIT_FAILURE);
}
/* Если с PID'ом все получилось, то родительский процесс можно завершить. */
if (pid > 0) {
  exit(EXIT_SUCCESS);
}

/* Изменяем файловую маску */
umask(0);       

/* Здесь можно открывать любые журналы */

/* Создание нового SID для дочернего процесса */
sid = setsid();
if (sid < 0) {
  /* Журналируем любой сбой */
  exit(EXIT_FAILURE);
}

/* Изменяем текущий рабочий каталог */
if ((chdir("/")) < 0) {
  /* Журналируем любой сбой */
  exit(EXIT_FAILURE);
}

/* Закрываем стандартные файловые дескрипторы */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);


Использование определенных для файловых дескрипторов констант является хорошей идеей, поскольку улучшает переносимость.


5. Разработка кода демона

5.1 Инициализация
На данном этапе вы уже в основном сообщили Linux о том что вы демон, так что сейчас самое время написать код самого демона. Инициализация является первым шагом на этом пути. Поскольку возможно существование множества разных функций, которые могут быть вызваны здесь для настройки вашего демона, я не буду углубляться в этот вопрос.

Важный момент здесь в том, что при инициализации чего-нибудь в демоне необходимо применять те же методы обнаружения и обработки ошибок. Будьте болтливы ("verbose") насколько это возможно при записи в syslog или в ваши собственные журналы. Отладка демона может затрудниться при недостатке информации о его состоянии.

5.2 Большой Цикл
Основной код демона обычно находится внутри бесконечного цикла. Технически это не бесконечный цикл конечно, но он организован как бесконечный:
pid_t pid, sid;

/* Ответвляемся от родительского процесса */
pid = fork();
if (pid < 0) {
  exit(EXIT_FAILURE);
}
/* Если с PID'ом все получилось, то родительский процесс можно завершить. */
if (pid > 0) {
  exit(EXIT_SUCCESS);
}

/* Изменяем файловую маску */
umask(0);       

/* Здесь можно открывать любые журналы */

/* Создание нового SID для дочернего процесса */
sid = setsid();
if (sid < 0) {
  /* Журналируем любой сбой */
  exit(EXIT_FAILURE);
}

/* Изменяем текущий рабочий каталог */
if ((chdir("/")) < 0) {
  /* Журналируем любой сбой */
  exit(EXIT_FAILURE);
}

/* Закрываем стандартные файловые дескрипторы */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

/* Специфичная для демона инициализация проходит тут */

/* Большой Цикл */
while (1) {
  /* Делаем тут чего-нибудь ... */
  sleep(30); /* ждем 30 секунд */
}


Типичный цикл обычно является циклом while, который имеет бесконечное условие завершения с вызовом sleep при необходимости выполнения через фиксированные интервалы.

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

6. Собираем все вместе

6.1 Законченный пример
Приведенное ниже является законченным примером демона и иллюстрирует все шаги, необходимые для запуска и работы. Для его выполнения просто скимпилируйте при помощи gcc и запустите на выполнение из командной строки. Для завершения выясните его PID и воспользуйтесь командой kill.

Я также задействовал подходящие заголовочные файлы для взаимодействия с syslog'ом, использование которого рекомендуется как минимум для записи в журнал информации о запуске/останове/паузе/завершении, в дополнение к использованию ваших собственных журналов через вызовы функций fopen()/fwrite()/fclose().
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>

int main(void) {

  /* Наши ID процесса и сессии */
  pid_t pid, sid;

  /* Ответвляемся от родительского процесса */
  pid = fork();
  if (pid < 0) {
    exit(EXIT_FAILURE);
  }
  /* Если с PID'ом все получилось, то родительский процесс можно завершить. */
  if (pid > 0) {
    exit(EXIT_SUCCESS);
  }

  /* Изменяем файловую маску */
  umask(0);

  /* Здесь можно открывать любые журналы */        

  /* Создание нового SID для дочернего процесса */
  sid = setsid();
  if (sid < 0) {
    /* Журналируем любой сбой */
    exit(EXIT_FAILURE);
  }

  /* Изменяем текущий рабочий каталог */
  if ((chdir("/")) < 0) {
    /* Журналируем любой сбой */
    exit(EXIT_FAILURE);
  }

  /* Закрываем стандартные файловые дескрипторы */
  close(STDIN_FILENO);
  close(STDOUT_FILENO);
  close(STDERR_FILENO);

  /* Специфичная для демона инициализация проходит тут */

  /* Большой Цикл */
  while (1) {
	/* Делаем здесь чего-нибудь ... */
	sleep(30); /* ждем 30 секунд */
  }
  exit(EXIT_SUCCESS);
}


Вы можете использовать этот скелет для разработки ваших собственных демонов. Не забывайте вести журналы событий (или используйте возможности syslog) и кодируйте надежно, кодируйте надежно, кодируйте надежно!

Опубликовал: San АНДРЕЕВ
Дата: 21.08.2006
постоянный адрес статьи: http://linuxportal.ru/entry.php/P2361_0_3_0/