2. Réglages de haute précision
Contenu de cette section
2.1 Temporisations
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.
2.2 Chronométrages
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
|