Мониторинг системной активности при помощи inotify

Создайте собственные приложения или используйте набор утилит с открытым кодом

Inotify – это подсистема Linux®, которая отслеживает операции файловой системы, такие как чтение, запись и создание. Inotify действует в реактивном режиме, удивительно проста в использовании и намного более эффективна, чем, например, мониторинг активности при помощи задания планировщика Cron. Научитесь интегрировать inotify в свои приложения и познакомьтесь с набором средств командной строки для более полной автоматизации системного администрирования.

Мартин Стрейчер (Martin Streicher), независимый web-разработчик, WSO2 Inc

Мартин Стрейчер (Martin Streicher) (Jeff J. Li) - фотографияМартин Стрейчер (Martin Streicher) - независимый web-разработчик и бывший главный редактор Linux Magazine. Он имеет степень магистра компьютерных наук Университета Пардью (Purdue University) и занимается программированием в UNIX-подобных операционных системах с 1986 года. Он коллекционирует предметы искусства и игрушки.



17.03.2009

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

По странному стечению обстоятельств ни одно из этих средств не является реактивным. Конечно, вы можете создать задание cron, повторяющееся с частотой обновления монитора, но это приведет к чрезвычайной затрате ресурсов, что не очень хорошо. Например, если вам необходимо отслеживать входящую информацию на нескольких FTP-серверах, вам придется сканировать целевые каталоги при помощи команды find, чтобы найти новые файлы. Хотя этот способ кажется безобидным, каждый вызов запускает новую оболочку shell с командой find, что требует множества системных вызовов для открытия каталога, его сканирования и так далее. Часто повторяющиеся или многократные задания с выполнением запросов могут быстро накапливаться. Хуже того, постоянные запросы не всегда допустимы. Представьте себе последствия с точки зрения сложности и нагрузки, если проверять изменения будет системный менеджер файлов, вроде Finder в Mac OS X.

Так что же делать администратору? К счастью, вы снова можете обратиться за помощью к своему верному компьютеру.

Знакомство с inotify

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

Использовать inotify просто: создайте дескриптор файла, присоедините одно или несколько наблюдений (наблюдение представляет собой путь к файлу и набор событий) и используйте метод read() для получения информации событий из дескриптора. Вместо того, чтобы впустую тратить процессорные ресурсы на проверки, метод read() ожидает наступления события.

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

А теперь давайте заглянем внутрь inotify, напишем немного кода на C и затем рассмотрим набор средств командной строки, которые вы можете создать и использовать для связывания команд и скриптов с событиями файловой системы. Inotify не выпустит вашего кота погулять ночью, но он умеет запускать cat и wget точно тогда, когда это необходимо.

Для работы с inotify вам понадобится компьютер под управлением Linux с ядром версии 2.6.13 или выше. (Предыдущие версии ядра Linux используют намного менее функциональное средство наблюдения за файлами под названием dnotify.) Если вы не знаете версию установленного ядра, запустите shell и введите команду uname -a:

% uname -a
Linux ubuntu-desktop 2.6.24-19-generic #1 SMP ... i686 GNU/Linux

При наличии ядра версии 2.6.13 и выше ваша система должна поддерживать inotify. Вы также можете проверить наличие на своем компьютере файла /usr/include/sys/inotify.h. Если он присутствует, по всей вероятности, ваше ядро поддерживает inotify.

Примечание: FreeBSD и соответственно Mac OS X поддерживают аналог inotify под названием kqueue. Для получения дополнительной информации на компьютере под управлением FreeBSD введите man 2 kqueue.

Эта статья была подготовлена с использованием ОС Ubuntu Desktop 8.04.1 (кодовое имя Hardy), запущенной на виртуальной машине Parallels Desktop 3.0, в ОС Mac OS X 10.5 Leopard.


API inotify для языка C

Для создания всех видов наблюдения за файловой системой в Inotify используются три системных вызова:

  • inotify_init() создает запрос подсистемы inotify в ядре и возвращает файловый дескриптор в случае успеха и -1 в случае отказа. Как и для других системных вызовов, для диагностики отказов inotify_init() используется проверка errno.
  • inotify_add_watch(), как подсказывает имя этого вызова, добавляет наблюдение. Каждое наблюдение должно содержать путь к файлу и список необходимых событий; при этом каждое событие задается константой, например, IN_MODIFY. Для отслеживания нескольких событий просто используйте логическое или языка C — вертикальную черту (|) — между событиями. Если вызов inotify_add_watch() успешен, он возвращает уникальный идентификатор для зарегистрированного наблюдения; в противном случае возвращается -1. Возвращенный идентификатор можно использовать для изменения или удаления соответствующего наблюдения.
  • inotify_rm_watch() удаляет наблюдение.

