Tout d'abord, je dois préciser que, du fait de la nature multi-tâches préemptive de Linux, on ne peut pas garantir à un programme en mode utilisateur un contrôle exact du temps. Votre processus peut perdre l'usage du processeur à n'importe quel instant pour une période allant d'environ 20 millisecondes à quelques secondes (sur un système lourdement chargé). Néanmoins, pour la plupart des applications utilisant les ports d'E/S, cela ne pose pas de problèmes. Pour minimiser cet inconvénient, vous pouvez augmenter la priorité (avec nice) de votre programme.
Il y a eu des discussions sur des projets de noyaux Linux temps-réel prenant ce phénomène en compte dans comp.os.linux.development.system, mais j'ignore leur avancement ; renseignez-vous dans ce groupe de discussion. Si vous en savez davantage, envoyez-moi un message...
Maintenant, commençons par le plus facile. Pour des délais de plusieurs secondes, la meilleure fonction reste probablement sleep(3). Pour des attentes de quelques dixièmes de secondes (20 ms semble un minimum), usleep(3) devrait convenir. Ces fonctions rendent le processeur aux autres processus, ce qui ne gâche pas de temps machine. Consultez les pages des manuels pour les détails.
Pour des temporisations inférieures à 20 millisecondes environ (suivant la vitesse de votre processeur et de votre machine, ainsi que la charge du système), il faut proscrire l'abandon du processeur car l'ordonnanceur de Linux ne rendrait le contrôle à votre processus qu'après 20 millisecondes minimum (en général). De ce fait, pour des temporisations courtes, usleep(3) attendra souvent sensiblement plus longtemps que ce que vous avez spécifié, au moins 20 ms.
Pour les délais courts (de quelques dizaines de microsecondes à quelques millisecondes), la méthode la plus simple consiste à utiliser udelay(), définie dans /usr/include/asm/delay.h (linux/include/asm-i386/delay.h). udelay() prend comme unique argument le nombre de microsecondes à attendre (unsigned long) et ne renvoie rien. L'attente dure quelques microsecondes de plus que le paramètre spécifié à cause du temps de calcul de la durée d'attente (voyez delay.h pour les détails).
Pour utiliser udelay() en dehors du noyau, la variable (unsigned long) loops_per_sec doit être être définie avec la bonne valeur. Autant que je sache, la seule façon de récupérer cette valeur depuis le noyau consiste à lire le nombre de BogoMips dans /proc/cpuinfo puis à le multiplier par 500000. On obtient ainsi une évaluation (imprécise) de loops_per_sec.
Pour les temporisations encore plus courtes, il existe plusieurs solutions. Ecrire n'importe quel octet sur le port 0x80 (voyez plus haut la manière de procéder) doit provoquer une attente d'exactement 1 microseconde, quelque soit le type et la vitesse de votre processeur. Cette écriture ne devrait pas avoir d'effets secondaires sur une machine standard (et certains pilotes de périphériques du noyau l'utilisent). C'est ainsi que {in|out}{b|w}_p() réalise normalement sa temporisation (voyez asm/io.h).
Si vous connaissez le type de processeur et la vitesse de l'horloge de la machine sur laquelle votre programme tournera, vous pouvez coder des délais plus courts "en dur" en exécutant certaines instructions d'assembleur (mais souvenez-vous que votre processus peut perdre le processeur à tout instant, et, par conséquent, que l'attente peut, de temps à autres, s'avérer beaucoup plus importante). Dans la table suivante, la durée d'un cycle d'horloge est déterminée par la vitesse interne du processeur~; par exemple, pour un processeur à 50MHz (486DX-50 ou 486DX2-50), un cycle prend 1/50000000 seconde.
Instruction cycles sur i386 cycles sur i486
nop 3 1
xchg %ax,%ax 3 3
or %ax,%ax 2 1
mov %ax,%ax 2 1
add %ax,0 2 1
{source : Borland Turbo Assembler 3.0 Quick Reference}
(désolé, je n'ai pas de valeurs pour les Pentiums~ ce sont probablement les mêmes que pour i486)
(Je ne connais pas d'instruction qui n'utilise qu'un seul cycle sur i386)
Les instructions nop et xchg du tableau n'ont pas d'effets de bord. Les autres peuvent modifier le registre des indicateurs, mais cela ne devrait pas avoir de conséquences puisque gcc est sensé le détecter.
Pour vous servir de cette astuce, appelez asm("intruction"); dans votre programme. Pour "instruction", utilisez la même syntaxe que dans la table précédente ; pour avoir plusieurs instructions dans un même asm(), faites asm("instruction; instruction; instruction");. Comme asm() est traduit en langage d'assemblage "inline" par gcc, il n'y a pas de perte de temps consécutive à un éventuel appel de fonction.
L'architecture des Intel x86 n'autorise pas de temporisations inférieures à un cycle d'horloge.
Pour des chronométrages à la seconde près, le plus simple consiste probablement à utiliser time(2). Pour des temps plus fins, gettimeofday(2) fournit une précision d'une microseconde (voyez toutefois, plus haut, les remarques concernant l'ordonnancement).
Si vous désirez que votre processus reçoive un signal après un certain laps de temps, utilisez setitimer(2). Consultez les pages des manuels des différentes fonctions pour les détails.
Chapitre suivant, Chapitre Précédent
Table des matières de ce chapitre, Table des matières générale
Début du document, Début de ce chapitre