PROFDINFO.COM

Votre enseignant d'informatique en ligne

La gestion du temps

Les sources du temps

L'horloge temps réel

L'horloge temps réel (HTR ou RTC pour Real Time Clock) est un circuit sur le carte mère qui gère la date et l'heure du système. Sur les cartes plus ancienne, le circuit utilisé était le Motorola MC146818. Sur les cartes plus récentes, l'horloge temps réel est intégré au southbridge, le circuit responsable de contrôler les entrées/sorties les plus lentes.

L'HTR conserve la date complète (jour, mois, année) et l'heure (heures, minutes, secondes) ainsi qu'un bit indiquant si l'ordinateur est à l'heure avancée de l'est. Puisque le temps doit s'écouler même si l'ordinateur est fermé, une batterie au litium alimente l'HTC lorsque la carte mère est hors tension. L'HTR contient un cristal oscillant qui vibre à une fréquence de 32768 Hz. Le cristal utilisé est moins dispendieux (donc de moins bonne qualité) que le cristal utilisé dans une montre au quartz! Ce qui implique que l'horloge d'un ordinateur peut ralentir ou accélérer d'une minute par mois surtout si l'ordinateur n'est jamais redémarré. C'est pourquoi, il faut synchroniser notre horloge avec une source extérieure comme une horloge atomique.

On peut lire et modifier la valeur de l'HTR grâce aux fonctions clock_gettime et clock_settime. Ces fonctions utilisent une structure contenant le nombre de secondes écoulées depuis le début du 1er janvier 1970 et le nombre de nanosecondes écoulés depuis le début de la dernière seconde. Ces nombres sont exprimés sur 32 bits. QNX offre aussi la fonction ClockTime pour gérer l'HTR mais cette dernière fonction ne respecte pas le standard POSIX. Cette fonction retournent le nombre de nanosecondes écoulées depuis le début du 1er janvier 1970. Ce nombre est exprimé sur 64 bits.

Question: Pouvez-vous prédire le moment où aura lieu le prochain bug de l'an 2000 si votre logiciel utilise: a) la fonction clock_gettime? b) la fonction ClockTime?

Le PIT

Au démarrage, le système d'exploitation lit la valeur de L'HTR puis l'inscrit en mémoire. Ensuite, il demande à un autre circuit, le Programmable Interrupt Timer (PIT), de générer des interruptions de haute priorité à intervalles réguliers. Le PIT, à l'origine implémenté par la puce Intel 8254 mais aujourd'hui aussi intégré au southbridge, contient un cristal oscillant à une fréquence de 1193180Hz. Lorsque le processeur recoit une interruption du PIT, il suspend la tâche en cours et demande au système d'exploitation de traiter l'interruption. Le système d'exploitation reconnait alors l'interruption du PIT, ajuste l'heure et la date en mémoire et en profite pour effectuer certaines tâches comme l'ordonnancement des threads. On programme l'intervalle des interruptions en inscrivant un nombre diviseur dans le registre 16 bits du PIT. Par exemple, si on inscrit la valeur 100, c'est une oscillation sur 100 qui sera transformée en interruption. L'intervalle des interruptions sera donc de 100 fois 1/1193180 donc de 83,8us. L'intervalle de temps entre deux interruptions est aussi appelé la granularité du système. Aucune mesure de temps (que ce soit les horloges, les chronomètres, les alarmes, les pauses) ne peut être plus précis que la granularité du système.

On peut lire et modifier la granularité du système grâce aux fonctions clock_getres et clock_setres. QNX offre aussi la fonction ClockPeriod pour lire et modifier la granularité du système mais cette dernière fonction ne respecte pas le standard POSIX.

Question: Peut-on programmer la granularité du système à exactement 1 ms? Sinon, qu'elle sera l'écart entre la valeur souhaitée et la valeur réelle?

Le compteur haute performance

On peut utiliser le compteur haute performance pour mesurer des intervalles de temps plus petits que la granularité du système. Un compteur de 64 bits est intégré à même les processeurs afin de compter le nombre de cycles exécutés. Par exemple, un processeur de 100MHz incrémentera le compteur haute performance à tous les 10 ns (1/100MHz). On peut lire la valeur du compteur haute performance grâce à la fonction ClockCycles. Pour calculer un temps, nous avons aussi besoin du nombre d'incréments par seconde grâce à SYSPAGE_ENTRY(qtime)->cycles_per_sec.

Des outils pratiques

Le chronomètre

Parfois, il peut être utile de mesurer le temps écoulé entre deux évènements. Pour ce faire il suffit de lire la valeur de l'HTR à l'aide de clock_gettime et de sauvegarder le résultat dans une variable TempDebut lors du premier évènement et de relire la valeur de l'HTR lors du second évènement et de sauvegarder le résultat dans la variable TempFin. Il suffira alors de soustraire TempsDebut de TempsFin pour connaitre le temps écoulé.

Pour être plus précis, on peut aussi utiliser le compteur haute performance. Pour ce faire il faut sauvegarder la valeur du compteur dans une variable lors du premier évènement et lors du deuxième évènement puis calculer (CompteurFin-CompteurDebut)/NbCyclesParSeconde.

L'alarme

Une alarme permet de réveiller une thread à intervalles réguliers. Une alarme permet donc de réguler un exécutif cyclique sans créer d'attente active. La création d'alarme est un service du système temps réel qui utilie les interruptions du PIT. Pour créer une alarme il faut effectuer les étapes suivantes:

  1. Créer un timer avec la fonction timer_create. On doit fournir à cette fonction une structure sigevent correction remplie. On doit écrire dans cette structure la façon dont le processus doit être réveillé (soit à l'aide d'un pulse ou d'un signal).
  2. Invoquer la fonction timer_settime pour démarrer l'alarme. On doit fournir à cette fonction une structure itimerspec correction remplie. La partie it_value de la structure spécifie l'intervalle de la première alarme. Si cette partie est initialisée à 0, l'alarme est désarmée. La partie it_interval de la structure spécifie l'intervalle de répétition de l'alarme. Si cette partie est initialisée à 0, il n'y aura pas de répétition.
  3. Appeler la fonction MsgReceive ou MsgReceivePulse pour placer la thread en état de sommeil jusqu'à la réception d'un pulse.
  4. Effectuer un tâche (par exemple lire une entrée et agir en conséquence) et retourner au numéro 3.