image Всем привет! Мы переиздали классический труд Уильяма Стивенсона и Стивена Раго с исправленными опечатками перевода в твердой обложке.

Эта книга заслуженно пользуется популярностью у серьезных программистов во всем мире, поскольку содержит самую важную и практическую информацию об управлении ядрами UNIX и Linux. Без этих знаний невозможно написать эффективный и надежный код. От основ — файлы, каталоги и процессы — вы постепенно перейдете к более сложным вопросам, таким как обработка сигналов и терминальный ввод/вывод, многопоточная модель выполнения и межпроцессное взаимодействие с применением сокетов. В общей сложности в этой книге охвачены более 70 интерфейсов, включая функции POSIX асинхронного ввода/вывода, циклические блокировки, барьеры и семафоры POSIX.

Внутри мы рассмотрим главу «Процессы-демоны».

13.2. Характеристики демонов


Рассмотрим некоторые наиболее распространенные системные демоны и их связь с группами процессов, управляющими терминалами и сеансами, описанными в главе 9. Команда ps(1) выводит информацию о процессах в системе. Эта команда имеет множество параметров, дополнительную информацию о них вы найдете в справочном руководстве. Запустим команду

ps -axj

в BSD-системе и будем использовать полученную от нее информацию при дальнейшем обсуждении. Ключ -a используется для вывода процессов, которыми владеют другие пользователи, ключ -x — для вывода процессов, не имеющих управляющего терминала, и ключ -j — для вывода дополнительных сведений, имеющих отношение к заданиям: идентификатора сеанса, идентификатора группы процессов, управляющего терминала и идентификатора группы процессов терминала.

Для систем, основанных на System V, аналогичная команда выглядит как ps -efj. (В целях безопасности некоторые версии UNIX не допускают просмотра с помощью команды ps процессов, принадлежащих другим пользователям.) Вывод команды ps выглядит примерно так:

image

Из данного примера мы убрали несколько колонок, которые не представляют для нас особого интереса, такие как накопленное процессорное время. Здесь показаны следующие колонки, слева направо: идентификатор пользователя (UID), идентификатор процесса (PID), идентификатор родительского процесса (PPID), идентификатор группы процессов (PGID), идентификатор сеанса (SID ), имя терминала (TTY) и строка команды (CMD).

Система, в которой была запущена эта команда (Linux 3.2.0), поддерживает понятие идентификатора сеанса, который мы упоминали при обсуждении функции setsid в разделе 9.5. Идентификатор сеанса — это просто идентификатор процесса лидера сеанса. Однако в системах, основанных на BSD, будет выведен адрес структуры session, соответствующей группе процессов, которой принадлежит данный процесс (раздел 9.11).

Перечень системных процессов, который вы увидите, во многом зависит от реализации операционной системы. Обычно это будут процессы с идентификатором родительского процесса 0, запускаемые ядром в процессе загрузки системы. (Исключением является процесс init, так как это команда уровня пользователя, которая запускается ядром во время загрузки.) Процессы ядра — это особые процессы, они существуют все время, пока работает система. Эти процессы обладают привилегиями суперпользователя и не имеют ни управляющего терминала, ни строки команды.

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

  • Имеется демон kswapd, также известный как демон выгрузки страниц памяти. Он обеспечивает поддержку подсистемы виртуальной памяти, с течением времени записывая измененные страницы памяти на диск, чтобы их можно было освободить.
  • Демон flush выталкивает измененные страницы на диск, когда объем доступной памяти падает до установленного минимального предела. Он также выталкивает измененные страницы памяти на диск через регулярные интервалы времени, чтобы уменьшить потерю данных в случае краха системы. В системе может одновременно выполняться несколько демонов flush — по одному для каждого устройства. В примере выше присутствует только один демон flush с именем flush-8:0, где 8 — это старший номер устройства, а 0 — младший.
  • Демон sync_supers периодически выталкивает на диск метаданные файловой системы.
  • Демон jbd обеспечивает поддержку журнала в файловой системе ext4.

Процесс с идентификатором 1 — это обычно процесс init (launchd в Mac OS X), о чем уже говорилось в разделе 8.2. Это системный демон, который, кроме всего прочего, отвечает за запуск различных системных служб на различных уровнях загрузки. Как правило, эти службы также реализованы в виде демонов.