Также понадобятся системные вызовы read() и close(). Имея дескриптор, полученный от inotify_init(), используйте read() для ожидания сигналов. Для типичного файлового дескриптора, приложение блокируется в ожидании получения событий, организованных как поток данных. Общий вызов close() для файлового дескриптора, полученного из inotify_init(), удаляет и сбрасывает все активные наблюдения, а также освобождает всю память, использующуюся inotify. (К этому случаю относится обычная оговорка, касающаяся количества ссылок: память, занятая наблюдениями и inotify, будет освобождена только после того, как будут закрыты все файловые дескрипторы, связанные с данным экземпляром.)

Вот и все: мы имеем мощный инструмент, использующий всего три вызова программных интерфейсов (API) и простую, привычную парадигму «все является файлом». Теперь мы готовы перейти к созданию тестового приложения.

Тестовое приложение: наблюдение за событиями

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

Листинг 1. Простое приложение inotify для отслеживания событий создания, удаления и изменения в каталоге.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>

#define EVENT_SIZE  ( sizeof (struct inotify_event) )
#define BUF_LEN     ( 1024 * ( EVENT_SIZE + 16 ) )

int main( int argc, char **argv ) 
{
  int length, i = 0;
  int fd;
  int wd;
  char buffer[BUF_LEN];

  fd = inotify_init();

  if ( fd < 0 ) {
    perror( "inotify_init" );
  }

  wd = inotify_add_watch( fd, "/home/strike", 
                         IN_MODIFY | IN_CREATE | IN_DELETE );
  length = read( fd, buffer, BUF_LEN );  

  if ( length < 0 ) {
    perror( "read" );
  }  

  while ( i < length ) {
    struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
    if ( event->len ) {
      if ( event->mask & IN_CREATE ) {
        if ( event->mask & IN_ISDIR ) {
          printf( "The directory %s was created.\n", event->name );       
        }
        else {
          printf( "The file %s was created.\n", event->name );
        }
      }
      else if ( event->mask & IN_DELETE ) {
        if ( event->mask & IN_ISDIR ) {
          printf( "The directory %s was deleted.\n", event->name );       
        }
        else {
          printf( "The file %s was deleted.\n", event->name );
        }
      }
      else if ( event->mask & IN_MODIFY ) {
        if ( event->mask & IN_ISDIR ) {
          printf( "The directory %s was modified.\n", event->name );
        }
        else {
          printf( "The file %s was modified.\n", event->name );
        }
      }
    }
    i += EVENT_SIZE + event->len;
  }

  ( void ) inotify_rm_watch( fd, wd );
  ( void ) close( fd );

  exit( 0 );
}

Приложение создает запрос inotify при помощи fd = inotify_init(); и добавляет одно наблюдение для отслеживания изменений, создания и удаления файлов в каталоге /home/strike в соответствии с параметрами wd = inotify_add_watch(...). Метод read() блокируется, пока не поступит один или несколько сигналов. Данные сигналов для всех файлов и событий передаются в одном потоке байтов, соответственно цикл приложения преобразует поток байтов в последовательность структур событий.

Описание структуры события в виде конструкции struct языка С приведено в файле заголовка /usr/include/sys/inotify.h., как показано в листинге 2.

Листинг 2. Описание структуры события
struct inotify_event 
{
  int wd; 		   /* Дескриптор наблюдения */
  uint32_t mask;   	/* Маска наблюдения */
  uint32_t cookie;      	/* Сookie для связывания двух событий */
  uint32_t len;		/* Длина имени файла, найденного в поле имени*/
  char name __flexarr;	/* Имя файла, дополненное до конца пустыми значениями */	
}

Поле wd ссылается на наблюдение, связанное с событием. Если запрос inotify содержит несколько наблюдений, вы можете использовать это поле, чтобы определить способ продолжения обработки. Поле mask представляет собой набор битов, определяющий, что случилось. Проверяйте каждый бит отдельно.

Для связывания двух событий между собой, например, когда файл перемещается из одного каталога в другой, используется cookie. Если вы наблюдаете за исходным и целевым каталогами, inotify создает два события перемещения – одно для исходного и одно для целевого каталога - и связывает их вместе при помощи cookie. Для наблюдения за перемещением укажите параметр IN_MOVED_FROM или IN_MOVED_TO, или используйте условное обозначение IN_MOVE для наблюдения за обоими каталогами. Используйте IN_MOVED_FROM и IN_MOVED_TO для проверки типа события.

Наконец, name и len содержат имя файла (но не путь!) и длину имени задействованного файла.

Компиляция кода тестового приложения

