Diff for /wikisrc/tutorials/kqueue_tutorial.mdwn between versions 1.1 and 1.6

version 1.1, 2011/11/25 00:19:18 version 1.6, 2020/09/09 14:28:56
Line 1 Line 1
 **Contents**  **Contents**
   
 [[!toc levels=3]]  [[!toc levels=3]]
   
 #   Introduction  #   Introduction
   
 The purpose of this document is to introduce the programmer to the methodology of kqueue, rather than providing a full and exhaustive documentation of its capabilities.  The purpose of this document is to introduce the programmer to the methodology of kqueue, rather than providing a full and exhaustive documentation of its capabilities. 
   
 Kqueue provides a standard API for applications to register their interest in various events/conditions and have the notifications for these delivered in an efficient way. It was designed to be scalable, flexible, reliable and correct.  Kqueue provides a standard API for applications to register their interest in various events/conditions and have the notifications for these delivered in an efficient way. It was designed to be scalable, flexible, reliable and correct. 
   
 #   kqueue API  #   kqueue API
   
 ##   kevent data structure  ##   kevent data structure
   
 The kevent structure goes like this:  The kevent structure goes like this: 
          
     struct kevent {      struct kevent {
                 uintptr_t ident;        /* identifier for this event */                  uintptr_t ident;        /* identifier for this event */
                 short     filter;       /* filter for event */                  uint32_t  filter;       /* filter for event */
                 u_short   flags;        /* action flags for kqueue */                  uint32_t  flags;        /* action flags for kqueue */
                 u_int     fflags;       /* filter flag value */                  uint32_t  fflags;       /* filter flag value */
                 intptr_t  data;         /* filter data value */                  int64_t   data;         /* filter data value */
                 void      *udata;       /* opaque user data identifier */                  void     *udata;        /* opaque user data identifier */
     };      };
          
   
 ###   <ident, filter> pair  ###   <ident, filter> pair
   
 A kevent is identified by an <ident, filter> pair. The `ident` might be a descriptor (file, socket, stream), a process ID or a signal number, depending on what we want to monitor. The `filter` identifies the kernel filter used to process the respective event. There are some pre-defined system filters, such as EVFILT_READ or EVFILT_WRITE, that are triggered when data exists for read or write operation is possible respectively.  A kevent is identified by an <ident, filter> pair. The `ident` might be a descriptor (file, socket, stream), a process ID or a signal number, depending on what we want to monitor. The `filter` identifies the kernel filter used to process the respective event. There are some pre-defined system filters, such as EVFILT_READ or EVFILT_WRITE, that are triggered when data exists for read or write operation is possible respectively. 
   
 If for instance we want to be notified when there's data available for reading in a socket, we have to specify a kevent in the form `<sckfd, EVFILT_READ>`, where sckfd is the file descriptor associated with the socket. If we would like to monitor the activity of a process, we would need a `<pid, EVFILT_PROC>` tuple. Keep in mind there can be only one kevent with the same <ident, filter> in our kqueue.  If for instance we want to be notified when there's data available for reading in a socket, we have to specify a kevent in the form `<sckfd, EVFILT_READ>`, where sckfd is the file descriptor associated with the socket. If we would like to monitor the activity of a process, we would need a `<pid, EVFILT_PROC>` tuple. Keep in mind there can be only one kevent with the same <ident, filter> in our kqueue. 
   
 ###   flags  ###   flags
   
 After having designed a kevent, we should decide whether we want to have it added to our kqueue. For this purpose we set the `flags` member to `EV_ADD`. We could also delete an existing one by setting `EV_DELETE` or just disable it with `EV_DISABLE`.  After having designed a kevent, we should decide whether we want to have it added to our kqueue. For this purpose we set the `flags` member to `EV_ADD`. We could also delete an existing one by setting `EV_DELETE` or just disable it with `EV_DISABLE`. 
   
 Combinations may be made by OR'ing the desired values. For instance, `EV_ADD | EV_ENABLE | EV_ONESHOT` would translate to "Add the event, enable it and return only the first occurrence of the filter being triggered. After the user retrieves the event from the kqueue, delete it."  Combinations may be made by OR'ing the desired values. For instance, `EV_ADD | EV_ENABLE | EV_ONESHOT` would translate to "Add the event, enable it and return only the first occurrence of the filter being triggered. After the user retrieves the event from the kqueue, delete it." 
   
 Reversely, if we would like to check whether a flag is set in a kevent, we would do it by AND'ing with the respective value. For instance:  Reversely, if we would like to check whether a flag is set in a kevent, we would do it by AND'ing with the respective value. For instance: 
          
     if (myevent.flags & EV_ERROR) {      if (myevent.flags & EV_ERROR) {
        /* handle errors */         /* handle errors */
     }      }
          
   
 ###   EV_SET() macro  ###   EV_SET() macro
   
 The EV_SET() macro is provided for ease of initializing a kevent structure. For the time being we won't elaborate on the rest of the kevent members; instead let's have a look at the case when we need to monitor a socket for any pending data for reading:  The EV_SET() macro is provided for ease of initializing a kevent structure. For the time being we won't elaborate on the rest of the kevent members; instead let's have a look at the case when we need to monitor a socket for any pending data for reading: 
          
     kevent ev;      kevent ev;
          
     EV_SET(&ev, sckfd, EVFILT_READ, EV_ADD, 0, 0, 0);      EV_SET(&ev, sckfd, EVFILT_READ, EV_ADD, 0, 0, 0);
          
   
 [![][18]][19]  If we liked to monitor a set of N sockets we would write something like this: 
       
    [18]: /images/Kevent.png      kevent ev[N];
    [19]: /images/Kevent.png (Kevent.png)      int i;
       
 If we liked to monitor a set of N sockets we would write something like this:      for (i = 0; i < N; i++)
             EV_SET(&ev[i], sckfd[i], EVFILT_READ, EV_ADD, 0, 0, 0);
     kevent ev[N];      
     int i;  
      ##   kqueue(2)
     for (i = 0; i < N; i++)  
        EV_SET(&ev[i], sckfd[i], EVFILT_READ, EV_ADD, 0, 0, 0);  The kqueue holds all the events we are interested in. Therefore, to begin with, we must create a new kqueue. We do so with the following code: 
          
       int kq;
 [![][20]][21]      
       if ((kq = kqueue()) == -1) {
    [20]: /images/Kevent2.png         perror("kqueue");
    [21]: /images/Kevent2.png (Kevent2.png)         exit(EXIT_FAILURE);
       }
 ##   kqueue(2)      
   
 The kqueue holds all the events we are interested in. Therefore, to begin with, we must create a new kqueue. We do so with the following code:  ##   kevent(2)
      
     int kq;  At this point the kqueue is empty. In order to populate it with a set of events, we use the kevent(2) function. This system call takes the array of events we constructed before and does not return until at least one event is received (or when an associated timeout is exhausted). The function returns the number of changes received and stores information about them in another array of struct kevent elements. 
          
     if ((kq = kqueue()) == -1) {      kevent chlist[N];   /* events we want to monitor */
        perror("kqueue");      kevent evlist[N];   /* events that were triggered */
        exit(EXIT_FAILURE);      int nev, i;
     }      
          /* populate chlist with the events we are interested in */
       /* ... */
 ##   kevent(2)       
       /* loop forever */
 At this point the kqueue is empty. In order to populate it with a set of events, we use the kevent(2) function. This system call takes the array of events we constructed before and does not return until at least one event is received (or when an associated timeout is exhausted). The function returns the number of changes received and stores information about them in another array of struct kevent elements.      for (;;) {
             nev = kevent(kq, chlist, N, 
     kevent chlist[N];   /* events we want to monitor */                          evlist, N,
     kevent evlist[N];   /* events that were triggered */                          NULL);   /* block indefinitely */
     int nev, i;      
             if (nev == -1) {
     /* populate chlist with the events we are interested in */            perror("kevent()");
     /* ... */            exit(EXIT_FAILURE);
               }
     /* loop forever */         else if (nev > 0) {
     for (;;) {            for (i = 0; i < nev; i++) {
        nev = kevent(kq, chlist, N,               /* handle events */
                         evlist, N,            }
                         NULL);   /* block indefinitely */         }
          }
        if (nev == -1) {      
           perror("kevent()");  
           exit(EXIT_FAILURE);  ###   timeout
        }  
        else if (nev > 0) {  Sometimes it is useful to set an upper time limit for kevent() to block. That way, it will return, no matter if none of the events was triggered. For this purpose we need the `timespec` structure, which is defined in `sys/time.h`: 
           for (i = 0; i < nev; i++) {      
              /* handle events */      struct timespec {
           }                  time_t tv_sec;        /* seconds */
        }                  long   tv_nsec;       /* and nanoseconds */
     }      };
          
   
 ###   timeout  The above code would turn into the following: 
       
 Sometimes it is useful to set an upper time limit for kevent() to block. That way, it will return, no matter if none of the events was triggered. For this purpose we need the `timespec` structure, which is defined in `sys/time.h`:      kevent chlist[N];   /* events we want to monitor */
          kevent evlist[N];   /* events that were triggered */
     struct timespec {      struct timespec tmout = { 5,     /* block for 5 seconds at most */ 
                 time_t tv_sec;        /* seconds */                                0 };   /* nanoseconds */
                 long   tv_nsec;       /* and nanoseconds */      int nev, i;
     };      
          /* populate chlist with the events we are interested in */
       /* ... */
 The above code would turn into the following:       
          /* loop forever */
     kevent chlist[N];   /* events we want to monitor */      for (;;) {
     kevent evlist[N];   /* events that were triggered */         nev = kevent(kq, chlist, N, 
     struct timespec tmout = { 5,     /* block for 5 seconds at most */                          evlist, N,
                               0 };   /* nanoseconds */                          &tmout);   /* set upper time limit to block */
     int nev, i;      
             if (nev == -1) {
     /* populate chlist with the events we are interested in */            perror("kevent()");
     /* ... */            exit(EXIT_FAILURE);
               }
     /* loop forever */         else if (nev == 0) {
     for (;;) {            /* handle timeout */
        nev = kevent(kq, chlist, N,         }
                         evlist, N,         else if (nev > 0) {
                         &tmout);   /* set upper time limit to block */            for (i = 0; i < nev; i++) {
                   /* handle events */
        if (nev == -1) {            }
           perror("kevent()");         }
           exit(EXIT_FAILURE);      }
        }      
        else if (nev == 0) {  
           /* handle timeout */  Note that if one uses a non-NULL zero timespec structure, the kevent() will return instantaneously, bringing down the performance to the levels of a plain poll method. 
        }  
        else if (nev > 0) {  #   Examples
           for (i = 0; i < nev; i++) {  
              /* handle events */  ##   A timer example
           }  
        }  The following code will setup a timer that will trigger a kevent every 5 seconds. Once it does, the process will fork and the child will execute the date(1) command. 
     }      
          #include <sys/event.h>
       #include <sys/time.h>
 Note that if one uses a non-NULL zero timespec structure, the kevent() will return instantaneously, bringing down the performance to the levels of a plain poll method.      #include <stdio.h>
       #include <stdlib.h>
 ##   Summary      #include <string.h>   /* for strerror() */
       #include <unistd.h>
 To summarize, the kqueue framework works like this:      
       /* function prototypes */
 [![][22]][23]      void diep(const char *s);
       
    [22]: /images/Summary.png      int main(void)
    [23]: /images/Summary.png (Summary.png)      {
          struct kevent change;    /* event we want to monitor */
 #   Examples         struct kevent event;     /* event that was triggered */
          pid_t pid;
 ##   A timer example         int kq, nev;
       
 The following code will setup a timer that will trigger a kevent every 5 seconds. Once it does, the process will fork and the child will execute the date(1) command.         /* create a new kernel event queue */
             if ((kq = kqueue()) == -1)
     #include <sys/event.h>            diep("kqueue()");
     #include <sys/time.h>      
     #include <stdio.h>         /* initalise kevent structure */
     #include <stdlib.h>         EV_SET(&change, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 5000, 0);
     #include <string.h>   /* for strerror() */      
     #include <unistd.h>         /* loop forever */
             for (;;) {
     /* function prototypes */            nev = kevent(kq, &change, 1, &event, 1, NULL);
     void diep(const char *s);      
                if (nev < 0)
     int main(void)               diep("kevent()");
     {      
        struct kevent change;    /* event we want to monitor */            else if (nev > 0) {
        struct kevent event;     /* event that was triggered */               if (event.flags & EV_ERROR) {   /* report any error */
        pid_t pid;                  fprintf(stderr, "EV_ERROR: %s\n", strerror(event.data));
        int kq, nev;                  exit(EXIT_FAILURE);
                   }
        /* create a new kernel event queue */      
        if ((kq = kqueue()) == -1)               if ((pid = fork()) < 0)         /* fork error */
           diep("kqueue()");                  diep("fork()");
          
        /* initalise kevent structure */               else if (pid == 0)              /* child */
        EV_SET(&change, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 5000, 0);                  if (execlp("date", "date", (char *)0) < 0)
                         diep("execlp()");
        /* loop forever */            }
        for (;;) {         }
           nev = kevent(kq, &change, 1, &event, 1, NULL);      
             close(kq);
           if (nev < 0)         return EXIT_SUCCESS;
              diep("kevent()");      }
          
           else if (nev > 0) {      void diep(const char *s)
              if (event.flags & EV_ERROR) {   /* report any error */      {
                 fprintf(stderr, "EV_ERROR: %s\n", strerror(event.data));         perror(s);
                 exit(EXIT_FAILURE);         exit(EXIT_FAILURE);
              }      }
          
              if ((pid = fork()) < 0)         /* fork error */  
                 diep("fork()");  Compile and run: 
          
              else if (pid == 0)              /* child */      $ gcc -o ktimer ktimer.c -Wall -W -Wextra -ansi -pedantic
                 if (execlp("date", "date", (char *)0) < 0)      $ ./ktimer
                    diep("execlp()");      Tue Mar 20 15:48:16 EET 2007
           }      Tue Mar 20 15:48:21 EET 2007
        }      Tue Mar 20 15:48:26 EET 2007
          Tue Mar 20 15:48:31 EET 2007
        close(kq);      ^C
        return EXIT_SUCCESS;      
     }  
      ##   A raw tcp client
     void diep(const char *s)  
     {  We will implement a raw tcp client using the kqueue framework. Whenever the host sends data to the socket, we will print them in the standard output stream. Similarly, when the user types something in the standard input stream, we will send it to the host through the socket. Basically, we need to monitor the following: 
        perror(s);  
        exit(EXIT_FAILURE);    1. any incoming host data in the socket 
     }    2. any user data in the standard input stream 
      
       #include <netinet/in.h>
 Compile and run:      #include <sys/event.h>
          #include <sys/socket.h>
     $ gcc -o ktimer ktimer.c -Wall -W -Wextra -ansi -pedantic      #include <sys/time.h>
     $ ./ktimer      #include <netdb.h>
     Tue Mar 20 15:48:16 EET 2007      #include <stdio.h>
     Tue Mar 20 15:48:21 EET 2007      #include <stdlib.h>
     Tue Mar 20 15:48:26 EET 2007      #include <string.h>
     Tue Mar 20 15:48:31 EET 2007      #include <unistd.h>
     ^C      
          #define BUFSIZE 1024
       
 ##   A raw tcp client      /* function prototypes */
       void diep(const char *s);
 We will implement a raw tcp client using the kqueue framework. Whenever the host sends data to the socket, we will print them in the standard output stream. Similarly, when the user types something in the standard input stream, we will send it to the host through the socket. Basically, we need to monitor the following:      int tcpopen(const char *host, int port);
       void sendbuftosck(int sckfd, const char *buf, int len);
   1. any incoming host data in the socket      
   2. any user data in the standard input stream      int main(int argc, char *argv[])
       {
 [![][24]][25]         struct kevent chlist[2];   /* events we want to monitor */
             struct kevent evlist[2];   /* events that were triggered */
    [24]: /images/Tcpclient.png         char buf[BUFSIZE];
    [25]: /images/Tcpclient.png (Tcpclient.png)         int sckfd, kq, nev, i;
       
 #include <netinet/in.h>         /* check argument count */
     #include <sys/event.h>         if (argc != 3) {
     #include <sys/socket.h>            fprintf(stderr, "usage: %s host port\n", argv[0]);
     #include <sys/time.h>            exit(EXIT_FAILURE);
     #include <netdb.h>         }
     #include <stdio.h>      
     #include <stdlib.h>         /* open a connection to a host:port pair */
     #include <string.h>         sckfd = tcpopen(argv[1], atoi(argv[2]));
     #include <unistd.h>      
             /* create a new kernel event queue */
     #define BUFSIZE 1024         if ((kq = kqueue()) == -1)
                diep("kqueue()");
     /* function prototypes */      
     void diep(const char *s);         /* initialise kevent structures */
     int tcpopen(const char *host, int port);         EV_SET(&chlist[0], sckfd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
     void sendbuftosck(int sckfd, const char *buf, int len);         EV_SET(&chlist[1], fileno(stdin), EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
          
     int main(int argc, char *argv[])         /* loop forever */
     {         for (;;) {
        struct kevent chlist[2];   /* events we want to monitor */            nev = kevent(kq, chlist, 2, evlist, 2, NULL);
        struct kevent evlist[2];   /* events that were triggered */      
        char buf[BUFSIZE];            if (nev < 0)
        int sckfd, kq, nev, i;               diep("kevent()");
          
        /* check argument count */             else if (nev > 0) {
        if (argc != 3) {               if (evlist[0].flags & EV_EOF)                       /* read direction of socket has shutdown */
           fprintf(stderr, "usage: %s host port\n", argv[0]);                  exit(EXIT_FAILURE);
           exit(EXIT_FAILURE);      
        }               for (i = 0; i < nev; i++) {
                      if (evlist[i].flags & EV_ERROR) {                /* report errors */
        /* open a connection to a host:port pair */                     fprintf(stderr, "EV_ERROR: %s\n", strerror(evlist[i].data));
        sckfd = tcpopen(argv[1], atoi(argv[2]));                     exit(EXIT_FAILURE);
                      }
        /* create a new kernel event queue */      
        if ((kq = kqueue()) == -1)                  if (evlist[i].ident == sckfd) {                  /* we have data from the host */
           diep("kqueue()");                     memset(buf, 0, BUFSIZE);
                         if (read(sckfd, buf, BUFSIZE) < 0)
        /* initialise kevent structures */                        diep("read()");
        EV_SET(&chlist[0], sckfd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);                     fputs(buf, stdout);
        EV_SET(&chlist[1], fileno(stdin), EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);                  }
          
        /* loop forever */                  else if (evlist[i].ident == fileno(stdin)) {     /* we have data from stdin */
        for (;;) {                     memset(buf, 0, BUFSIZE);
           nev = kevent(kq, chlist, 2, evlist, 2, NULL);                     fgets(buf, BUFSIZE, stdin);
                         sendbuftosck(sckfd, buf, strlen(buf));
           if (nev < 0)                  }
              diep("kevent()");               }
                }
            else if (nev > 0) {         }
              if (evlist[0].flags & EV_EOF)                       /* read direction of socket has shutdown */      
                 exit(EXIT_FAILURE);         close(kq);
             return EXIT_SUCCESS;
              for (i = 0; i < nev; i++) {      }
                 if (evlist[i].flags & EV_ERROR) {                /* report errors */      
                    fprintf(stderr, "EV_ERROR: %s\n", strerror(evlist[i].data));      void diep(const char *s)
                    exit(EXIT_FAILURE);      {
                 }         perror(s);
             exit(EXIT_FAILURE);
                 if (evlist[i].ident == sckfd) {                  /* we have data from the host */      }
                    memset(buf, 0, BUFSIZE);      
                    if (read(sckfd, buf, BUFSIZE) < 0)      int tcpopen(const char *host, int port)
                       diep("read()");      {
                    fputs(buf, stdout);         struct sockaddr_in server;
                 }         struct hostent *hp;
             int sckfd;
                 else if (evlist[i].ident == fileno(stdin)) {     /* we have data from stdin */      
                    memset(buf, 0, BUFSIZE);         if ((hp = gethostbyname(host)) == NULL)
                    fgets(buf, BUFSIZE, stdin);            diep("gethostbyname()");
                    sendbuftosck(sckfd, buf, strlen(buf));      
                 }         if ((sckfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
              }            diep("socket()");
           }      
        }         server.sin_family = AF_INET;
             server.sin_port = htons(port);
        close(kq);         server.sin_addr = *((struct in_addr *)hp->h_addr);
        return EXIT_SUCCESS;         memset(&(server.sin_zero), 0, 8);
     }      
             if (connect(sckfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) < 0)
     void diep(const char *s)            diep("connect()");
     {      
        perror(s);         return sckfd;
        exit(EXIT_FAILURE);      }
     }      
          void sendbuftosck(int sckfd, const char *buf, int len)
     int tcpopen(const char *host, int port)      {
     {         int bytessent, pos;
        struct sockaddr_in server;      
        struct hostent *hp;         pos = 0;
        int sckfd;         do {
                if ((bytessent = send(sckfd, buf + pos, len - pos, 0)) < 0)
        if ((hp = gethostbyname(host)) == NULL)               diep("send()");
           diep("gethostbyname()");            pos += bytessent;
             } while (bytessent > 0);
        if ((sckfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)      }
           diep("socket()");      
      
        server.sin_family = AF_INET;  Compile and run: 
        server.sin_port = htons(port);      
        server.sin_addr = *((struct in_addr *)hp->h_addr);      $ gcc -o kclient kclient.c -Wall -W -Wextra -ansi -pedantic
        memset(&(server.sin_zero), 0, 8);      $ ./kclient irc.freenode.net 7000
          NOTICE AUTH :*** Looking up your hostname...
        if (connect(sckfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) < 0)      NOTICE AUTH :*** Found your hostname, welcome back
           diep("connect()");      NOTICE AUTH :*** Checking ident
          NOTICE AUTH :*** No identd (auth) response
        return sckfd;      _USER guest tolmoon tolsun :Ronnie Reagan
     }      NICK Wiz_
          :herbert.freenode.net 001 Wiz :Welcome to the freenode IRC Network Wiz
     void sendbuftosck(int sckfd, const char *buf, int len)      ^C 
     {      
        int bytessent, pos;  
      (Whatever is in _italics_ it is what we type.) 
        pos = 0;  
        do {  ##   More examples
           if ((bytessent = send(sckfd, buf + pos, len - pos, 0)) < 0)  
              diep("send()");  More kqueue examples (including the aforementioned) may be found [here](http://repo.or.cz/w/eleutheria.git?a=tree;h=master;hb=master). 
           pos += bytessent;  
        } while (bytessent > 0);     [26]: http://repo.or.cz/w/eleutheria.git?a=tree;h=master;hb=master (http://repo.or.cz/w/eleutheria.git?a=tree;h=master;hb=master)
     }  
      #   Documentation
   
 Compile and run:    1. [[!template id=man name="kqueue" section="2"]]: kqueue, kevent NetBSD Manual Pages
        2. [Kqueue: A generic and scalable event notification facility (pdf)](http://people.freebsd.org/~jlemon/papers/kqueue.pdf)
     $ gcc -o kclient kclient.c -Wall -W -Wextra -ansi -pedantic    3. [kqueue slides](http://people.freebsd.org/~jlemon/kqueue_slides/)
     $ ./kclient irc.freenode.net 7000    4. [The Julipedia: An example of kqueue](http://julipedia.blogspot.com/2004/10/example-of-kqueue.html)
     NOTICE AUTH :*** Looking up your hostname...  
     NOTICE AUTH :*** Found your hostname, welcome back  
     NOTICE AUTH :*** Checking ident  
     NOTICE AUTH :*** No identd (auth) response  
     _USER guest tolmoon tolsun :Ronnie Reagan  
     NICK Wiz_  
     :herbert.freenode.net 001 Wiz :Welcome to the freenode IRC Network Wiz  
     ^C  
      
   
 (Whatever is in _italics_ it is what we type.)  
   
 ##   More examples  
   
 More kqueue examples (including the aforementioned) may be found [here](http://repo.or.cz/w/eleutheria.git?a=tree;h=master;hb=master).  
   
    [26]: http://repo.or.cz/w/eleutheria.git?a=tree;h=master;hb=master (http://repo.or.cz/w/eleutheria.git?a=tree;h=master;hb=master)  
   
 #   Documentation  
   
   1. [kqueue, kevent NetBSD Manual Pages](http://netbsd.gw.com/cgi-bin/man-cgi?kqueue++NetBSD-current)  
   2. [Kqueue: A generic and scalable event notification facility (pdf)](http://people.freebsd.org/~jlemon/papers/kqueue.pdf)  
   3. [kqueue slides](http://people.freebsd.org/~jlemon/kqueue_slides/)  
   4. [The Julipedia: An example of kqueue](http://julipedia.blogspot.com/2004/10/example-of-kqueue.html)  
   

Removed from v.1.1  
changed lines
  Added in v.1.6


CVSweb for NetBSD wikisrc <wikimaster@NetBSD.org> software: FreeBSD-CVSweb