Демон rpcbind осуществляет преобразование числовых идентификаторов служб RPC (Remote Procedure Call — удаленный вызов процедур) в номера сетевых портов. Демон rsyslogd может использоваться программами для вывода сообщений в системный журнал, куда затем сможет заглянуть администратор. Сообщения могут выводиться в консоль, а также записываться в файл. (Более подробно механизм журналирования syslogd рассматривается в разделе 13.4.)
В разделе 9.3 мы уже говорили о демоне inetd. Этот демон ожидает поступления из сети запросов к различным сетевым серверам. Демоны nfsd, nfsiod, lockd, rpciod, rpc.idmapd, rpc.statd и rpc.mountd обеспечивают поддержку сетевой файловой системы (Network File System, NFS). Обратите внимание, что первые четыре из них являются демонами ядра, а последние три — демонами уровня пользователя.

Демон cron производит запуск команд через регулярные интервалы времени. Он обрабатывает различные задания системного администрирования, запуская их через заданные промежутки времени. Демон atd напоминает демон cron и дает пользователям возможность запускать задания в определенные моменты времени, но запускает задания однократно. Демон cupsd — это сервер печати, он обслуживает запросы к принтеру. Демон sshd обеспечивает удаленный доступ к системе и выполнение в защищенном режиме.

Обратите внимание, что большинство демонов обладают привилегиями суперпользователя (root). Ни один из демонов не имеет управляющего терминала — вместо имени терминала стоит знак вопроса. Демоны ядра запускаются без управляющего терминала. Отсутствие управляющего терминала у демонов пользовательского уровня, вероятно, результат вызова функции setsid. Все демоны пользовательского уровня являются лидерами групп и лидерами сеансов, а также единственными процессами в своих группах процессов и сеансах (исключение составляет rsyslogd). И наконец, обратите внимание, что родителем для большинства демонов является процесс init.

13.3. Правила программирования демонов


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

1. Вызвать функцию umask, чтобы сбросить маску режима создания файлов в значение 0. Маска, наследуемая от запускающего процесса, может маскировать некоторые биты прав доступа. Если предполагается, что процесс-демон будет создавать файлы, может потребоваться установка определенных битов прав доступа. Например, если демон создает файлы с правом на чтение и на запись для группы, маска режима создания файла, которая выключает любой из этих битов, будет препятствовать этому. C другой стороны, если демон вызывает библиотечные функции, создающие файлы, имеет смысл установить более ограничивающую маску (например, 007), так как библиотечные функции могут не принимать аргумент с битами прав доступа.

2. Вызвать функцию fork и завершить родительский процесс. Для чего это делается? Во-первых, если демон запущен как обычная команда оболочки, завершив родительский процесс, мы заставим командную оболочку думать, что команда выполнилась. Во-вторых, дочерний процесс наследует идентификатор группы процессов от родителя, но получает свой идентификатор процесса; тем самым гарантируется, что дочерний процесс не будет являться лидером группы, а это необходимое условие для вызова функции setsid, который будет произведен далее.

3. Создать новый сеанс, обратившись к функции setsid. При этом (вспомните раздел 9.5) процесс становится (а) лидером нового сеанса, (б) лидером новой группы процессов и (в) лишается управляющего терминала.

Для систем, основанных на System V, некоторые специалисты рекомендуют в этой точке повторно вызвать функцию fork и завершить родительский процесс, чтобы второй потомок продолжал работу в качестве демона. Такой прием гарантирует, что демон не будет являться лидером сеанса, и это препятствует получению управляющего терминала в System V (раздел 9.6). Как вариант, чтобы избежать обретения управляющего терминала, при любом открытии терминального устройства следует указывать флаг O_NOCTTY.

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

5. Закрыть все ненужные файловые дескрипторы. Это предотвращает удержание в открытом состоянии некоторых дескрипторов, унаследованных от родительского процесса (командной оболочки или другого процесса). С помощью нашей функции open_max (листинг 2.4) или с помощью функции getrlimit (раздел 7.11) можно определить максимально возможный номер дескриптора и закрыть все дескрипторы вплоть до этого номера.

