Provided by: manpages-fr-dev_3.65d1p1-1_all bug

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 (consultez feature_test_macros(7)) :

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

DESCRIPTION

       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 pseudoterminaux 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()
              (consultez 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;

           s = socket(AF_INET, SOCK_STREAM, 0);
           if (s == -1) {
               perror("socket");
               return -1;
           }
           yes = 1;
           if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
                   &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;

           s = socket(AF_INET, SOCK_STREAM, 0);
           if (s == -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_FD2;
                       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.65 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

       Depuis 2010, cette traduction est maintenue à l'aide de l'outil po4a <http://po4a.alioth.debian.org/> par
       l'équipe de traduction francophone au sein du projet perkamon <http://perkamon.alioth.debian.org/>.

       Stéphan Rafin (2002),  Alain  Portal  <http://manpagesfr.free.fr/> (2006).  Julien  Cristau  et  l'équipe
       francophone de traduction de Debian (2006-2009).

       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> ».