Oct 16, 2011| Гареев Роман, кн 301
Демон - это фоновый процесс, разработанный специально для автономной работы, с минимальным вмешательством пользователя или вообще без него. HTTP-демон (httpd) веб-сервера Apache является одним из примеров таких процессов. Он работает в фоновом режиме, слушая специфичные порты, и обслуживает страницы или выполняет скрипты, в зависимости от вида запроса. Перед автором стояла задача реализовать демон-планировщик, т. е. демон, запускающий указанные команды оболочки, в зависимости от различных событий файловой системы, таких как создание, удаление, перемещение и т.п. Данный текст описывает основные этапы его создания на языке С++ с использованием inotify (подсистемы ядра Linux).
Функциональность
Прежде всего, необходимо определиться с задачей, которую будет решать демон. Обычно она одна, независимо от того управление ли это сотнями почтовых ящиков во множестве доменов или формирование отчета и отправка его администратору. На функциональность моего демона сильно повлияли возможности inotify, о которой пойдет дальнейшее повествование.
Inotify появилась в ядре Linux, начиная с версии 2.6.13. С её помощью программы мониторинга могут открывать один файловый дескриптор и наблюдать через него за одним или несколькими файлами или директориями, отслеживая указанный набор событий, таких как открытие, закрытие, перемещение, переименование, удаление, создание или изменение атрибутов. Предшественницей inotify была подсистема dnotify, содержащая некоторые ограничения, которые были убраны.
В inotify используется один файловый дескриптор, тогда как dnotify открывает по одному файловому дескриптору для каждой директории, за изменениями в которых будет вестись наблюдение. Это может привести к достижению ограничения на количество файловых дескрипторов, открываемых процессом, и к большим потерям памяти.
Используемый в inotify файловый дескриптор получается посредством системного вызова и не связан с каким-либо устройством или файлом. В случае dnotify файловый дескриптор привязан к директории. Из-за этого возникает проблема при работе со сменными носителями - нельзя демонтировать устройство, на котором расположена директория. В случае inotify при демонтировании файловой системы расположенный на ней наблюдаемый файл или директория генерируют специальное событие, после чего наблюдение автоматически снимается.
Inotify может наблюдать как за файлами, так и за директориями, тогда как Dnotify - только за директориями. Поэтому для того, чтобы узнать, что произошло с содержимым директории, приходилось хранить структуру stat или эквивалентную ей структуру данных, содержащую информацию о находящихся в директории файлах, а затем при наступлении события сравнивать ее с текущим состоянием.
В inotify используется файловый дескриптор. Это предоставляет возможность использовать для наблюдения за событиями стандартные функции select или poll, позволяющие организовать эффективный параллельный ввод/вывод или интегрироваться с основным циклом событий (mainloop) библиотеки Glib. В dnotify же используются сигналы. Начиная с версии ядра 2.6.25 в inotify также были добавлены уведомления на основе сигналов.
Можно наблюдать за различными типами событий. Некоторые события применимы только для самого наблюдаемого элемента (например, события IN_DELETE_SELF), тогда как другие, например, IN_ATTRIB или IN_OPEN, применимы как для наблюдаемого элемента, так и, в случае директории, для файлов внутри директории. Мой демон поддерживает основные события inotify, перечисленные ниже:
Был осуществлен доступ к наблюдаемому объекту или файлу внутри наблюдаемой директории. Например, был прочитан открытый файл.
Был изменен наблюдаемый объект или файл внутри наблюдаемой директории. Например, был обновлен открытый файл.
Были изменены метаданные наблюдаемого объекта или файла внутри наблюдаемой директории. Например, были изменены временные метки или права доступа.
Были закрыты файл или директория, открытые ранее только для чтения.
Были открыты файл или директория.
Наблюдаемый объект или элемент в наблюдаемой директории был перемещен из места наблюдения. Событие включает в себя cookie, по которому его можно сопоставить с событием
Файл или директория были перемещены в наблюдаемую директорию. Событие включает в себя cookie, как и событие IN_MOVED_FROM. Если файл или директория просто переименовать, то произойдут оба события. Если объект перемещается в/из директории, за которой не установлено наблюдение, мы увидим только одно событие. При перемещении или переименовании объекта наблюдение за ним продолжается. См. также событие IN_MOVE-SELF ниже.
Маска, представляющая собой логическое ИЛИ для двух предыдущих событий перемещения (IN_MOVED_FROM | IN_MOVED_TO).
В наблюдаемой директории были созданы файл или поддиректория.
В наблюдаемой директории были удалены файл или поддиректория.
Наблюдаемый объект был удален. Наблюдение завершается и генерируется событие IN_IGNORED.
Наблюдаемый объект был перемещен.
Создание демона:
Когда демон запускается, он выполняет некоторую низкоуровневую работу для подготовки себя к основной работе. Первая включает в себя несколько шагов:
Отделение от родительского процесса
Демон запускается либо самой системой, либо пользователем в терминале или скрипте. Во время запуска его процесс ничем не отличается от любого другого процесса в системе. Чтобы сделать его автономным, нужно создать дочерний процесс, в котором будет выполняться код демона. Для этого используется функция fork():
Функция fork() возвращает либо id дочернего процесса (PID, не равный нулю), либо -1 в случае ошибки. Если процесс не может породить потомка, то демон должен завершиться прямо здесь.
Если получение PID от fork() совершилось успешно, то завершаем родительский процесс. После ответвления дочерний процесс продолжает выполнение остального кода с этого места.
Нужно отметить, что при разработке демона необходимо делать код максимально стабильным. Поэтому большую часть всего кода демона составляют проверки на ошибки.
Изменение файловой маски (Umask)
Чтобы иметь возможность писать в любые файлы (включая журналы), созданные демоном, файловая маска (umask) должна быть изменена так, чтобы они могли быть записаны или прочитаны правильным образом
/* Изменяем файловую маску */ umask(0);
Через установку umask в 0 можно получить полный доступ к файлам, созданным демоном. Даже не планируется использовать какие-либо файлы, установка umask может понадобиться на случай доступа к файлам на файловой системе.
Открытие журналов на запись
Обычно файл журнала содержит отладочную информацию от демона, его текущее состояние, успешность выполнения им операций. Желательно, чтобы файлы журнала имели однозначно определяющее их имя, помогающее избежать путаницы. Для этого автор использует дату и время запуска в качестве имени.
Создание уникального ID сессии (SID)
Для нормальной работы дочерний процесс должен получить уникальный SID от ядра. Иначе дочерний процесс станет “сиротой”.
Функция setsid() возвращает данные того же типа что и fork(). Чтобы проверить, что создался SID для дочернего процесса используется аналогичная процедура проверки на ошибки.
Изменение рабочего каталога
Текущий рабочий каталог желательно сменить на место, гарантированно присутствующее в системе. Поскольку многие дистрибутивы Linux не полностью следуют стандарту иерархии файловой системы Linux (FHS, Filesystem Hierarchy Standard), то в системе гарантированно присутствует только корень файловой системы (/). Сменить каталог можно при помощи функции chdir().
Закрытие стандартных файловых дескрипторов
Одним из последних шагов в стартовой настройке демона является закрытие стандартных файловых дескрипторов (STDIN, STDOUT, STDERR). Поскольку демон не может использовать терминал, эти файловые дескрипторы излишни и создают угрозу безопасности. Закрыть их можно при помощи функции close():
Установка обработчиков сигналов прерывания
Сигнал - это событие, которое прекращает нормальное выполнение программы. По умолчанию обработчик будет завершать выполнение процесса для большинства сигналов. Однако, при этом могут происходить утечки памяти, из-за того что некоторые ресурсы останутся захваченными процессом. В моем демоне обрабатываются только SIGTERM (сигнал завершения, являющийся сигналом по умолчанию для утилиты kill) и SIGHUP (сигнал переинициализации). Для этого вводится обработчик сигналов void signal_handler(int sig), используемый в signal в дальнейшем.
В случае SIGTERM происходит закрытие всех файловых дескрипторов, а в случае SIGHUP - перечитывание конфиг файла
С помощью функции void startSigHunting(FILE * hLog) устанавливается обработка сигналов, указанных ранее и проверяется успешность установки для каждого из них.
Инициализация демона:
На этом этапе вызываются функции специфичные для работы демона и активно взаимодействующие с интерфейсом предлагаемым inotify, который будет рассмотрен далее.
inotify_init
- системный вызов, который создает экземпляр inotify и возвращает файловый дескриптор, указывающий на этот экземпляр.
inotify_init1
- похожа на inotify_init, но предоставляет дополнительные флаги. Если флаги не указаны, работает так же, как inotify_init.
inotify_add_watch
- добавляет наблюдение для файла или директории и указывает, за какими событиями следует наблюдать. Имеются флаги, которые определяют, нужно ли добавлять события к существующему наблюдению, осуществлять ли наблюдение только в случае, если наблюдаемый объект является директорией, нужно ли следовать по символическим ссылкам и является ли наблюдение "одноразовым", которое следует остановить при возникновении первого события.
inotify_rm_watch
- удаляет наблюдение из списка наблюдений.
read
- читает из буфера данные об одном или нескольких событиях.
close
- закрывает файловый дескриптор и удаляет все связанные с ним наблюдения. Когда все файловые дескрипторы экземпляра inotify закрываются, все системные ресурсы освобождаются, чтобы ядро могло их повторно использовать.
В ней происходит разбор конфигурационного файла mconfig.txt
В случае успеха iRez примет значение 0, и произойдет открытие файловых дескрипторов для каждой из записей в mconfig.txt
После этого делается проверка успешности открытия всех дискрипторов и в случае успеха вызывается функция vBWatch
В функции void vBWatch(FILE *& hLog, vector< int > inotify_fd, vector< vector
int process_inotify_events (FILE * hLog, queue
int event_check (vector< int > fd) — добавляет созданные ранние файловые дескрипторы в множество типа fd_set и вызывает select, ожидающий поступления событий на них
Все события в inotify имеют тип - структуру следующего вида:
int read_events (FILE * hLog, queue
void handle_event (FILE * hLog, inotify_event_p event, int fd, set
В зависимости от типа события будет запущены команды указанные ранее для директории или файла. Например, так будет обрабатываться событие IN_MODIFY