6. Некоторые демоны открывают файловые дескрипторы с номерами 0, 1 и 2 на устройстве /dev/null, — таким образом, любые библиотечные функции, которые пытаются читать со стандартного устройства ввода или писать в стандартное устройство вывода или сообщений об ошибках, не будут оказывать никакого влияния. Поскольку демон не связан ни с одним терминальным устройством, он не сможет взаимодействовать с пользователем в интерактивном режиме. Даже если демон запущен в интерактивном сеансе, он все равно переходит в фоновый режим, и начальный сеанс может завершиться без воздействия на процесс-демон. С этого же терминала в систему могут входить другие пользователи, и демон не должен выводить какую-либо информацию на терминал, да и пользователи не ждут, что их ввод с терминала будет прочитан демоном.

Пример
В листинге 13.1 приводится функция, которую может вызывать приложение, желающее стать демоном.

Листинг 13.1. Инициализация процесса-демона

#include "apue.h"
#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>

void
daemonize(const char *cmd)
{

        int                 i, fd0, fd1, fd2;
        pid_t               pid;
        struct rlimit       rl;
        struct sigaction    sa;

        /*
         * Сбросить маску режима создания файла.
         */
        umask(0);
        /*
         * Получить максимально возможный номер дескриптора файла.
         */
        if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
            err_quit("%s: невозможно получить максимальный номер дескриптора ", cmd);

        /*
         * Стать лидером нового сеанса, чтобы утратить управляющий терминал.
         */
        if ((pid = fork()) < 0)
            err_quit("%s: ошибка вызова функции fork", cmd);
        else if (pid != 0) /* родительский процесс */
            exit(0);
        setsid();

        /*
         * Обеспечить невозможность обретения управляющего терминала в будущем.
         */
        sa.sa_handler = SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        if (sigaction(SIGHUP, &sa, NULL) < 0)
            err_quit("%s: невозможно игнорировать сигнал SIGHUP", cmd);
        if ((pid = fork()) < 0)
            err_quit("%s: ошибка вызова функции fork", cmd);
        else if (pid != 0) /* родительский процесс */
            exit(0);

        /*
         * Назначить корневой каталог текущим рабочим каталогом,
         * чтобы впоследствии можно было отмонтировать файловую систему.
         */
        if (chdir("/") < 0)
            err_quit("%s: невозможно сделать текущим рабочим каталогом /", cmd);

        /*
         * Закрыть все открытые файловые дескрипторы.
         */
        if (rl.rlim_max == RLIM_INFINITY)
            rl.rlim_max = 1024;
        for (i = 0; i < rl.rlim_max; i++)
        close(i);

        /*
         * Присоединить файловые дескрипторы 0, 1 и 2 к /dev/null.
         */
        fd0 = open("/dev/null", O_RDWR);
        fd1 = dup(0);
        fd2 = dup(0);

        /*
         * Инициализировать файл журнала.
         */
        openlog(cmd, LOG_CONS, LOG_DAEMON);
        if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
            syslog(LOG_ERR, "ошибочные файловые дескрипторы %d %d %d",
                   fd0, fd1, fd2);
            exit(1);
        }
    }

Если функцию daemonize вызвать из программы, которая затем приостанавливает работу, мы сможем проверить состояние демона с помощью команды ps:

$ ./a.out
$ ps -efj
UID     PID   PPID   PGID   SID   TTY  CMD
sar   13800      1  13799 13799   ?    ./a.out
$ ps -efj | grep 13799
sar   13800      1  13799 13799   ?    ./a.out

С помощью команды ps можно также убедиться, что в системе нет активного процесса с идентификатором 13799. Это означает, что наш демон относится к осиротевшей группе процессов (раздел 9.10) и не является лидером сеанса, а поэтому не имеет возможности обрести управляющий терминал. Это результат второго вызова функции fork в функции daemonize. Как видите, наш демон инициализирован правильно.

13.4. Журналирование ошибок


Одна из проблем, присущих демонам, связана с обслуживанием сообщений об ошибках. Демон не может просто выводить сообщения в стандартное устройство вывода сообщений об ошибках, поскольку не имеет управляющего терминала. Мы не можем требовать от демона, чтобы он выводил сообщения в консоль, поскольку на большинстве рабочих станций в консоли запускается многооконная система. Мы также не можем требовать, чтобы демон хранил свои сообщения в отдельном файле. Это стало бы источником постоянной головной боли для системного администратора, который будет вынужден запоминать, в какой файл каждый демон пишет свои сообщения. Необходим некий централизованный механизм регистрации сообщений об ошибках.

