Loading

NOM

       select,  pselect,  FD_CLR,  FD_ISSET,  FD_SET,  FD_ZERO  - Multiplexage
       d’entrées-sorties synchrones.

SYNOPSIS

       /* D’après POSIX.1-2001 */
       #include <sys/select.h>

       /* D’après les standards précédents */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *utimeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

       #include <sys/select.h>

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, const struct timespec *ntimeout,
                   const sigset_t *sigmask);

   Exigences de  macros  de  test  de  fonctionnalités  pour  la  glibc  (voir
   feature_test_macros(7)) :

       pselect() : _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >=600

       select()  (ou  pselect())  est  utilisé  pour  superviser  efficacement
       plusieurs descripteurs de fichiers pour vérifier si  l’un  d’entre  eux
       est  ou  devient  « prêt » ; c’est-à-dire savoir si des entrées-sorties
       deviennent  possibles  ou  si  une  «  condition  exceptionnelle »  est
       survenue sur l’un des descripteurs.

       Ses  paramètres  principaux sont trois « ensembles » de descripteurs de
       fichiers : readfds, writefds et exceptfds. Chaque ensemble est de  type
       fd_set,  et  son  contenu  peut être manipulé avec les macros FD_CLR(),
       FD_ISSET(), FD_SET(), et FD_ZERO(). Un  ensemble  nouvellement  déclaré
       doit  d’abord  être  effacé en utilisant FD_ZERO(). select() modifie le
       contenu de ces ensembles selon les règles ci-dessous. Après un appel  à
       select(),  vous  pouvez  vérifier  si  un  descripteur  de  fichier est
       toujours présent dans l’ensemble  à  l’aide  de  la  macro  FD_ISSET().
       FD_ISSET()  renvoie  une  valeur non nulle si un descripteur de fichier
       indiqué est présent dans  un  ensemble  et  zéro  s’il  ne  l’est  pas.
       FD_CLR() retire un descripteur de fichier d’un ensemble.

   Arguments
       readfds
              Cet  ensemble est examiné afin de déterminer si des données sont
              disponibles en lecture à partir  d’un  de  ses  descripteurs  de
              fichier.  Suite  à un appel à select(), readfds ne contient plus
              aucun de ses descripteurs de fichiers à l’exception de ceux  qui
              sont immédiatement disponibles pour une lecture.

       writefds
              Cet ensemble est examiné afin de déterminer s’il y a de l’espace
              afin d’écrire  des  données  dans  un  de  ses  descripteurs  de
              fichier.  Suite à un appel à select(), writefds ne contient plus
              aucun de ses descripteurs de fichiers à l’exception de ceux  qui
              sont immédiatement disponibles pour une écriture.

       exceptfds
              Cet    ensemble    est    examiné    pour    des    « conditions
              exceptionnelles ».   En   pratique,    seule    une    condition
              exceptionnelle   est  courante :  la  disponibilité  de  données
              hors-bande (OOB : Out Of Band) en lecture sur  une  socket  TCP.
              Consultez  recv(2),  send(2)  et tcp(7) pour plus de détails sur
              les données hors bande. Un autre cas moins courant  dans  lequel
              select(2) indique une condition exceptionnelle survient avec des
              pseudo terminaux en mode paquet ; consultez tty_ioctl(4).) Suite
              à  un  appel à select(), exceptfds ne contient plus aucun de ses
              descripteurs de fichier à l’exception de ceux pour lesquels  une
              condition exceptionnelle est survenue.

       nfds   Il s’agit d’un entier valant un de plus que n’importe lequel des
              descripteurs de fichier  de  tous  les  ensembles.  En  d’autres
              termes,  lorsque  vous  ajoutez  des  descripteurs  de fichier à
              chacun des ensembles, vous devez déterminer  la  valeur  entière
              maximale  de  tous ces derniers, puis ajouter un à cette valeur,
              et la passer comme paramètre nfds.

       utimeout
              Il s’agit du temps le plus long que select()  pourrait  attendre
              avant  de  rendre  la  main,  même  si  rien d’intéressant n’est
              arrivé. Si cette valeur est positionnée à NULL, alors,  select()
              bloque  indéfiniment dans l’attente qu’un descripteur de fichier
              devienne prêt. utimeout peut être positionné à zéro seconde,  ce
              qui  provoque le retour immédiat de select(), en indiquant quels
              descripteurs de fichiers étaient prêts au moment de l’appel.  La
              structure struct timeval est définie comme :

                  struct timeval {
                      time_t tv_sec;    /* secondes */
                      long tv_usec;     /* microsecondes */
                  };

       ntimeout
              Ce  paramètre de pselect() a la même signification que utimeout,
              mais struct timespec a une  précision  à  la  nanoseconde  comme
              explicité ci-dessous :

                  struct timespec {
                      long tv_sec;    /* secondes */
                      long tv_nsec;   /* nanosecondes */
                  };

       sigmask
              Cet  argument  renferme un ensemble de signaux que le noyau doit
              débloquer (c’est-à-dire supprimer du masque de signaux du thread
              appelant)  pendant que l’appelant est bloqué par pselect() (voir
              sigaddset(3) et sigprocmask(2)). Il peut valoir NULL et, dans ce
              cas,  il  ne  modifie  pas  l’ensemble des signaux non bloqués à
              l’entrée et la sortie de la fonction. Dans ce cas, pselect()  se
              comporte alors de façon identique à select().

   Combinaison dévénements de signaux et de données
       pselect()  est utile si vous attendez un signal ou qu’un descripteur de
       fichier deviennent prêt pour des entrées-sorties.  Les  programmes  qui
       reçoivent  des signaux utilisent généralement le gestionnaire de signal
       uniquement pour lever un drapeau global. Le drapeau global indique  que
       l’événement doit être traité dans la boucle principale du programme. Un
       signal provoque l’arrêt de l’appel select() (ou pselect())  avec  errno
       positionnée à EINTR. Ce comportement est essentiel afin que les signaux
       puissent être traités dans la boucle  principale  du  programme,  sinon
       select()  bloquerait  indéfiniment.  Ceci  étant,  la boucle principale
       implante quelque part une condition vérifiant  le  drapeau  global,  et
       l’on  doit  donc  se demander : que se passe-t-il si un signal est levé
       après la condition mais avant l’appel à select() ? La réponse  est  que
       select()  bloquerait  indéfiniment,  même  si  un signal est en fait en
       attente. Cette "race condition" est résolue par l’appel pselect().  Cet
       appel  peut être utilisé afin de définir le masque des signaux qui sont
       censés être reçus que durant l’appel à pselect(). Par  exemple,  disons
       que  l’événement  en  question est la fin d’un processus fils. Avant le
       démarrage  de  la  boucle  principale,  nous  bloquerions  SIGCHLD   en
       utilisant sigprocmask(2). Notre appel pselect() débloquerait SIGCHLD en
       utilisant le masque de  signaux  vide.  Le  programme  ressemblerait  à
       ceci :

       static volatile sig_atomic_t got_SIGCHLD = 0;

       static void
       child_sig_handler(int sig)
       {
           got_SIGCHLD = 1;
       }

       int
       main(int argc, char *argv[])
       {
           sigset_t sigmask, empty_mask;
           struct sigaction sa;
           fd_set readfds, writefds, exceptfds;
           int r;

           sigemptyset(&sigmask);
           sigaddset(&sigmask, SIGCHLD);
           if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == -1) {
               perror("sigprocmask");
               exit(EXIT_FAILURE);
           }

           sa.sa_flags = 0;
           sa.sa_handler = child_sig_handler;
           sigemptyset(&sa.sa_mask);
           if (sigaction(SIGCHLD, &sa, NULL) == -1) {
               perror("sigaction");
               exit(EXIT_FAILURE);
           }

           sigemptyset(&empty_mask);

           for (;;) {          /* main loop */
               /* Initialiser readfds, writefds et exceptfds
                  avant l’appel à pselect(). (Code omis.) */

               r = pselect(nfds, &readfds, &writefds, &exceptfds,
                           NULL, &empty_mask);
               if (r == -1 && errno != EINTR) {
                   /* Gérer les erreurs */
               }

               if (got_SIGCHLD) {
                   got_SIGCHLD = 0;

                   /* Gérer les événements signalés ici; e.g., wait() pour
                      que tous les fils se terminent. (Code omis.) */
               }

               /* corps principal du programme */
           }
       }

   Pratique
       Quelle  est  donc  la  finalité de select() ? Ne peut on pas simplement
       lire et écrire dans les descripteurs chaque fois  qu’on  le  souhaite ?
       L’objet  de  select()  est  de  surveiller  de  multiples  descripteurs
       simultanément et d’endormir proprement le  processus  s’il  n’y  a  pas
       d’activité.  Les  programmeurs  UNIX  se  retrouvent  souvent  dans une
       situation dans laquelle ils doivent gérer des entrées-sorties provenant
       de plus d’un descripteur de fichier et dans laquelle le flux de données
       est intermittent. Si vous deviez créer une séquence d’appels read(2) et
       write(2),  vous  vous retrouveriez potentiellement bloqué sur un de vos
       appels attendant pour lire ou  écrire  des  données  à  partir/vers  un
       descripteur  de  fichier,  alors qu’un autre descripteur de fichier est
       inutilisé bien qu’il soit prêt pour des entrées-sorties. select()  gère
       efficacement cette situation.

   Règles de select
       De  nombreuses personnes qui essaient d’utiliser select() obtiennent un
       comportement difficile à comprendre et  produisent  des  résultats  non
       portables  ou  des  effets de bord. Par exemple, le programme ci-dessus
       est écrit avec précaution afin de ne bloquer nulle part, même  s’il  ne
       positionne  pas ses descripteurs de fichier en mode non bloquant.Il est
       facile d’introduire des erreurs subtiles qui annuleraient l’avantage de
       l’utilisation  de select(), aussi, voici une liste de points essentiels
       à contrôler lors de l’utilisation de select().

       1.  Vous devriez toujours essayer  d’utiliser  select()  sans  timeout.
           Votre  programme  ne  devrait  rien avoir à faire s’il n’y a pas de
           données disponibles. Le code dépendant de timeouts n’est en général
           pas portable et difficile à déboguer.

       2.  La  valeur  nfds  doit  être calculée correctement pour des raisons
           d’efficacité comme expliqué plus haut.

       3.  Aucun descripteur de fichier ne doit être ajouté  à  un  quelconque
           ensemble  si  vous  ne  projetez  pas de vérifier son état après un
           appel à select(), et de réagir de façon  adéquate.  Voir  la  règle
           suivante.

       4.  Après  le retour de select(), tous les descripteurs de fichier dans
           tous les ensembles devraient être testés  pour  savoir  s’ils  sont
           prêts.

       5.  Les  fonctions  read(2),  recv(2), write(2) et send(2) ne lisent ou
           n’écrivent pas forcément la quantité totale de  données  spécifiée.
           Si  elles  lisent/écrivent la quantité totale, c’est parce que vous
           avez une faible charge de trafic et un flux rapide.  Ce  n’est  pas
           toujours  le  cas.  Vous  devriez  gérer  le  cas  où vos fonctions
           traitent seulement l’envoi ou la réception d’un unique octet.

       6.  Ne lisez/n’écrivez jamais seulement quelques octets  à  la  fois  à
           moins  que  vous  ne  soyez absolument sûr de n’avoir qu’une faible
           quantité de données à traiter. Il est parfaitement inefficace de ne
           pas  lire/écrire  autant  de  données  que vous pouvez en stocker à
           chaque fois. Les tampons de l’exemple ci-dessous font  1024  octets
           bien qu’ils aient facilement pu être rendus plus grands.

       7.  Les  fonctions  read(2),  recv(2),  write(2)  et send(2) tout comme
           l’appel select() peuvent renvoyer -1 avec errno positionné à  EINTR
           ou  EAGAIN  (EWOULDBLOCK)  ce  qui  ne relève pas d’une erreur. Ces
           résultats doivent être correctement  gérés  (cela  n’est  pas  fait
           correctement   ci-dessus).  Si  votre  programme  n’est  pas  censé
           recevoir de signal, alors, il est  hautement  improbable  que  vous
           obteniez   EINTR.   Si   votre  programme  n’a  pas  configuré  les
           entrées-sorties en mode non  bloquant,  vous  n’obtiendrez  pas  de
           EAGAIN.

       8.  N’appelez  jamais  read(2),  recv(2),  write(2)  ou send(2) avec un
           tampon de taille nulle.

       9.  Si l’une des fonctions read(2), recv(2), write(2) et send(2) échoue
           avec  une  erreur autre que celles indiquées en 7., ou si l’une des
           fonctions d’entrée renvoie 0, indiquant une fin de fichier, vous ne
           devriez  pas  utiliser  ce  descripteur  à  nouveau pour un appel à
           select().   Dans   l’exemple   ci-dessous,   le   descripteur   est
           immédiatement  fermé  et  ensuite est positionné à -1 afin qu’il ne
           soit pas inclus dans un ensemble.

       10. La valeur de timeout doit être initialisée à chaque nouvel appel  à
           select(),   puisque   des   systèmes  d’exploitation  modifient  la
           structure. Cependant, pselect() ne  modifie  pas  sa  structure  de
           timeout.

       11. Comme  select()  modifie ses ensembles de descripteurs de fichiers,
           si l’appel est effectué dans une boucle alors les ensembles doivent
           être ré-initialisés avant chaque appel.

   Émulation de usleep
       Sur  les  systèmes  qui  ne  possèdent  pas la fonction usleep(3), vous
       pouvez appeler  select()  avec  un  timeout  à  valeur  finie  et  sans
       descripteur de fichier de la façon suivante :

           struct timeval tv;
           tv.tv_sec = 0;
           tv.tv_usec = 200000;  /* 0.2 secondes */
           select(0, NULL, NULL, NULL, &tv);

       Le fonctionnement n’est cependant garanti que sur les systèmes Unix.

