Provided by:
manpages-fr-dev_3.27fr1.4-1_all 
NOM
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - Multiplexage
d'entrees-sorties synchrones
SYNOPSIS
/* D'apres POSIX.1-2001 */
#include <sys/select.h>
/* D'apres les standards precedents */
#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 fonctionnalites pour la glibc (consultez
feature_test_macros(7)) :
pselect() : _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
DESCRIPTION
select() (ou pselect()) est utilise pour superviser efficacement
plusieurs descripteurs de fichiers pour verifier si l'un d'entre eux
est ou devient << pret >> ; c'est-a-dire savoir si des entrees-sorties
deviennent possibles ou si une << condition exceptionnelle >> est
survenue sur l'un des descripteurs.
Ses parametres principaux sont trois << ensembles >> de descripteurs de
fichiers : readfds, writefds et exceptfds. Chaque ensemble est de type
fd_set, et son contenu peut etre manipule avec les macros FD_CLR(),
FD_ISSET(), FD_SET(), et FD_ZERO(). Un ensemble nouvellement declare
doit d'abord etre efface en utilisant FD_ZERO(). select() modifie le
contenu de ces ensembles selon les regles ci-dessous. Apres un appel a
select(), vous pouvez verifier si un descripteur de fichier est
toujours present dans l'ensemble a l'aide de la macro FD_ISSET().
FD_ISSET() renvoie une valeur non nulle si un descripteur de fichier
indique est present dans un ensemble et zero s'il ne l'est pas.
FD_CLR() retire un descripteur de fichier d'un ensemble.
Arguments
readfds
Cet ensemble est examine afin de determiner si des donnees sont
disponibles en lecture a partir d'un de ses descripteurs de
fichier. Suite a un appel a select(), readfds ne contient plus
aucun de ses descripteurs de fichiers a l'exception de ceux qui
sont immediatement disponibles pour une lecture.
writefds
Cet ensemble est examine afin de determiner s'il y a de l'espace
afin d'ecrire des donnees dans un de ses descripteurs de
fichier. Suite a un appel a select(), writefds ne contient plus
aucun de ses descripteurs de fichiers a l'exception de ceux qui
sont immediatement disponibles pour une ecriture.
exceptfds
Cet ensemble est examine pour des << conditions
exceptionnelles >>. En pratique, seule une condition
exceptionnelle est courante : la disponibilite de donnees
hors-bande (OOB : Out Of Band) en lecture sur une socket TCP.
Consultez recv(2), send(2) et tcp(7) pour plus de details sur
les donnees 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
a un appel a select(), exceptfds ne contient plus aucun de ses
descripteurs de fichier a 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 a
chacun des ensembles, vous devez determiner la valeur entiere
maximale de tous ces derniers, puis ajouter un a cette valeur,
et la passer comme parametre nfds.
utimeout
Il s'agit du temps le plus long que select() pourrait attendre
avant de rendre la main, meme si rien d'interessant n'est
arrive. Si cette valeur est positionnee a NULL, alors, select()
bloque indefiniment dans l'attente qu'un descripteur de fichier
devienne pret. utimeout peut etre positionne a zero seconde, ce
qui provoque le retour immediat de select(), en indiquant quels
descripteurs de fichiers etaient prets au moment de l'appel. La
structure struct timeval est definie comme :
struct timeval {
time_t tv_sec; /* secondes */
long tv_usec; /* microsecondes */
};
ntimeout
Ce parametre de pselect() a la meme signification que utimeout,
mais struct timespec a une precision a la nanoseconde comme
explicite ci-dessous :
struct timespec {
long tv_sec; /* secondes */
long tv_nsec; /* nanosecondes */
};
sigmask
Cet argument renferme un ensemble de signaux que le noyau doit
debloquer (c'est-a-dire supprimer du masque de signaux du thread
appelant) pendant que l'appelant est bloque 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
bloques a l'entree et la sortie de la fonction. Dans ce cas,
pselect() se comporte alors de facon identique a select().
Combinaison d''ev'enements de signaux et de donn'ees
pselect() est utile si vous attendez un signal ou qu'un descripteur de
fichier deviennent pret pour des entrees-sorties. Les programmes qui
recoivent des signaux utilisent generalement le gestionnaire de signal
uniquement pour lever un drapeau global. Le drapeau global indique que
l'evenement doit etre traite dans la boucle principale du programme. Un
signal provoque l'arret de l'appel select() (ou pselect()) avec errno
positionnee a EINTR. Ce comportement est essentiel afin que les signaux
puissent etre traites dans la boucle principale du programme, sinon
select() bloquerait indefiniment. Ceci etant, la boucle principale
implante quelque part une condition verifiant le drapeau global, et
l'on doit donc se demander : que se passe-t-il si un signal est leve
apres la condition mais avant l'appel a select() ? La reponse est que
select() bloquerait indefiniment, meme si un signal est en fait en
attente. Cette "race condition" est resolue par l'appel pselect(). Cet
appel peut etre utilise afin de definir le masque des signaux qui sont
censes etre recus que durant l'appel a pselect(). Par exemple, disons
que l'evenement en question est la fin d'un processus fils. Avant le
demarrage de la boucle principale, nous bloquerions SIGCHLD en
utilisant sigprocmask(2). Notre appel pselect() debloquerait SIGCHLD en
utilisant le masque de signaux vide. Le programme ressemblerait a
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 a pselect(). (Code omis.) */
r = pselect(nfds, &readfds, &writefds, &exceptfds,
NULL, &empty_mask);
if (r == -1 && errno != EINTR) {
/* Gerer les erreurs */
}
if (got_SIGCHLD) {
got_SIGCHLD = 0;
/* Gerer les evenements signales ici; e.g., wait() pour
que tous les fils se terminent. (Code omis.) */
}
/* corps principal du programme */
}
}
Pratique
Quelle est donc la finalite de select() ? Ne peut on pas simplement
lire et ecrire dans les descripteurs chaque fois qu'on le souhaite ?
L'objet de select() est de surveiller de multiples descripteurs
simultanement et d'endormir proprement le processus s'il n'y a pas
d'activite. Les programmeurs UNIX se retrouvent souvent dans une
situation dans laquelle ils doivent gerer des entrees-sorties provenant
de plus d'un descripteur de fichier et dans laquelle le flux de donnees
est intermittent. Si vous deviez creer une sequence d'appels read(2) et
write(2), vous vous retrouveriez potentiellement bloque sur un de vos
appels attendant pour lire ou ecrire des donnees a partir/vers un
descripteur de fichier, alors qu'un autre descripteur de fichier est
inutilise bien qu'il soit pret pour des entrees-sorties. select() gere
efficacement cette situation.
R`egles de select
De nombreuses personnes qui essaient d'utiliser select() obtiennent un
comportement difficile a comprendre et produisent des resultats non
portables ou des effets de bord. Par exemple, le programme ci-dessus
est ecrit avec precaution afin de ne bloquer nulle part, meme 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
a controler lors de l'utilisation de select().
1. Vous devriez toujours essayer d'utiliser select() sans timeout.
Votre programme ne devrait rien avoir a faire s'il n'y a pas de
donnees disponibles. Le code dependant de timeouts n'est en general
pas portable et difficile a deboguer.
2. La valeur nfds doit etre calculee correctement pour des raisons
d'efficacite comme explique plus haut.
3. Aucun descripteur de fichier ne doit etre ajoute a un quelconque
ensemble si vous ne projetez pas de verifier son etat apres un
appel a select(), et de reagir de facon adequate. Voir la regle
suivante.
4. Apres le retour de select(), tous les descripteurs de fichier dans
tous les ensembles devraient etre testes pour savoir s'ils sont
prets.
5. Les fonctions read(2), recv(2), write(2) et send(2) ne lisent ou
n'ecrivent pas forcement la quantite totale de donnees specifiee.
Si elles lisent/ecrivent la quantite 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 gerer le cas ou vos fonctions
traitent seulement l'envoi ou la reception d'un unique octet.
6. Ne lisez/n'ecrivez jamais seulement quelques octets a la fois a
moins que vous ne soyez absolument sur de n'avoir qu'une faible
quantite de donnees a traiter. Il est parfaitement inefficace de ne
pas lire/ecrire autant de donnees que vous pouvez en stocker a
chaque fois. Les tampons de l'exemple ci-dessous font 1024 octets
bien qu'ils aient facilement pu etre 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 positionne a EINTR
ou EAGAIN (EWOULDBLOCK) ce qui ne releve pas d'une erreur. Ces
resultats doivent etre correctement geres (cela n'est pas fait
correctement ci-dessus). Si votre programme n'est pas cense
recevoir de signal, alors, il est hautement improbable que vous
obteniez EINTR. Si votre programme n'a pas configure les
entrees-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) echoue
avec une erreur autre que celles indiquees en 7., ou si l'une des
fonctions d'entree renvoie 0, indiquant une fin de fichier, vous ne
devriez pas utiliser ce descripteur a nouveau pour un appel a
select(). Dans l'exemple ci-dessous, le descripteur est
immediatement ferme et ensuite est positionne a -1 afin qu'il ne
soit pas inclus dans un ensemble.
10. La valeur de timeout doit etre initialisee a chaque nouvel appel a
select(), puisque des systemes 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 effectue dans une boucle alors les ensembles doivent
etre reinitialises avant chaque appel.
'Emulation de usleep
Sur les systemes qui ne possedent pas la fonction usleep(3), vous
pouvez appeler select() avec un timeout a valeur finie et sans
descripteur de fichier de la facon 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 systemes Unix.
VALEUR RENVOY'EE
En cas de succes, select() renvoie le nombre total de descripteurs de
fichiers encore presents dans les ensembles de descripteurs de fichier.
En cas de timeout echu, alors les descripteurs de fichier devraient
tous etre vides (mais peuvent ne pas l'etre sur certains systemes). Par
contre, la valeur renvoyee est zero.
Une valeur de retour egale a -1 indique une erreur, errno est alors
positionne de facon adequate. En cas d'erreur, le contenu des ensembles
renvoyes et le contenu de la structure de timeout sont indefinis et ne
devraient pas etre exploites. pselect() ne modifie cependant jamais
ntimeout.
NOTES
De facon generale, tous les systemes d'exploitation qui gerent les
sockets proposent egalement select(). select() peut etre utilise pour
resoudre de facon portable et efficace de nombreux problemes que des
programmeurs naifs essaient de resoudre avec des threads, des forks,
des IPC, des signaux, des memoires partagees et d'autres methodes peu
elegantes.
L'appel systeme poll(2) a les memes fonctionnalites que select(), tout
en etant legerement plus efficace quand il doit surveiller des
ensembles de descripteurs creux. Il est disponible sur la plupart des
systemes de nos jours, mais etait historiquement moins portable que
select().
L'API epoll(7) specifique a 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'utilite reelle 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 donnees 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;
}
/* Verifie si l'ecriture de donnees a rattrape la lecture de donnees */
if (buf1_written == buf1_avail)
buf1_written = buf1_avail = 0;
if (buf2_written == buf2_avail)
buf2_written = buf2_avail = 0;
/* une extremite a ferme la connexion, continue
d'ecrire vers l'autre extremite jusqu'a 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 donnees hors bande OOB transmis
par les serveurs telnet. Il gere le probleme epineux des flux de
donnees bidirectionnels simultanes. Vous pourriez penser qu'il est plus
efficace d'utiliser un appel fork(2) et de dedier une tache a chaque
flux. Cela devient alors plus delicat que vous ne l'imaginez. Une autre
idee est de configurer les entrees-sorties comme non bloquantes en
utilisant fcntl(2). Cela pose egalement probleme puisque ca vous force
a utiliser des timeouts inefficaces.
Le programme ne gere pas plus d'une connexion a la fois bien qu'il soit
aisement extensible a une telle fonctionnalite en utilisant une liste
chainee 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.27 du projet man-pages
Linux. Une description du projet et des instructions pour signaler des
anomalies peuvent etre trouvees a l'adresse
<URL:http://www.kernel.org/doc/man-pages/>.
TRADUCTION
Depuis 2010, cette traduction est maintenue a l'aide de l'outil po4a
<URL:http://po4a.alioth.debian.org/> par l'equipe de traduction
francophone au sein du projet perkamon
<URL:http://perkamon.alioth.debian.org/>.
Stephan Rafin (2002), Alain Portal
<URL:http://manpagesfr.free.fr/> (2006). Julien Cristau et l'equipe
francophone de traduction de Debian (2006-2009).
Veuillez signaler toute erreur de traduction en ecrivant a
<debian-l10n-french@lists.debian.org> ou par un rapport de bogue sur le
paquet manpages-fr.
Vous pouvez toujours avoir acces a la version anglaise de ce document
en utilisant la commande << man -L C <section> <page_de_man> >>.