Механизм syslog для BSD-систем был разработан в Беркли и получил широкое распространение начиная с 4.2BSD. Большинство систем, производных от BSD, поддерживают syslog. До появления SVR4 ОС System V не имела централизованного механизма регистрации сообщений об ошибках. Функция syslog была включена в стандарт Single UNIX Specification как расширение XSI.

Механизм syslog для BSD-систем широко используется начиная с 4.2BSD. Большинство демонов используют именно этот механизм. На рис. 13.1 показана его структура. Существует три способа регистрации сообщений.

1. Процедуры ядра могут обращаться к функции log. Эти сообщения доступны любому пользовательскому процессу, который может открыть и прочитать устройство /dev/klog. Мы не будем рассматривать эту функцию, поскольку не собираемся писать процедуры ядра.

2. Большинство пользовательских процессов (демонов) для регистрации сообщений вызывают функцию syslog(3). Порядок работы с ней мы рассмотрим позже. Эта функция отправляет сообщения через сокет домена UNIX — /dev/log.

3. Пользовательский процесс, выполняющийся на данном или на другом компьютере, соединенном с данным компьютером сетью TCP/IP, может отправлять сообщения по протоколу UDP на порт 514. Обратите внимание, что функция syslog никогда не генерирует дейтаграммы UDP — данная функциональность требует, чтобы сама программа поддерживала сетевые взаимодействия.

image

За дополнительной информацией о сокетах домена UNIX обращайтесь к [Stevens, Fenner, and Rudoff, 2004]. Обычно демон syslogd понимает все три способа регистрации сообщений. На запуске этот демон читает конфигурационный файл (как правило, /etc/syslog.conf), в котором определяется, куда должны передаваться сообщения различных классов. Например, срочные сообщения могут выводиться в консоль системного администратора (если он находится в системе), тогда как предупреждения могут записываться в файл.

В нашем случае взаимодействие с этим механизмом осуществляется посредством функции syslog.

#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
int setlogmask(int maskpri);

Возвращает предыдущее значение маски приоритета журналируемых сообщений

Функцию openlog можно не вызывать. Если перед первым обращением к функции syslog функция openlog не вызывалась, она будет вызвана автоматически. Вызывать функцию closelog также необязательно — она просто закрывает файловый дескриптор, который использовался для взаимодействия с демоном syslogd.

Функция openlog позволяет определить в аргументе ident строку идентификации, которая обычно содержит имя программы (например, cron или inetd). Аргумент option — это битовая маска, которая определяет различные способы вывода сообщений. В табл. 13.1 приводятся значения, которые могут быть включены в маску. В столбце XSI отмечены те из них, которые стандарт Single UNIX Specification включает в определение функции openlog.

Возможные значения аргумента facility приводятся в табл. 13.2. Обратите внимание, что стандарт Single UNIX Specification определяет лишь часть значений, обычно доступных в конкретной системе. Аргумент facility позволяет определить, как должны обрабатываться сообщения из разных источников. Если программа не вызывает функцию openlog или передает ей в аргументе facility значение 0, указать источник сообщения можно с помощью функции syslog, определив его как часть аргумента priority.

Функция syslog вызывается для передачи сообщения. В аргументе priority передается комбинация из значения для аргумента facility (табл. 13.2) и уровня важности сообщения (табл. 13.3). Уровни важности перечислены в табл. 13.3 в порядке убывания, от высшего к низшему.

Таблица 13.1. Возможные значения, которые могут быть включены в аргумент option функции openlog

image

Аргумент format и все последующие аргументы передаются функции vsprintf для создания строки сообщения. Символы %m в строке формата заменяются сообщением об ошибке (strerror), которое соответствует значению переменной errno.

Функция setlogmask может использоваться для установки маски приоритетов сообщений процесса. Эта функция возвращает предыдущее значение маски. Если маска приоритетов установлена, сообщения, уровень приоритета которых не содержится в маске, не будут журналироваться. Обратите внимание: из вышесказанного следует, что если маска имеет значение 0, журналироваться будут все сообщения.

Во многих системах имеется также программа logger(1), которая может передавать сообщения механизму syslog. Некоторые реализации позволяют передавать программе необязательные аргументы, в которых указывается источник сообщения (facility), уровень важности (level) и строка идентификации (ident), хотя стандарт System UNIX Specification не определяет дополнительных аргументов. Команда logger предназначена для использования в сценариях на языке командной оболочки, которые выполняются в неинтерактивном режиме и нуждаются в механизме журналирования сообщений.

