7

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.

eradman
  • 1,996
  • 1
  • 17
  • 23
  • But isn't the ID unique, so that you can mix different event types in the same event q? Or have i got your question wrong? – dhein Aug 19 '13 at 13:10
  • Exactly right; the ID must be unique because the kevent struct doesn't store the event type. Responding to one event type is very simple loop that blocks on kevent(), but handling multiple event types (VNODE & SIGNAL in my example) is considerably harder because some coordination between threads (or processes) seems to be required. – eradman Aug 20 '13 at 20:14
  • Hm, then I'm out I'm sorry... but I'll need this in the futur too.... I just thought there wont be any troubble in multiple threads or processes, because the id is unique in system range. But I'm gonna follow this. – dhein Aug 20 '13 at 21:16
  • I cant find documents but shouldn't the signal number be in the data fields and not the identifier? this EV_SET makes no sense to me. But i dont know for sure, im not getting my code to work either. – Lothar Aug 05 '15 at 16:37

1 Answers1

8

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.

Menthos
  • 330
  • 2
  • 5