La fonction select()
Fonction select()
Il existe une fonction sous Unix qui permet de déterminer l'état en I/O d'un fichier.
Or tout est fichier sous Unix, donc de manière plus général, cette fonction permet de connaître l'état en I/O d'un périphérique.
Ceci peut donc être intéressant surtout lorsque la fonction qui est utilisée pour lire ou écrire vers un périphérique est bloquante (lecture au clavier, d'un socket...).
Cette fonction se nomme select()
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
Elle ne se limita pas à déterminer l'état d'un fichier, mais peut traiter plusieurs fichiers en un seul appel. De plus, elle pourra tester dans ce même appel l'état en entrée, sortie et/ou encore les exceptions.
Les includes sous Unix
Vous aurez besoin des trois includes suivant sous Unix:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
Les includes sous Windows
Pour Windows, l'include windows.h intégrant l'ensemble des autres includes, vous n'aurez rien d'autres à faire.
En entrée:
n est le plus grand descripteur de fichier (en valeur !) parmis ceux que l'on veut tester sur les trois ensembles + 1
readfds est un pointeur sur un tampon qui va contenir l'ensemble des états en lecture des descripteurs de fichiers: un appel-système de lecture ne bloquera pas
Un descripteur
en fin de fichier sera considéré comme prêt !
writefds est un pointeur sur un tampon qui va contenir l'ensemble des états en écriture: pour vérifier si une écriture ne bloquera pas !
exceptfds est un pointeur sur un tampon qui va contenir l'ensemble des états en exception: l'arrivée de données hors-bande sur une socket, et la disponibilité d'informations d'état concernant un pseudo-terminal en mode paquet.
timeout est une structure de type timeval dans lequel vous aurez préalablement initialisé le temps maximum d'attente pour lire l'états des descripteurs de fichiers. Ce temps peut être positionné à 0 pour ne pas gérer de délai d'attente.
Evitez de passer
NULL à la place du paramètre timeout, le select() pourrait ne
jamais rendre la main !
En sortie:
select retourne -1 s'il échoue, et errno contient un des codes erreurs suivant:
- EBADF Un descripteur de fichier (dans l'un des ensembles) est invalide.
- EINTR Un signal a été intercepté.
- EINVAL n est négatif
- ENOMEM Pas assez de mémoire pour le noyau.
null si rien ne s'est produit avant le time out
supérieur à 0 sinon
Les ensembles sont modifiés pour indiquer les descripteurs qui ont changé de statut.
Il n'est évidemment
pas nécessaire de tester les trois ensembles. Si un des ensembles ne
doit pas être utilisé, il suffira de passer NULL en paramètre
à la place d'un pointeur valide.
Jamais essayé,
mais indiqué sur tous les sites faisant un man sur select:
Sous Linux timeout est modifié pour indiquer le temps restant mais la plupart des autres implémentations ne le font pas.
Donc il est préférable de ne pas considérer timeout après l'appel à select et de toujours réinitialiser ces valeurs avant un nouvel appel à select !
Macros à utiliser avec la fonction select()
Pour manipuler les 3 ensembles précédents, il faut passer par les macros suivantes:
Tout d'abord effacer tout le contenu d'un ensemble:
FD_ZERO(fd_set *set);
En entrée: Poineur sur l'ensemble à effacer
Une fois un tampon effacé, il faut indiquer les descripteurs de fichier à surveiller: FD_SET(int fd, fd_set *set);
En entrée:
fd le descripteur de fichier à positionner
set le tampon concernant l'ensemble dans lequel vous voulez le positionner. Si vous voulez traiter un descripteur de fichier sur plusieurs ensembles, il faudra répéter la macro autant de fois que nécessaire
De la même manière, vous pouvez retirer un descripteur via la macro FD_CLR(int fd, fd_set *set); qui fonctionne exactement de la même manière que ci-dessus.
Une fois la fonction select() exécutée, vous pouvez tester le résultat en utilisant la macro FD_ISSET(int fd, fd_set *set);
Cette macro atendant les mêmes paramètres que les deux précédentes. Si elle retourne vrai, c'est que le descripteur est accéssible dans l'ensemble que vous avez testé.
Ces macros
ne sont pas optionnelles ! N'essayez pas d'intervenir vous même dans les
tampons de ces ensembles utilisez ces macros, sinon vous n'auriez pas de code
portable !
Effacez toujours un tampon avant de faire un select().
Exemple
Voici l'exemple que l'on trouve systèmatiquement en faisant man select:
Il s'agit de vérifier pendant 5 sec. si un caractère est frappé
au clavier: Donc est-on prêt à lire un caractère sur le
descripteur de fichier 1 ?
Evidemment, 5 secondes, c'est un peu long, vous n'utilisez donc jamais ce code tel quel !
Je ne sais pas qui est à l'origine de ce code...
#include <stdio.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int main(void) { fd_set rfds; struct timeval tv; int retval; /* Surveiller stdin (fd 0) en attente d'entrées */ FD_ZERO(&rfds); FD_SET(0, &rfds); /* Pendant 5 secondes maxi */ tv.tv_sec = 5; tv.tv_usec = 0; retval = select(1, &rfds, NULL, NULL, &tv); /* Considerer tv comme indéfini maintenant ! */ if (retval) /* Si on test FD_ISSET(0, &rfds), celui-ci est vrai, mais comme il n'y a qu'un descripteur, l'appel n'est pas nécessaire */ printf("Données disponibles maintenant\n"); else printf("Pas de données depuis 5 secondes\n"); exit(0); }
On pourra avoir un code similaire avec les sockets