Очереди ядра: альтернатива событиям файловой системы
API очередей ядра обеспечивает путь к приложению для получения уведомлений каждый раз, когда данный файл или каталог изменяется всегда, включая изменения в содержании файла, атрибутах, имени или длине. Ваше приложение может также получить уведомление, если Вы наблюдаете блочное устройство или устройство посимвольного ввода-вывода, и доступ отклоняется через вызов к revoke
.
API очередей ядра также обеспечивает способ контролировать дочерние процессы и узнать, когда они вызывают exit
, fork
, exec
, и т.д. Это использование очередей ядра выходит за рамки этого документа. Для получения дополнительной информации об очередях ядра и процессах, необходимо считать документацию FreeBSD для очередей ядра. Можно найти ссылки к этой документации в http://people .freebsd.org/~jmg/kq.html.
Выбор механизма события
События файловой системы предназначаются для обеспечения уведомления об изменениях с гранулярностью уровня каталога. В большинстве целей это достаточно. В некоторых случаях, однако, Вы, возможно, должны получить уведомления с более прекрасной гранулярностью. Например, Вы, возможно, должны были бы наблюдать только изменения, сделанные к единственному файлу. С этой целью очередь ядра (kqueue) система уведомления является более соответствующей.
При контроле большой иерархии содержания необходимо использовать события файловой системы вместо этого, однако, потому что очереди ядра являются несколько более сложными, чем события ядра и могут быть более интенсивно использующими ресурсы из-за дополнительной включенной коммуникации пользовательского ядра.
Используя очереди ядра
Очереди ядра (kqueue) и события ядра (kevent) механизм чрезвычайно мощны и гибки, позволяя Вам получить поток событий уровня ядра (включая модификации файла) и определить ряд фильтров, ограничивающих, какие события поставлены Вашему приложению.
Для использования очередей ядра необходимо сделать четыре вещи:
Создайте очередь событий ядра путем вызова
kqueue(2) OS X Developer Tools Manual Page
. Эта функция возвращает дескриптор файла для недавно выделенной очереди событий.Откройте дескриптор файла для каждого файла, который Вы хотите наблюдать.
Создайте список событий для наблюдения за. Чтобы сделать это, используйте
EV_SET
заполнить поля структуры события ядра. Прототип следующие:EV_SET(&kev, ident, filter, flags, fflags, data, udata);
Первый параметр,
kev
, адрес самой структуры. Второе,ident
, содержит дескриптор файла для файла, который Вы наблюдаете.Третий параметр,
filter
, содержит имя фильтра ядра, результаты которого Вы хотите видеть. Например, можно использоватьEVFILT_VNODE
контролировать vnode операции на файле.Остающиеся параметры являются все определенными для определенного фильтра и описаны в странице руководства для
kevent
.Вызвать
kevent
в цикле. Эта функция контролирует очередь событий ядра для событий и хранит их в буфере, который Вы обеспечиваете. Прототип следующие:int kevent(int kq, const struct kevent *changelist,
int nchanges, struct kevent *eventlist,
int nevents, const struct timespec *timeout);
Его параметрами является (в порядке) дескриптор файла очереди, список событий для наблюдения за (от предыдущего шага), число событий в том списке, пространстве временного хранения для получающихся данных о событии, размера того хранения и тайм-аута.
На успехе,
kevent
функционируйте возвращается, число событий возвратилось. Если тайм-аут истекает, прежде чем любое событие имеет место, это возвращается 0. В зависимости от природы ошибки об ошибках можно сообщить любой как событие сEV_ERROR
отметьте набор и системную ошибку, сохраненную вdata
поле или путем возврата-1 с ошибкой, сохраненной вerrno
.
Краткий пример
Перечисление a-1 является кратким примером, показывающим, как контролировать единственный файл с помощью очередей ядра. Для более сложного примера, контролирующего каталоги, смотрите на пример кода FileNotification.
Часы перечисления a-1 файл Используя очереди ядра
#include <unistd.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <fcntl.h> |
#include <sys/types.h> |
#include <sys/event.h> |
#include <sys/time.h> |
#include <errno.h> |
#include <string.h> |
#include <inttypes.h> |
#define NUM_EVENT_SLOTS 1 |
#define NUM_EVENT_FDS 1 |
char *flagstring(int flags); |
int main(int argc, char *argv[]) |
{ |
char *path = argv[1]; |
int kq; |
int event_fd; |
struct kevent events_to_monitor[NUM_EVENT_FDS]; |
struct kevent event_data[NUM_EVENT_SLOTS]; |
void *user_data; |
struct timespec timeout; |
unsigned int vnode_events; |
if (argc != 2) { |
fprintf(stderr, "Usage: monitor <file_path>\n"); |
exit(-1); |
} |
/* Open a kernel queue. */ |
if ((kq = kqueue()) < 0) { |
fprintf(stderr, "Could not open kernel queue. Error was %s.\n", strerror(errno)); |
} |
/* |
Open a file descriptor for the file/directory that you |
want to monitor. |
*/ |
event_fd = open(path, O_EVTONLY); |
if (event_fd <=0) { |
fprintf(stderr, "The file %s could not be opened for monitoring. Error was %s.\n", path, strerror(errno)); |
exit(-1); |
} |
/* |
The address in user_data will be copied into a field in the |
event. If you are monitoring multiple files, you could, |
for example, pass in different data structure for each file. |
For this example, the path string is used. |
*/ |
user_data = path; |
/* Set the timeout to wake us every half second. */ |
timeout.tv_sec = 0; // 0 seconds |
timeout.tv_nsec = 500000000; // 500 milliseconds |
/* Set up a list of events to monitor. */ |
vnode_events = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME | NOTE_REVOKE; |
EV_SET( &events_to_monitor[0], event_fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, vnode_events, 0, user_data); |
/* Handle events. */ |
int num_files = 1; |
int continue_loop = 40; /* Monitor for twenty seconds. */ |
while (--continue_loop) { |
int event_count = kevent(kq, events_to_monitor, NUM_EVENT_SLOTS, event_data, num_files, &timeout); |
if ((event_count < 0) || (event_data[0].flags == EV_ERROR)) { |
/* An error occurred. */ |
fprintf(stderr, "An error occurred (event count %d). The error was %s.\n", event_count, strerror(errno)); |
break; |
} |
if (event_count) { |
printf("Event %" PRIdPTR " occurred. Filter %d, flags %d, filter flags %s, filter data %" PRIdPTR ", path %s\n", |
event_data[0].ident, |
event_data[0].filter, |
event_data[0].flags, |
flagstring(event_data[0].fflags), |
event_data[0].data, |
(char *)event_data[0].udata); |
} else { |
printf("No event.\n"); |
} |
/* Reset the timeout. In case of a signal interrruption, the |
values may change. */ |
timeout.tv_sec = 0; // 0 seconds |
timeout.tv_nsec = 500000000; // 500 milliseconds |
} |
close(event_fd); |
return 0; |
} |
/* A simple routine to return a string for a set of flags. */ |
char *flagstring(int flags) |
{ |
static char ret[512]; |
char *or = ""; |
ret[0]='\0'; // clear the string. |
if (flags & NOTE_DELETE) {strcat(ret,or);strcat(ret,"NOTE_DELETE");or="|";} |
if (flags & NOTE_WRITE) {strcat(ret,or);strcat(ret,"NOTE_WRITE");or="|";} |
if (flags & NOTE_EXTEND) {strcat(ret,or);strcat(ret,"NOTE_EXTEND");or="|";} |
if (flags & NOTE_ATTRIB) {strcat(ret,or);strcat(ret,"NOTE_ATTRIB");or="|";} |
if (flags & NOTE_LINK) {strcat(ret,or);strcat(ret,"NOTE_LINK");or="|";} |
if (flags & NOTE_RENAME) {strcat(ret,or);strcat(ret,"NOTE_RENAME");or="|";} |
if (flags & NOTE_REVOKE) {strcat(ret,or);strcat(ret,"NOTE_REVOKE");or="|";} |
return ret; |
} |
Для получения дополнительной информации
Для получения дополнительной информации об очередях ядра, см. страницу руководства для kqueue
), пример кода FileNotification и документация FreeBSD для очередей ядра в http://people .freebsd.org/~jmg/kq.html..