VALEUR RENVOYÉE

       En  cas  de succès, select() renvoie le nombre total de descripteurs de
       fichiers encore présents dans les ensembles de descripteurs de fichier.

       En  cas  de  timeout  échu, alors les descripteurs de fichier devraient
       tous être vides (mais peuvent ne pas l’être sur certains systèmes). Par
       contre, la valeur renvoyée est zéro.

       Une  valeur  de  retour  égale à -1 indique une erreur, errno est alors
       positionné de façon adéquate. En cas d’erreur, le contenu des ensembles
       renvoyés  et le contenu de la structure de timeout sont indéfinis et ne
       devraient pas être exploités. pselect()  ne  modifie  cependant  jamais
       ntimeout.

NOTES

       De  façon  générale,  tous  les  systèmes d’exploitation qui gèrent les
       sockets proposent également select(). select() peut être  utilisé  pour
       résoudre  de  façon  portable et efficace de nombreux problèmes que des
       programmeurs naïfs essaient de résoudre avec des  threads,  des  forks,
       des  IPC,  des signaux, des mémoires partagées et d’autres méthodes peu
       élégantes.

       L’appel système poll(2) a les mêmes fonctionnalités que select(),  tout
       en  étant  légèrement  plus  efficace  quand  il  doit  surveiller  des
       ensembles de descripteurs creux. Il est disponible sur la  plupart  des
       systèmes  de  nos  jours,  mais était historiquement moins portable que
       select().

       L’API epoll(7) spécifique à Linux fournit une interface  plus  efficace
       que  select(2)  et  poll(2)  lorsque  l’on surveille un grand nombre de
       descripteurs de fichier.