Таблица 13.2. Возможные значения аргумента facility функции openlog
image

image

Таблица 13.3. Уровни серьезности сообщений (в порядке убывания)
image

Пример
В (гипотетическом) демоне печати можно встретить следующие строки:

openlog("lpd", LOG_PID, LOG_LPR);
syslog(LOG_ERR, "open error for %s: %m", filename);

Обращение к функции openlog устанавливает строку идентификации с именем программы, указывает, что идентификатор процесса обязательно должен добавляться к сообщению, и оговаривает, что источником сообщений будет демон системы печати. В вызове функции syslog указан уровень важности сообщения и само сообщение. Если опустить вызов функции openlog, вызов syslog мог бы выглядеть так:

syslog(LOG_ERR | LOG_LPR, "open error for %s: %m", filename);

Здесь в аргументе priority мы объединили ссылку на источник сообщения и уровень важности сообщения.

Кроме функции syslog многие платформы поддерживают ее разновидность, которая принимает дополнительные аргументы в виде списка переменной длины.

#include <syslog.h>
#include <stdarg.h>

void vsyslog(int priority, const char *format, va_list arg);

Все четыре платформы, обсуждаемые в данной книге, поддерживают функцию vsyslog, но она не входит в состав стандарта Single UNIX Specification. Обратите внимание: чтобы сделать эту функцию доступной в своем приложении, может потребоваться определить дополнительный символ, такой как __BSD_VISIBLE в FreeBSD или __USE_BSD в Linux.

Большинство реализаций syslogd для сокращения времени обработки запросов от приложений помещают поступившие сообщения в очередь. Если в это время демону поступит два одинаковых сообщения, в журнал будет записано только одно. Но в конец такого сообщения демоном будет добавлена строка примерно такого содержания: «last message repeated N times» (последнее сообщение было повторено N раз).

13.5. Демоны в единственном экземпляре


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

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

Одним из основных механизмов, обеспечивающих ограничение количества одновременно работающих копий демона, являются блокировки файлов и записей. (Блокировки файлов и записей в файлах мы рассмотрим в разделе 14.3.) Если каждый из демонов создаст файл и попытается установить для этого файла блокировку для записи, система разрешит установить только одну такую блокировку. Все последующие попытки установить блокировку для записи будут терпеть неудачу, сообщая остальным копиям демона, что демон уже запущен.
Блокировки файлов и записей — это удобный механизм взаимного исключения. Если демон установит блокировку для целого файла, она будет автоматически снята по завершении демона. Это упрощает процедуру восстановления после ошибок, поскольку снимает необходимость удаления блокировки, оставшейся от предыдущей копии демона.

Пример
Функция в листинге 13.2 демонстрирует использование блокировок файлов и записей, чтобы обеспечить запуск единственного экземпляра демона.

Листинг 13.2. Функция, которая гарантирует запуск только одной копии демона

#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>     

#define LOCKFILE "/var/run/daemon.pid"
#define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)

extern int lockfile(int);

int
already_running(void)
{
        int     fd;
        char    buf[16];

        fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE);
        if (fd < 0) {
            syslog(LOG_ERR, "невозможно открыть %s: %s",
                   LOCKFILE, strerror(errno));
            exit(1);
        }
        if (lockfile(fd) < 0) {
            if (errno == EACCES || errno == EAGAIN) {
                close(fd);
                return(1);
            }
            syslog(LOG_ERR, "невозможно установить блокировку на %s: %s",
                   LOCKFILE, strerror(errno));
            exit(1);
        }
        ftruncate(fd, 0);
        sprintf(buf, "%ld", (long)getpid());
        write(fd, buf, strlen(buf)+1);
        return(0);
    }

Каждая копия демона будет пытаться создать файл и записать в него свой идентификатор процесса. Это поможет системному администратору идентифицировать процесс. Если файл уже заблокирован, функция lockfile завершится неудачей с кодом ошибки EACCESS или EAGAIN в переменной errno и в вызывающую программу вернет значение 1, указывающее, что демон уже запущен. Иначе функция усекает размер файла до нуля, записывает в него идентификатор процесса и возвращает значение 0.

