
- •Часть 3. Подсистема управления процессами
- •Глава 7. Среда окружения процесса 2
- •Глава 8. Управление процессами 16
- •Глава 9. Взаимоотношения между процессами 49
- •Глава 10. Сигналы 62
- •Глава 11. Процессы-демоны 98
- •Глава 7. Среда окружения процесса
- •7.1. Введение
- •7.2. Функция main
- •7.3. Завершение работы процесса
- •7.3.1. Функции семейства exit
- •7.3.2. Функция atexit
- •7.4. Аргументы командной строки
- •7.5. Раскладка памяти программы на языке c
- •7.6. Разделяемые библиотеки
- •7.7. Распределение памяти
- •7.8. Список переменных окружения
- •7.9. Функции getrlimit и setrlimit
- •7.10. Выводы по главе 7
- •7.11. Упражнения по главе 7
- •Глава 8. Управление процессами
- •8.1. Введение
- •8.2. Идентификаторы процесса
- •8.3. Функция fork
- •8.4. Совместное использование файлов
- •8.5. Функция exit
- •8.6. Функции wait и waitpid
- •8.7. Гонка за ресурсами
- •8.8. Функция exec
- •8.9. Изменение идентификаторов пользователя и группы
- •8.9.1. Функции setreuid и setregid
- •8.9.2. Функции seteuid и setegid
- •8.9.3. Идентификаторы группы
- •8.10. Интерпретируемые файлы
- •8.11. Функция system
- •8.12. Идентификация пользователя
- •8.13. Временные характеристики процесса
- •8.14. Выводы по главе 8
- •8.15. Упражнения по главе 8
- •Глава 9. Взаимоотношения между процессами
- •9.1. Введение
- •9.2. Вход с терминала
- •9.2.1. Вход в систему с терминала в bsd-системах
- •9.2.2. Вход в систему с терминала в Linux
- •9.3. Вход в систему через сетевое соединение
- •9.3.1. Вход в систему через сетевое соединение в bsd-системах
- •9.3.2. Вход в систему через сетевое соединение в Linux
- •9.4. Группы процессов
- •9.5. Сессии
- •9.6. Управляющий терминал
- •9.7. Функции tcgetpgrp, tcsetpgrp и tcgetsid
- •9.8. Выводы по главе 9
- •9.9. Упражнения по главе 9
- •Глава 10. Сигналы
- •10.1. Введение
- •10.2. Концепция сигналов
- •10.3. Функция signal
- •10.3.1. Запуск программы
- •10.3.2. Создание процесса
- •10.4. Ненадежные сигналы
- •10.5. Прерванные системные вызовы
- •10.6. Реентерабельные функции
- •10.7. Надежные сигналы. Терминология и семантика
- •10.8. Функции kill и raise
- •10.9. Функции alarm и pause
- •10.10. Наборы сигналов
- •10.11. Функция sigprocmask
- •10.12. Функция sigpending
- •10.13. Функция sigaction
- •10.14. Функция sigsuspend
- •10.15. Функция abort
- •10.16. Функция system
- •10.17. Функция sleep
- •10.18. Дополнительные возможности
- •10.19. Выводы по главе 10
- •10.20. Упражнения по главе 10
- •Глава 11. Процессы-демоны
- •11.1. Введение
- •11.2. Характеристики демонов
- •11.3. Правила программирования демонов
- •11.4. Журналирование ошибок
- •11.5. Демоны в единственном экземпляре
- •11.6. Соглашения для демонов
- •11.7. Модель клиент-сервер
- •11.8. Выводы по главе 11
- •11.9. Упражнения по главе 11
11.3. Правила программирования демонов
При программировании демонов во избежание нежелательных взаимодействий следует придерживаться определенных правил. Сначала мы перечислим эти правила, а затем продемонстрируем функцию daemonize, которая их реализует.
Прежде всего, нужно вызвать функцию umask, чтобы сбросить маску режима создания файла в значение 0. Маска режима создания файла, наследуемая от запускающего процесса, может маскировать некоторые биты прав доступа. Если предполагается, что процесс-демон будет создавать файлы, может потребоваться установка определенных битов прав доступа. Например, если демон создает файлы с правом на чтение и на запись для группы, маска режима создания файла, которая выключает любой из этих битов, воспрепятствовала бы этому.
Вызвать функцию fork и завершить родительский процесс. Для чего это делается? Во-первых, если демон был запущен из оболочки как обычная команда, то, завершив родительский процесс, мы заставим командную оболочку думать, что команда была выполнена. Во-вторых, дочерний процесс наследует идентификатор группы процессов от родителя, но получает свой идентификатор процесса; таким образом мы гарантируем, что дочерний процесс не будет являться лидером группы, а это необходимое условие для вызова функции setsid, который будет произведен далее.
Создать новую сессию, обратившись к функции setsid. При этом (вспомните раздел 9.5) процесс становится (а) лидером новой сессии, (б) лидером новой группы процессов и (в) лишается управляющего терминала.
Сделать корневой каталог текущим рабочим каталогом. Текущий рабочий каталог, унаследованный от родительского процесса, может находиться вне пределов корневой файловой системы. Поскольку демон, как правило, существует все время, пока система не будет перезагружена, то в подобной ситуации, если рабочий каталог демона находится в файловой системе, отличной от корневой, ее невозможно будет размонтировать.
Как вариант, некоторые демоны могут устанавливать собственный текущий рабочий каталог, в котором они производят все необходимые действия. Например, демоны печати в качестве текущего рабочего каталога часто выбирают буферный каталог, куда помещаются задания на печать.
Закрыть все ненужные файловые дескрипторы. Это предотвращает удержание в открытом состоянии некоторых дескрипторов, унаследованных от родительского процесса (командной оболочки или другого процесса). С помощью функции getrlimit (раздел 7.9) можно определить максимально возможный номер дескриптора и закрыть все дескрипторы вплоть до этого номера.
Некоторые демоны открывают файловые дескрипторы с номерами 0, 1 и 2 на устройстве /dev/null – таким образом, любые библиотечные функции, которые пытаются читать со стандартного устройства ввода или писать на стандартное устройство вывода (стандартное устройство вывода сообщений об ошибках), не будут оказывать на демон никакого влияния. Поскольку демон не связан ни с одним терминальным устройством, он не сможет взаимодействовать с пользователем в интерактивном режиме. Даже если демон изначально был запущен в рамках интерактивной сессии, он все равно переходит в фоновый режим, и начальная сессия может завершиться без воздействия на процесс-демон. С этого же терминала в систему могут входить другие пользователи, и демон не должен выводить какую-либо информацию на терминал, да и пользователи не ждут того, что их ввод с терминала будет прочитан демоном.
Пример. В листинге 11.1 приводится функция, которую может вызвать приложение, желающее стать демоном.
Листинг 11.1. Инициализация процесса-демона
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <syslog.h>
#include <sys/resource.h>
#include <sys/types.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)
{
fprintf(stderr, "%s: невозможно получить максимальный номер дескриптора\n",
cmd, strerror(errno));
exit(1);
}
/* стать лидером новой сессии, чтобы утратить управляющий терминал */
if ((pid = fork()) < 0)
{
fprintf(stderr, "%s: ошибка вызова функции fork\n", cmd, strerror(errno));
exit(1);
}
else
if (pid != 0) /* родительский процесс */
exit(0);
setsid();
/* игнорировать сигнал SIGHUP в дальнейшей работе */
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
fprintf(stderr, "%s: невозможно игнорировать сигнал SIGHUP\n", cmd,
strerror(errno));
exit(1);
}
/* назначить корневой каталог текущим рабочим каталогом,
чтобы впоследствии можно было размонтировать файловую систему */
if (chdir("/") < 0)
{
fprintf(stderr, "%s: невозможно перейти в корневой каталог\n", cmd,
strerror(errno));
exit(1);
}
/* закрыть все открытые файловые дескрипторы */
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);
}
}