EXEMPLE

       Voici un exemple qui montre mieux l’utilité réelle de select(). Le code
       ci-dessous  consiste en un programme de « TCP forwarding » qui redirige
       un port TCP vers un autre.

       #include <stdlib.h>
       #include <stdio.h>
       #include <unistd.h>
       #include <sys/time.h>
       #include <sys/types.h>
       #include <string.h>
       #include <signal.h>
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <arpa/inet.h>
       #include <errno.h>

       static int forward_port;

       #undef max
       #define max(x,y) ((x) > (y) ? (x) : (y))

       static int
       listen_socket(int listen_port)
       {
           struct sockaddr_in a;
           int s;
           int yes;

           if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
               perror("socket");
               return -1;
           }
           yes = 1;
           if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
                   (char *) &yes, sizeof(yes)) == -1) {
               perror("setsockopt");
               close(s);
               return -1;
           }
           memset(&a, 0, sizeof(a));
           a.sin_port = htons(listen_port);
           a.sin_family = AF_INET;
           if (bind(s, (struct sockaddr *) &a, sizeof(a)) == -1) {
               perror("bind");
               close(s);
               return -1;
           }
           printf("accepting connections on port %d\n", listen_port);
           listen(s, 10);
           return s;
       }

       static int
       connect_socket(int connect_port, char *address)
       {
           struct sockaddr_in a;
           int s;

           if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
               perror("socket");
               close(s);
               return -1;
           }

           memset(&a, 0, sizeof(a));
           a.sin_port = htons(connect_port);
           a.sin_family = AF_INET;

           if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) {
               perror("bad IP address format");
               close(s);
               return -1;
           }

           if (connect(s, (struct sockaddr *) &a, sizeof(a)) == -1) {
               perror("connect()");
               shutdown(s, SHUT_RDWR);
               close(s);
               return -1;
           }
           return s;
       }

       #define SHUT_FD1 do {                                \
                            if (fd1 >= 0) {                 \
                                shutdown(fd1, SHUT_RDWR);   \
                                close(fd1);                 \
                                fd1 = -1;                   \
                            }                               \
                        } while (0)

       #define SHUT_FD2 do {                                \
                            if (fd2 >= 0) {                 \
                                shutdown(fd2, SHUT_RDWR);   \
                                close(fd2);                 \
                                fd2 = -1;                   \
                            }                               \
                        } while (0)

       #define BUF_SIZE 1024

       int
       main(int argc, char *argv[])
       {
           int h;
           int fd1 = -1, fd2 = -1;
           char buf1[BUF_SIZE], buf2[BUF_SIZE];
           int buf1_avail, buf1_written;
           int buf2_avail, buf2_written;

           if (argc != 4) {
               fprintf(stderr, "Utilisation\n\tfwd <listen-port> "
                        "<forward-to-port> <forward-to-ip-address>\n");
               exit(EXIT_FAILURE);
           }

           signal(SIGPIPE, SIG_IGN);

           forward_port = atoi(argv[2]);

           h = listen_socket(atoi(argv[1]));
           if (h == -1)
               exit(EXIT_FAILURE);

           for (;;) {
               int r, nfds = 0;
               fd_set rd, wr, er;

               FD_ZERO(&rd);
               FD_ZERO(&wr);
               FD_ZERO(&er);
               FD_SET(h, &rd);
               nfds = max(nfds, h);
               if (fd1 > 0 && buf1_avail < BUF_SIZE) {
                   FD_SET(fd1, &rd);
                   nfds = max(nfds, fd1);
               }
               if (fd2 > 0 && buf2_avail < BUF_SIZE) {
                   FD_SET(fd2, &rd);
                   nfds = max(nfds, fd2);
               }
               if (fd1 > 0 && buf2_avail - buf2_written > 0) {
                   FD_SET(fd1, &wr);
                   nfds = max(nfds, fd1);
               }
               if (fd2 > 0 && buf1_avail - buf1_written > 0) {
                   FD_SET(fd2, &wr);
                   nfds = max(nfds, fd2);
               }
               if (fd1 > 0) {
                   FD_SET(fd1, &er);
                   nfds = max(nfds, fd1);
               }
               if (fd2 > 0) {
                   FD_SET(fd2, &er);
                   nfds = max(nfds, fd2);
               }

               r = select(nfds + 1, &rd, &wr, &er, NULL);

               if (r == -1 && errno == EINTR)
                   continue;

               if (r == -1) {
                   perror("select()");
                   exit(EXIT_FAILURE);
               }

               if (FD_ISSET(h, &rd)) {
                   unsigned int l;
                   struct sockaddr_in client_address;

                   memset(&client_address, 0, l = sizeof(client_address));
                   r = accept(h, (struct sockaddr *) &client_address, &l);
                   if (r == -1) {
                       perror("accept()");
                   } else {
                       SHUT_FD1;
                       SHUT_FD2;
                       buf1_avail = buf1_written = 0;
                       buf2_avail = buf2_written = 0;
                       fd1 = r;
                       fd2 = connect_socket(forward_port, argv[3]);
                       if (fd2 == -1)
                           SHUT_FD1;
                       else
                           printf("connexion de %s\n",
                                   inet_ntoa(client_address.sin_addr));
                   }
               }

               /* NB : lecture des données hors bande avant les lectures normales */

               if (fd1 > 0)
                   if (FD_ISSET(fd1, &er)) {
                       char c;

                       r = recv(fd1, &c, 1, MSG_OOB);
                       if (r < 1)
                           SHUT_FD1;
                       else
                           send(fd2, &c, 1, MSG_OOB);
                   }
               if (fd2 > 0)
                   if (FD_ISSET(fd2, &er)) {
                       char c;

                       r = recv(fd2, &c, 1, MSG_OOB);
                       if (r < 1)
                           SHUT_FD1;
                       else
                           send(fd1, &c, 1, MSG_OOB);
                   }
               if (fd1 > 0)
                   if (FD_ISSET(fd1, &rd)) {
                       r = read(fd1, buf1 + buf1_avail,
                                 BUF_SIZE - buf1_avail);
                       if (r < 1)
                           SHUT_FD1;
                       else
                           buf1_avail += r;
                   }
               if (fd2 > 0)
                   if (FD_ISSET(fd2, &rd)) {
                       r = read(fd2, buf2 + buf2_avail,
                                 BUF_SIZE - buf2_avail);
                       if (r < 1)
                           SHUT_FD2;
                       else
                           buf2_avail += r;
                   }
               if (fd1 > 0)
                   if (FD_ISSET(fd1, &wr)) {
                       r = write(fd1, buf2 + buf2_written,
                                  buf2_avail - buf2_written);
                       if (r < 1)
                           SHUT_FD1;
                       else
                           buf2_written += r;
                   }
               if (fd2 > 0)
                   if (FD_ISSET(fd2, &wr)) {
                       r = write(fd2, buf1 + buf1_written,
                                  buf1_avail - buf1_written);
                       if (r < 1)
                           SHUT_FD2;
                       else
                           buf1_written += r;
                   }

               /* Vérifie si l’écriture de données a rattrapé la lecture de données */

               if (buf1_written == buf1_avail)
                   buf1_written = buf1_avail = 0;
               if (buf2_written == buf2_avail)
                   buf2_written = buf2_avail = 0;

               /* une extrémité a fermé la connexion, continue
                  d’écrire vers l’autre extrémité jusqu’à ce
                  que ce soit vide */

               if (fd1 < 0 && buf1_avail - buf1_written == 0)
                   SHUT_FD2;
               if (fd2 < 0 && buf2_avail - buf2_written == 0)
                   SHUT_FD1;
           }
           exit(EXIT_SUCCESS);
       }

       Le programme ci-dessus redirige correctement la plupart  des  types  de
       connexions TCP y compris les signaux de données hors bande OOB transmis
       par les serveurs telnet. Il  gère  le  problème  épineux  des  flux  de
       données bidirectionnels simultanés. Vous pourriez penser qu’il est plus
       efficace d’utiliser un appel fork(2) et de dédier une  tâche  à  chaque
       flux. Cela devient alors plus délicat que vous ne l’imaginez. Une autre
       idée est de configurer les  entrées-sorties  comme  non  bloquantes  en
       utilisant  fcntl(2). Cela pose également problème puisque ça vous force
       à utiliser des timeouts inefficaces.

       Le programme ne gère pas plus d’une connexion à la fois bien qu’il soit
       aisément  extensible  à une telle fonctionnalité en utilisant une liste
       chaînée de tampons — un  pour  chaque  connexion.  Pour  l’instant,  de
       nouvelles connexions provoquent l’abandon de la connexion courante.

