Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use kqueue to respond to more than one event type

Tags:

kqueue

When an event is registered with kqueue an ID relating to that event type is supplied; for example a file descriptor is used to identify a file to watch

int kq;
struct kevent ke;

kq = kqueue();
fd = open(argv[1], O_RDONLY);
EV_SET(&ke, fd, EVFILT_VNODE, EV_ADD, NOTE_DELETE | NOTE_RENAME, 0, NULL);
kevent(kq, &ke, 1, NULL, 0, NULL);

while (1) {
    kevent(kq, NULL, 0, &ke, 1, NULL);
    /* respond to file system event */
}

Now if I also need to respond to other event types such signals we need a new instance of kqueue so as to avoid a conflict with the ident argument of kevent().

kq_sig = kqueue();
struct kevent ke_sig;

/* set the handler and ignore SIGINT */
signal(SIGINT, SIG_IGN);
EV_SET(&ke_sig, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
kevent(kq_sig, &ke_sig, 1, NULL, 0, NULL);
while (1) {
    kevent(kq_sig, NULL, 0, &ke_sig, 1, NULL);
    /* respond signals */
}

Watching more than one event type appears to necessitate multiple threads that act on shared state (receiving a signal could close a file descriptor for example).

Is there a more general mechanism for sending a message from one thread to another using kqueue? In some cases I can conceive of enabling and disabling a filter as a means of edge-triggering another kevent.

like image 841
eradman Avatar asked Apr 05 '13 20:04

eradman


1 Answers

The kevent struct actually provides info about the event that occured:

struct kevent {
         uintptr_t       ident;          /* identifier for this event */
         int16_t         filter;         /* filter for event */
         uint16_t        flags;          /* general flags */
         uint32_t        fflags;         /* filter-specific flags */
         intptr_t        data;           /* filter-specific data */
         void            *udata;         /* opaque user data identifier */
 };

You must be interested in:

  • ident that in your case returns either fd or SIGINT;
  • filter that (still in your case) returns either EVFILT_VNODE or EVFILT_SIGNAL;
  • fflag that in the EVFILT_VNODE will tell you if the file descriptor event was NOTE_DELETE or NOTE_RENAME.

You can register two kevent structures to a single queue and then use these structure members to determine if the event was related to a file descriptor or a signal.

Here is a complete example that demonstrates how to do this:

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>

int
main(int argc, char** argv)
{
    /* A single kqueue */
    int kq = kqueue();
    /* Two kevent structs */
    struct kevent *ke = malloc(sizeof(struct kevent) * 2);

    /* Initialise one struct for the file descriptor, and one for SIGINT */
    int fd = open(argv[1], O_RDONLY);
    EV_SET(ke, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_RENAME, 0, NULL);
    signal(SIGINT, SIG_IGN);
    EV_SET(ke + 1, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);

    /* Register for the events */
    if(kevent(kq, ke, 2, NULL, 0, NULL) < 0)
        perror("kevent");

    while(1) {
        memset(ke, 0x00, sizeof(struct kevent));
        if(kevent(kq, NULL, 0, ke, 1, NULL) < 0)
            perror("kevent");

        switch(ke->filter)
        {
            /* File descriptor event: let's examine what happened to the file */
            case EVFILT_VNODE:
                printf("Events %d on file descriptor %d\n", ke->fflags, (int) ke->ident);

                if(ke->fflags & NOTE_DELETE)
                    printf("The unlink() system call was called on the file referenced by the descriptor.\n");
                if(ke->fflags & NOTE_WRITE)
                    printf("A write occurred on the file referenced by the descriptor.\n");
                if(ke->fflags & NOTE_EXTEND)
                    printf("The file referenced by the descriptor was extended.\n");
                if(ke->fflags & NOTE_ATTRIB)
                    printf("The file referenced by the descriptor had its attributes changed.\n");
                if(ke->fflags & NOTE_LINK)
                    printf("The link count on the file changed.\n");
                if(ke->fflags & NOTE_RENAME)
                    printf("The file referenced by the descriptor was renamed.\n");
                if(ke->fflags & NOTE_REVOKE)
                    printf("Access to the file was revoked via revoke(2) or the underlying fileystem was unmounted.");
                break;

            /* Signal event */
            case EVFILT_SIGNAL:
                printf("Received %s\n", strsignal(ke->ident));
                exit(42);
                break;

            /* This should never happen */
            default:
                printf("Unknown filter\n");
        }
    }
}

Note that here we use a single thread, which is way more efficient and requires no further synchronization in the user space.

like image 118
Menthos Avatar answered Oct 06 '22 16:10

Menthos