Чтобы скомпилировать код, перейдите из каталога /home/strike в свой домашний каталог, сохраните код в файл и запустите компилятор С – обычно в большинстве дистрибутивов Linux это gcc. Затем запустите исполняемый файл, как показано в листинге 3.

Листинг 3. Запуск исполняемого файла
% cc -o watcher watcher.c 
% ./watcher

Вместе с запуском наблюдения откройте втрое окно терминала и используйте команды touch, cat и rm для изменения содержимого вашего домашнего каталога, как показано в листинге 4. После каждого эксперимента перезагружайте ваше новое приложение.

Листинг 4. Используйте touch, cat и rm
% cd $HOME
% touch a b c
The file a was created.
The file b was created.
The file c was created.

% ./watcher  & 
% rm a b c
The file a was deleted.
The file b was deleted.
The file c was deleted.

% ./watcher  & 
% touch a b c
The file a was created.
The file b was created.
The file c was created.

% ./watcher  & 
% cat /etc/passwd >> a
The file a was modified.

% ./watcher  & 
% mkdir d
The directory d was created.

Экспериментируйте с другими доступными флагами наблюдения. Для отслеживания изменений разрешений добавьте в маску параметр IN_ATTRIB.

Советы по использованию inotify

Также можно поэкспериментировать с методами select(), pselect(), poll() и epoll(), чтобы обойтись без блокировок. Это бывает полезно, если вы хотите использовать наблюдения в составе главного цикла GUI-приложения или в качестве демона, который отслеживает другие типы входящих подключений. Просто добавьте дескриптор inotify к набору одновременно отслеживаемых дескрипторов. В листинге 5 показана каноническая форма select().

Листинг 5. Каноническая форма select()
int return_value;
fd_set descriptors;
struct timeval time_to_wait;

FD_ZERO ( &descriptors );
FD_SET( ..., &descriptors );
FD_SET ( fd, &descriptors );

...

time_to_wait.tv_sec = 3;
time.to_waittv_usec = 0;

return_value = select ( fd + 1, &descriptors, NULL, NULL, &time_to_wait);

if ( return_value < 0 ) {
	/* Error */
}

else if ( ! return_value ) {
	/* Timeout */
}

else if ( FD_ISSET ( fd, &descriptors ) ) {
	/* Process the inotify events */
	...
}

else if ...

Метод select() останавливает программу на время, указанное в параметре time_to_wait. В случае возникновения любой активности для какого-либо из файловых дескрипторов набора во время этой паузы выполнение немедленно возобновляется. В противном случае по истечении времени ожидания вызов завершается, позволяя приложению выполнить другие действия, например, среагировать на сигнал мыши или клавиатуры в GUI-приложении.

Ниже приведено еще несколько советов по использованию inotify:

  • Если файл или каталог, находящиеся под наблюдением, удаляются, связанные с ними наблюдения удаляются автоматически (после доставки соответствующего сообщения об удалении).
  • Если вы отслеживаете файл или каталог в демонтируемой файловой системе, ваши наблюдения получают событие демонтирования до удаления всех связанных наблюдений.
  • Добавьте в маску наблюдения флаг IN_ONESHOT, чтобы настроить одноразовое уведомление. После однократной отправки уведомления оно удаляется.
  • Для изменения события укажите тот же путь, но другую маску. Новое наблюдение заменит старое.
  • В любых практических ситуациях вы вряд ли выйдете за предельно допустимое число наблюдений для экземпляра inotify. Тем не менее в зависимости от частоты событий вы можете превысить размер очереди событий. Переполнение очереди вызывает событие IN_Q_OVERFLOW.
  • Метод close() уничтожает экземпляр inotify и все связанные с ним наблюдения и очищает все незавершенные события в очереди.

Установка набора inotify-tools

Программный интерфейс inotify прост в использовании, но если вы предпочитаете не писать собственные программы, существует прекрасная и удобная альтернатива на основе открытого исходного кода. Библиотека Inotify-tools (ссылка приведена в разделе Ресурсы) содержит две утилиты командной строки для наблюдения за активностью файловой системы:

  • inotifywait просто блокируется в ожидании событий inotify. Вы можете отслеживать любой набор файлов и каталогов или дерево каталогов (каталог, его подкаталоги, его подподкаталоги и так далее). Используйте inotifywait в shell-скриптах.
  • inotifywatch собирает статистику по наблюдаемой файловой системе, в том числе о том, сколько раз было зафиксировано каждое событие inotify.