VOIR AUSSI

       accept(2),  connect(2), ioctl(2), poll(2), read(2), recv(2), select(2),
       send(2),   sigprocmask(2),   write(2),   sigaddset(3),    sigdelset(3),
       sigemptyset(3), sigfillset(3), sigismember(3), epoll(7)

COLOPHON

       Cette  page  fait  partie  de  la  publication 3.23 du projet man-pages
       Linux. Une description du projet et des instructions pour signaler  des
       anomalies       peuvent       être       trouvées      à      l’adresse
       http://www.kernel.org/doc/man-pages/.

TRADUCTION

       Cette page de manuel a été traduite  par  Stéphan  Rafin  <stephan  DOT
       rafin  AT  laposte  DOT  net> en 2002, puis a été mise à jour par Alain
       Portal <aportal AT  univ-montp2  DOT  fr>  jusqu’en  2006,  et  mise  à
       disposition sur http://manpagesfr.free.fr/.

       Les mises à jour et corrections de la version présente dans Debian sont
       directement gérées par Julien Cristau <jcristau@debian.org> et l’équipe
       francophone de traduction de Debian.

       Veuillez   signaler   toute   erreur   de   traduction  en  écrivant  à
       <debian-l10n-french@lists.debian.org> ou par un rapport de bogue sur le
       paquet manpages-fr.

       Vous  pouvez  toujours avoir accès à la version anglaise de ce document
       en utilisant la commande « man -L C <section> <page_de_man> ».