Усечение размера файла необходимо, потому что идентификатор процесса предыдущей копии демона, представленный в виде строки, мог иметь большую длину. Предположим, например, что ранее запускавшаяся копия демона имела идентификатор процесса 12345, а текущая копия имеет идентификатор процесса 9999. То есть когда этот демон запишет свой идентификатор, в файле окажется строка 99995. Операция усечения файла удаляет информацию, которая относится к предыдущей копии демона.

13.6. Соглашения для демонов


В системе UNIX демоны придерживаются следующих соглашений.

  • Если демон использует файл блокировки, этот файл помещается в каталог /var/run. Однако, чтобы создать файл в этом каталоге, демон должен обладать привилегиями суперпользователя. Имя файла обычно имеет вид name.pid, где name — имя демона или службы. Например, демон cron создает файл блокировки с именем /var/run/crond.pid.
  • Если демон поддерживает определение дополнительных настроек, они обычно хранятся в каталоге /etc. Имя конфигурационного файла, как правило, имеет вид name.conf, где name — имя демона или службы. Например, конфигурационный файл демона syslogd называется /etc/syslog.conf.
  • Демоны могут запускаться из командной строки, но чаще запуск демонов производится из сценариев инициализации системы (/etc/rc* или /etc/init.d/*). Если после завершения демон должен автоматически перезапускаться, мы можем указать на это процессу init, добавив запись respawn в файл /etc/inittab (предполагается, что система использует команду init в стиле System V).
  • Если демон имеет конфигурационный файл, настройки из него читаются демоном во время запуска, и затем он обычно не обращается к этому файлу. Если в конфигурационный файл были внесены изменения, демон пришлось бы останавливать и перезапускать снова, чтобы новые настройки вступили в силу. Во избежание этого некоторые демоны устанавливают обработчики сигнала SIGHUP, в которых производится чтение конфигурационного файла и перенастройка демона. Поскольку демоны не имеют управляющего терминала и являются либо лидерами сеансов без управляющего терминала, либо членами осиротевших групп процессов, у них нет причин ожидать сигнала SIGHUP. Поэтому он может использоваться для других целей.

Пример
Программа в листинге 13.3 демонстрирует один из способов заставить демон перечитать файл конфигурации. Программа использует функцию sigwait и отдельный поток для обработки сигналов, как описано в разделе 12.8.

Листинг 13.3. Пример демона, который перечитывает конфигурационный файл по сигналу

#include "apue.h"
#include <pthread.h>
#include <syslog.h>

sigset_t mask;
extern int already_running(void);

void
reread(void)
{
        /* ... */
    }

void *
thr_fn(void *arg)
{
        int err, signo;

        for (;;) {
            err = sigwait(&mask, &signo);
            if (err != 0) {
                syslog(LOG_ERR, "ошибка вызова функции sigwait");
                exit(1);
            }

            switch (signo) {
            case SIGHUP:
                syslog(LOG_INFO, "Чтение конфигурационного файла");
                reread();
                break;
            case SIGTERM:
                syslog(LOG_INFO, "получен сигнал SIGTERM; выход");
                exit(0);
            default:
                syslog(LOG_INFO, "получен непредвиденный сигнал %d\n", signo);
            }
        }
        return(0);
    }

    int
    main(int argc, char *argv[])
    {
        int              err;
        pthread_t        tid;
        char             *cmd;
        struct sigaction sa;

        if ((cmd = strrchr(argv[0], '/')) == NULL)
            cmd = argv[0];
        else
            cmd++;

        /*
         * Перейти в режим демона.
         */
        daemonize(cmd);

        /*
         * Убедиться, что ранее не была запущена другая копия демона.
         */
        if (already_running()) {
            syslog(LOG_ERR, "демон уже запущен");
            exit(1);
        }

        /*
         * Восстановить действие по умолчанию для сигнала SIGHUP
         * и заблокировать все сигналы.
         */
        sa.sa_handler = SIG_DFL;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        if (sigaction(SIGHUP, &sa, NULL) < 0)
            err_quit("%s: невозможно восстановить действие SIG_DFL для SIGHUP");
        sigfillset(&mask);
        if ((err = pthread_sigmask(SIG_BLOCK, &mask, NULL)) != 0)
            err_exit(err, "ошибка выполнения операции SIG_BLOCK");

        /*
         * Создать поток для обработки SIGHUP и SIGTERM.
         */
        err = pthread_create(&tid, NULL, thr_fn, 0);
        if (err != 0)
            err_exit(err, "невозможно создать поток");

        /*
         * Остальная часть программы-демона.
         */
        /* ... */
        exit(0);
    }

