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

version 1.1, 2011/11/25 00:19:18 version 1.2, 2012/02/05 07:14:36
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 */                  short     filter;       /* filter for event */
                 u_short   flags;        /* action flags for kqueue */                  u_short   flags;        /* action flags for kqueue */
                 u_int     fflags;       /* filter flag value */                  u_int     fflags;       /* filter flag value */
                 intptr_t  data;         /* filter data value */                  intptr_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]  [![][18]][19]
   
    [18]: /images/Kevent.png     [18]: /images/Kevent.png
    [19]: /images/Kevent.png (Kevent.png)     [19]: /images/Kevent.png (Kevent.png)
   
 If we liked to monitor a set of N sockets we would write something like this:  If we liked to monitor a set of N sockets we would write something like this: 
          
     kevent ev[N];      kevent ev[N];
     int i;      int i;
          
     for (i = 0; i < N; i++)      for (i = 0; i < N; i++)
        EV_SET(&ev[i], sckfd[i], EVFILT_READ, EV_ADD, 0, 0, 0);         EV_SET(&ev[i], sckfd[i], EVFILT_READ, EV_ADD, 0, 0, 0);
          
   
 [![][20]][21]  [![][20]][21]
   
    [20]: /images/Kevent2.png     [20]: /images/Kevent2.png
    [21]: /images/Kevent2.png (Kevent2.png)     [21]: /images/Kevent2.png (Kevent2.png)
   
 ##   kqueue(2)  ##   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:  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;      int kq;
          
     if ((kq = kqueue()) == -1) {      if ((kq = kqueue()) == -1) {
        perror("kqueue");         perror("kqueue");
        exit(EXIT_FAILURE);         exit(EXIT_FAILURE);
     }      }
          
   
 ##   kevent(2)  ##   kevent(2)
   
 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.  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. 
          
     kevent chlist[N];   /* events we want to monitor */      kevent chlist[N];   /* events we want to monitor */
     kevent evlist[N];   /* events that were triggered */      kevent evlist[N];   /* events that were triggered */
     int nev, i;      int nev, i;
          
     /* populate chlist with the events we are interested in */      /* populate chlist with the events we are interested in */
     /* ... */      /* ... */
             
     /* loop forever */      /* loop forever */
     for (;;) {      for (;;) {
        nev = kevent(kq, chlist, N,         nev = kevent(kq, chlist, N, 
                         evlist, N,                          evlist, N,
                         NULL);   /* block indefinitely */                          NULL);   /* block indefinitely */
          
        if (nev == -1) {         if (nev == -1) {
           perror("kevent()");            perror("kevent()");
           exit(EXIT_FAILURE);            exit(EXIT_FAILURE);
        }         }
        else if (nev > 0) {         else if (nev > 0) {
           for (i = 0; i < nev; i++) {            for (i = 0; i < nev; i++) {
              /* handle events */               /* handle events */
           }            }
        }         }
     }      }
          
   
 ###   timeout  ###   timeout
   
 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`:  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`: 
          
     struct timespec {      struct timespec {
                 time_t tv_sec;        /* seconds */                  time_t tv_sec;        /* seconds */
                 long   tv_nsec;       /* and nanoseconds */                  long   tv_nsec;       /* and nanoseconds */
     };      };
          
   
 The above code would turn into the following:  The above code would turn into the following: 
          
     kevent chlist[N];   /* events we want to monitor */      kevent chlist[N];   /* events we want to monitor */
     kevent evlist[N];   /* events that were triggered */      kevent evlist[N];   /* events that were triggered */
     struct timespec tmout = { 5,     /* block for 5 seconds at most */      struct timespec tmout = { 5,     /* block for 5 seconds at most */ 
                               0 };   /* nanoseconds */                                0 };   /* nanoseconds */
     int nev, i;      int nev, i;
          
     /* populate chlist with the events we are interested in */      /* populate chlist with the events we are interested in */
     /* ... */      /* ... */
             
     /* loop forever */      /* loop forever */
     for (;;) {      for (;;) {
        nev = kevent(kq, chlist, N,         nev = kevent(kq, chlist, N, 
                         evlist, N,                          evlist, N,
                         &tmout);   /* set upper time limit to block */                          &tmout);   /* set upper time limit to block */
          
        if (nev == -1) {         if (nev == -1) {
           perror("kevent()");            perror("kevent()");
           exit(EXIT_FAILURE);            exit(EXIT_FAILURE);
        }         }
        else if (nev == 0) {         else if (nev == 0) {
           /* handle timeout */            /* handle timeout */
        }         }
        else if (nev > 0) {         else if (nev > 0) {
           for (i = 0; i < nev; i++) {            for (i = 0; i < nev; i++) {
              /* handle events */               /* handle events */
           }            }
        }         }
     }      }
          
   
 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.  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. 
   
 ##   Summary  ##   Summary
   
 To summarize, the kqueue framework works like this:  To summarize, the kqueue framework works like this: 
   
 [![][22]][23]  [![][22]][23]
   
    [22]: /images/Summary.png     [22]: /images/Summary.png
    [23]: /images/Summary.png (Summary.png)     [23]: /images/Summary.png (Summary.png)
   
 #   Examples  #   Examples
   
 ##   A timer example  ##   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.  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/event.h>
     #include <sys/time.h>      #include <sys/time.h>
     #include <stdio.h>      #include <stdio.h>
     #include <stdlib.h>      #include <stdlib.h>
     #include <string.h>   /* for strerror() */      #include <string.h>   /* for strerror() */
     #include <unistd.h>      #include <unistd.h>
          
     /* function prototypes */      /* function prototypes */
     void diep(const char *s);      void diep(const char *s);
          
     int main(void)      int main(void)
     {      {
        struct kevent change;    /* event we want to monitor */         struct kevent change;    /* event we want to monitor */
        struct kevent event;     /* event that was triggered */         struct kevent event;     /* event that was triggered */
        pid_t pid;         pid_t pid;
        int kq, nev;         int kq, nev;
          
        /* create a new kernel event queue */         /* create a new kernel event queue */
        if ((kq = kqueue()) == -1)         if ((kq = kqueue()) == -1)
           diep("kqueue()");            diep("kqueue()");
          
        /* initalise kevent structure */         /* initalise kevent structure */
        EV_SET(&change, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 5000, 0);         EV_SET(&change, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 5000, 0);
          
        /* loop forever */         /* loop forever */
        for (;;) {         for (;;) {
           nev = kevent(kq, &change, 1, &event, 1, NULL);            nev = kevent(kq, &change, 1, &event, 1, NULL);
          
           if (nev < 0)            if (nev < 0)
              diep("kevent()");               diep("kevent()");
          
           else if (nev > 0) {            else if (nev > 0) {
              if (event.flags & EV_ERROR) {   /* report any error */               if (event.flags & EV_ERROR) {   /* report any error */
                 fprintf(stderr, "EV_ERROR: %s\n", strerror(event.data));                  fprintf(stderr, "EV_ERROR: %s\n", strerror(event.data));
                 exit(EXIT_FAILURE);                  exit(EXIT_FAILURE);
              }               }
          
              if ((pid = fork()) < 0)         /* fork error */               if ((pid = fork()) < 0)         /* fork error */
                 diep("fork()");                  diep("fork()");
          
              else if (pid == 0)              /* child */               else if (pid == 0)              /* child */
                 if (execlp("date", "date", (char *)0) < 0)                  if (execlp("date", "date", (char *)0) < 0)
                    diep("execlp()");                     diep("execlp()");
           }            }
        }         }
          
        close(kq);         close(kq);
        return EXIT_SUCCESS;         return EXIT_SUCCESS;
     }      }
          
     void diep(const char *s)      void diep(const char *s)
     {      {
        perror(s);         perror(s);
        exit(EXIT_FAILURE);         exit(EXIT_FAILURE);
     }      }
          
   
 Compile and run:  Compile and run: 
          
     $ gcc -o ktimer ktimer.c -Wall -W -Wextra -ansi -pedantic      $ gcc -o ktimer ktimer.c -Wall -W -Wextra -ansi -pedantic
     $ ./ktimer      $ ./ktimer
     Tue Mar 20 15:48:16 EET 2007      Tue Mar 20 15:48:16 EET 2007
     Tue Mar 20 15:48:21 EET 2007      Tue Mar 20 15:48:21 EET 2007
     Tue Mar 20 15:48:26 EET 2007      Tue Mar 20 15:48:26 EET 2007
     Tue Mar 20 15:48:31 EET 2007      Tue Mar 20 15:48:31 EET 2007
     ^C      ^C
          
   
 ##   A raw tcp client  ##   A raw tcp client
   
 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:  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: 
   
   1. any incoming host data in the socket    1. any incoming host data in the socket 
   2. any user data in the standard input stream    2. any user data in the standard input stream 
   
 [![][24]][25]  [![][24]][25]
          
    [24]: /images/Tcpclient.png     [24]: /images/Tcpclient.png
    [25]: /images/Tcpclient.png (Tcpclient.png)     [25]: /images/Tcpclient.png (Tcpclient.png)
   
 #include <netinet/in.h>  #include <netinet/in.h>
     #include <sys/event.h>      #include <sys/event.h>
     #include <sys/socket.h>      #include <sys/socket.h>
     #include <sys/time.h>      #include <sys/time.h>
     #include <netdb.h>      #include <netdb.h>
     #include <stdio.h>      #include <stdio.h>
     #include <stdlib.h>      #include <stdlib.h>
     #include <string.h>      #include <string.h>
     #include <unistd.h>      #include <unistd.h>
          
     #define BUFSIZE 1024      #define BUFSIZE 1024
          
     /* function prototypes */      /* function prototypes */
     void diep(const char *s);      void diep(const char *s);
     int tcpopen(const char *host, int port);      int tcpopen(const char *host, int port);
     void sendbuftosck(int sckfd, const char *buf, int len);      void sendbuftosck(int sckfd, const char *buf, int len);
          
     int main(int argc, char *argv[])      int main(int argc, char *argv[])
     {      {
        struct kevent chlist[2];   /* events we want to monitor */         struct kevent chlist[2];   /* events we want to monitor */
        struct kevent evlist[2];   /* events that were triggered */         struct kevent evlist[2];   /* events that were triggered */
        char buf[BUFSIZE];         char buf[BUFSIZE];
        int sckfd, kq, nev, i;         int sckfd, kq, nev, i;
          
        /* check argument count */         /* check argument count */
        if (argc != 3) {         if (argc != 3) {
           fprintf(stderr, "usage: %s host port\n", argv[0]);            fprintf(stderr, "usage: %s host port\n", argv[0]);
           exit(EXIT_FAILURE);            exit(EXIT_FAILURE);
        }         }
          
        /* open a connection to a host:port pair */         /* open a connection to a host:port pair */
        sckfd = tcpopen(argv[1], atoi(argv[2]));         sckfd = tcpopen(argv[1], atoi(argv[2]));
          
        /* create a new kernel event queue */         /* create a new kernel event queue */
        if ((kq = kqueue()) == -1)         if ((kq = kqueue()) == -1)
           diep("kqueue()");            diep("kqueue()");
          
        /* initialise kevent structures */         /* initialise kevent structures */
        EV_SET(&chlist[0], sckfd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);         EV_SET(&chlist[0], sckfd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
        EV_SET(&chlist[1], fileno(stdin), EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);         EV_SET(&chlist[1], fileno(stdin), EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
          
        /* loop forever */         /* loop forever */
        for (;;) {         for (;;) {
           nev = kevent(kq, chlist, 2, evlist, 2, NULL);            nev = kevent(kq, chlist, 2, evlist, 2, NULL);
          
           if (nev < 0)            if (nev < 0)
              diep("kevent()");               diep("kevent()");
          
            else if (nev > 0) {             else if (nev > 0) {
              if (evlist[0].flags & EV_EOF)                       /* read direction of socket has shutdown */               if (evlist[0].flags & EV_EOF)                       /* read direction of socket has shutdown */
                 exit(EXIT_FAILURE);                  exit(EXIT_FAILURE);
          
              for (i = 0; i < nev; i++) {               for (i = 0; i < nev; i++) {
                 if (evlist[i].flags & EV_ERROR) {                /* report errors */                  if (evlist[i].flags & EV_ERROR) {                /* report errors */
                    fprintf(stderr, "EV_ERROR: %s\n", strerror(evlist[i].data));                     fprintf(stderr, "EV_ERROR: %s\n", strerror(evlist[i].data));
                    exit(EXIT_FAILURE);                     exit(EXIT_FAILURE);
                 }                  }
          
                 if (evlist[i].ident == sckfd) {                  /* we have data from the host */                  if (evlist[i].ident == sckfd) {                  /* we have data from the host */
                    memset(buf, 0, BUFSIZE);                     memset(buf, 0, BUFSIZE);
                    if (read(sckfd, buf, BUFSIZE) < 0)                     if (read(sckfd, buf, BUFSIZE) < 0)
                       diep("read()");                        diep("read()");
                    fputs(buf, stdout);                     fputs(buf, stdout);
                 }                  }
          
                 else if (evlist[i].ident == fileno(stdin)) {     /* we have data from stdin */                  else if (evlist[i].ident == fileno(stdin)) {     /* we have data from stdin */
                    memset(buf, 0, BUFSIZE);                     memset(buf, 0, BUFSIZE);
                    fgets(buf, BUFSIZE, stdin);                     fgets(buf, BUFSIZE, stdin);
                    sendbuftosck(sckfd, buf, strlen(buf));                     sendbuftosck(sckfd, buf, strlen(buf));
                 }                  }
              }               }
           }            }
        }         }
          
        close(kq);         close(kq);
        return EXIT_SUCCESS;         return EXIT_SUCCESS;
     }      }
          
     void diep(const char *s)      void diep(const char *s)
     {      {
        perror(s);         perror(s);
        exit(EXIT_FAILURE);         exit(EXIT_FAILURE);
     }      }
          
     int tcpopen(const char *host, int port)      int tcpopen(const char *host, int port)
     {      {
        struct sockaddr_in server;         struct sockaddr_in server;
        struct hostent *hp;         struct hostent *hp;
        int sckfd;         int sckfd;
          
        if ((hp = gethostbyname(host)) == NULL)         if ((hp = gethostbyname(host)) == NULL)
           diep("gethostbyname()");            diep("gethostbyname()");
          
        if ((sckfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)         if ((sckfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
           diep("socket()");            diep("socket()");
          
        server.sin_family = AF_INET;         server.sin_family = AF_INET;
        server.sin_port = htons(port);         server.sin_port = htons(port);
        server.sin_addr = *((struct in_addr *)hp->h_addr);         server.sin_addr = *((struct in_addr *)hp->h_addr);
        memset(&(server.sin_zero), 0, 8);         memset(&(server.sin_zero), 0, 8);
          
        if (connect(sckfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) < 0)         if (connect(sckfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) < 0)
           diep("connect()");            diep("connect()");
          
        return sckfd;         return sckfd;
     }      }
          
     void sendbuftosck(int sckfd, const char *buf, int len)      void sendbuftosck(int sckfd, const char *buf, int len)
     {      {
        int bytessent, pos;         int bytessent, pos;
          
        pos = 0;         pos = 0;
        do {         do {
           if ((bytessent = send(sckfd, buf + pos, len - pos, 0)) < 0)            if ((bytessent = send(sckfd, buf + pos, len - pos, 0)) < 0)
              diep("send()");               diep("send()");
           pos += bytessent;            pos += bytessent;
        } while (bytessent > 0);         } while (bytessent > 0);
     }      }
          
   
 Compile and run:  Compile and run: 
          
     $ gcc -o kclient kclient.c -Wall -W -Wextra -ansi -pedantic      $ gcc -o kclient kclient.c -Wall -W -Wextra -ansi -pedantic
     $ ./kclient irc.freenode.net 7000      $ ./kclient irc.freenode.net 7000
     NOTICE AUTH :*** Looking up your hostname...      NOTICE AUTH :*** Looking up your hostname...
     NOTICE AUTH :*** Found your hostname, welcome back      NOTICE AUTH :*** Found your hostname, welcome back
     NOTICE AUTH :*** Checking ident      NOTICE AUTH :*** Checking ident
     NOTICE AUTH :*** No identd (auth) response      NOTICE AUTH :*** No identd (auth) response
     _USER guest tolmoon tolsun :Ronnie Reagan      _USER guest tolmoon tolsun :Ronnie Reagan
     NICK Wiz_      NICK Wiz_
     :herbert.freenode.net 001 Wiz :Welcome to the freenode IRC Network Wiz      :herbert.freenode.net 001 Wiz :Welcome to the freenode IRC Network Wiz
     ^C      ^C 
          
   
 (Whatever is in _italics_ it is what we type.)  (Whatever is in _italics_ it is what we type.) 
   
 ##   More examples  ##   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).  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)     [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  #   Documentation
   
   1. [kqueue, kevent NetBSD Manual Pages](http://netbsd.gw.com/cgi-bin/man-cgi?kqueue++NetBSD-current)    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)    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/)    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)    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.2


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