В момент написания этой статьи последней версией библиотеки inotify-tools была 3.13, выпущенная 1 января 2008 года. Существует два способа установить inotify-tools: можно загрузить и собрать программу самостоятельно или установить набор двоичных файлов при помощи менеджера пакетов вашего дистрибутива Linux, если вам известен репозиторий, содержащий inotify-tools. Чтобы сделать последнее в дистрибутиве на основе Debian выполните команду apt-cache search inotifyи поищите подходящие инструменты, как показано в листинге 6. В системе, которую я использовал для подготовки этой статьи (Ubuntu Desktop 8.04), эти инструменты уже есть в наличии.

Листинг 6. Поиск inotify-tools
% apt-cache search inotify
incron - cron-like daemon which handles filesystem events
inotail - tail replacement using inotify
inoticoming - trigger actions when files hit an incoming directory
inotify-tools - command-line programs providing a simple interface to inotify
iwatch - realtime filesystem monitoring program using inotify
libinotify-ruby - Ruby interface to Linux's inotify system
libinotify-ruby1.8 - Ruby interface to Linux's inotify system
libinotify-ruby1.9 - Ruby interface to Linux's inotify system
libinotifytools0 - utility wrapper around inotify
libinotifytools0-dev - Development library and header files for libinotifytools0
liblinux-inotify2-perl - scalable directory/file change notification
muine-plugin-inotify - INotify Plugin for the Muine music player
python-kaa-base - Base Kaa Framework for all Kaa Modules
python-pyinotify - Simple Linux inotify Python bindings
python-pyinotify-doc - Simple Linux inotify Python bindings
% sudo apt-get install inotify-tools
...
Setting up inotify-tools.

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

Листинг 7. Компиляция кода.
% wget \
    http://internap.dl.sourceforge.net/sourceforge/inotify-tools/inotify-tools-3.13.tar.gz
% tar zxvf inotify-tools-3.13.tar.gz
inotify-tools-3.13/
inotify-tools-3.13/missing
inotify-tools-3.13/src/
inotify-tools-3.13/src/Makefile.in
...
inotify-tools-3.13/ltmain.sh

% cd inotify-tools.3.13
% ./configure
% make
% make install

Теперь все готово для использования утилит. Например, если вы хотите отслеживать изменения в вашем домашнем каталоге целиком, запустите inotifywait. Простейший вариант вызова inotifywait -r -m рекурсивно отслеживает аргументы (-r) и оставляет утилиту запущенной после каждого события (-m):

% inotifywait -r  -m $HOME
Watches established.

Откройте другое окно терминала и обратитесь к своему домашнему каталогу. Интересно, что даже простой просмотр каталога при помощи команды Is создает событие:

/home/strike OPEN,ISDIR

В справке inotifywait приведены параметры, ограничивающие события определенным перечнем (используйте несколько раз параметр -e имя_события для создания перечня), либо исключающие определенные файлы (--exclude образец) из рекурсивных наблюдений.


Будьте в курсе

Наряду с apt-cache , описанным выше, существуют и другие утилиты на основе inotify, достойные вашего внимания. Утилита incron является дополнением для планировщика cron, но вместо расписания реагирует на события inotify. Утилита inoticoming специально создана для отслеживания выпадающих списков. Если вы разработчик на Perl, Ruby или Python, вы можете найти модули и библиотеки удобного вызова inotify для вашего любимого языка программирования.

Например, программисты Perl могут использовать Linux::Inotify2 (подробности см. в разделе Ресурсы) для добавления функций inotify в любое приложение Perl. Показанный в листинге 8 код, взятый из файла README Linux::Inotify2, демонстрирует интерфейс обратного вызова для отслеживания событий.

Листинг 8. Интерфейс обратного вызова для отслеживания событий
use Linux::Inotify2;

my $inotify = new Linux::Inotify2 
	or die "Unable to create new inotify object: $!"; 

 # for Event:
 Event->io (fd =>$inotify->fileno, poll => 'r', cb => sub { $inotify->poll });

 # for Glib:
 add_watch Glib::IO $inotify->fileno, in => sub { $inotify->poll };

 # manually:
 1 while $inotify->poll;

 # add watchers
 $inotify->watch ("/etc/passwd", IN_ACCESS, sub {
    my $e = shift;
    my $name = $e->fullname;
    print "$name was accessed\n" if $e->IN_ACCESS;
    print "$name is no longer mounted\n" if $e->IN_UNMOUNT;
    print "$name is gone\n" if $e->IN_IGNORED;
    print "events for $name have been lost\n" if $e->IN_Q_OVERFLOW; 

    # cancel this watcher: remove no further events
    $e->w->cancel;
 });

Поскольку в Linux все является файлом, несложно придумать бесчисленные примеры использования наблюдений inotify.

Остается один вопрос: «Кто наблюдает за наблюдателями?».

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

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

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=376380
ArticleTitle=Мониторинг системной активности при помощи inotify
publish-date=03172009