Для перехода в режим демона программа использует функцию daemonize из листинга 13.1. После возврата из нее вызывается функция already_running из листинга 13.2, которая проверяет наличие других запущенных копий демона. В этой точке сигнал SIGHUP все еще игнорируется, поэтому мы должны переустановить его диспозицию в значение по умолчанию, иначе функция sigwait никогда не сможет получить его.

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

В табл. 10.1 указано, что по умолчанию сигналы SIGHUP и SIGTERM завершают процесс. Поскольку эти сигналы заблокированы, демон не будет завершаться, если получит один из них. Вместо этого поток, вызывая sigwait, будет получать номера доставленных сигналов.

Пример
Программа в листинге 13.4 показывает, как демон может перехватить сигнал SIGHUP и выполнить повторное чтение конфигурационного файла, не используя для этого отдельный поток.

Листинг 13.4. Альтернативная реализация демона, который перечитывает конфигурационный файл по сигналу

#include "apue.h"
#include <syslog.h>
#include <errno.h>
    
extern int lockfile(int);
extern int already_running(void);

void
reread(void)
{
        /* ... */
}

void
sigterm(int signo)
{
        syslog(LOG_INFO, "получен сигнал SIGTERM; выход");
        exit(0);
}

void
sighup(int signo)
{
        syslog(LOG_INFO, "Чтение конфигурационного файла");
        reread();
}


int
main(int argc, char *argv[])
{
        char             *cmd;
        struct sigaction sa;
    
        if ((cmd = strrchr(argv[0], '/')) == NULL)
            cmd = argv[0];
        else
            cmd++;
     
        /*
         * Перейти в режим демона.
         */
        daemonize(cmd);  

        /*
         * Убедиться, что ранее не была запущена другая копия демона.
         */
        if (already_running()) {
            syslog(LOG_ERR, "демон уже запущен");
            exit(1);
        }

        /*
         * Установить обработчики сигналов.
         */
        sa.sa_handler = sigterm;
        sigemptyset(&sa.sa_mask);
        sigaddset(&sa.sa_mask, SIGHUP);
        sa.sa_flags = 0;
        if (sigaction(SIGTERM, &sa, NULL) < 0) {
            syslog(LOG_ERR, "невозможно перехватить сигнал SIGTERM: %s",
                   strerror(errno));
            exit(1);
        }
        sa.sa_handler = sighup;
        sigemptyset(&sa.sa_mask);
        sigaddset(&sa.sa_mask, SIGTERM);
        sa.sa_flags = 0;
        if (sigaction(SIGHUP, &sa, NULL) < 0) {
            syslog(LOG_ERR, "невозможно перехватить сигнал SIGHUP: %s",
                   strerror(errno));
            exit(1);
        }

        /*
         * Остальная часть программы-демона.
         */
         /* ... */
         exit(0);
}

13.7. Модель клиент-сервер


Наиболее часто процессы-демоны используются в качестве серверных процессов. На рис. 13.1 показан пример взаимодействия с сервером syslogd, который получает сообщения от приложений (клиентов) посредством сокета домена UNIX.

Вообще, под сервером подразумевается некий процесс, который ожидает запросов на предоставление определенных услуг клиентам. Так, на рис. 13.1 сервер syslogd предоставляет услуги журналирования сообщений об ошибках.

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

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

Самое простое решение этой проблемы: установить флаг закрытия при вызове функции exec (close-on-exec) для всех файловых дескрипторов, которые не требуются запускаемой программе. В листинге 13.5 приводится функция, которую можно использовать в серверном процессе для этих целей.

Листинг 13.5. Установка флага закрытия при вызове exec

#include "apue.h"
#include <fcntl.h>

int
set_cloexec(int fd)
{
        int     val;

        if ((val = fcntl(fd, F_GETFD, 0)) < 0)
            return(-1);

        val |= FD_CLOEXEC; /* установить флаг закрытия при вызове exec */

        return(fcntl(fd, F_SETFD, val));
}

13.8. Подведение итогов


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

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

» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок

Для Хаброжителей скидка 20% по купону — UNIX

Комментарии (2)


  1. Varim
    19.02.2018 18:54

    Интересен год оригинала, если прошло 10 лет, наверное за это время многое могло помнятся в unix/linux и в языке C. Или нет.