© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 14/09/2000 -
Dernière mise à jour : 28/09/2001
Remarques préliminaires :
Utilisation de la classe CTimevalBase : fonction système select() exo_01
Classe CTimevalBase Classe CTimespecBase
Fonctions sleep() et nanosleep() - Classes CTimeval et CTimespec
Fonction alarm() - Timer exo_03
exo_02 Première amélioration Seconde amélioration
Classe CItimervalBaseMesure de durées par un timer
Classes CTimer...
exo_04
Temps réel, temps virtuel exo_05 Mode user, mode kernel exo_06
La première, de conception ancienne, est essentiellement utilisée par les timers Unix. Elle permet de manipuler des délais jusqu'à la micro-seconde (pourvu que la machine et la configuration le permettent). La dernière, d'introduction récente, permet de définir des délais de façon beaucoup plus fine et est conforme à la norme POSIX. Elle est en particulier utilisée par la fonction système nanosleep().
Malheureusement, aucune des deux n'offre de réelle sécurité dans son utilisation.
La documentation indique que la valeur de la donnée membre tv_usec d'un timeval ne doit pas excéder 999 999, que celle de la donnée membre d'un timespec ne doit pas excéder 999 999 999.
Mais rien ne permet de garantir le respect de cette contrainte, puisque les données membres sont publiques (c'est une struct), ce qui peut provoquer des erreurs.
Considérons par exemple la séquence suivante :
timeval tv1 = {3, 1500000}; // le mot
"struct" devant timeval serait nécessaire
// s'il existait une fonction timeval() timeval tv2 = {2, 2500000}; bool egal = timercmp (tv1, tv2, <); // timercmp[2] est une macro permettant de comparer 2 timeval |
Le booléen egal sera faux après exécution de ces instructions.
De plus, les opérations arithmétiques sur ces structures ne sont pas complètes.
Chargez-les dans vos propres répertoires include et util. Afin de pouvoir les utiliser correctement ultérieurement, passez un peu de temps à étudier leurs spécifications. L'analyse du code peut aussi vous apprendre des choses : voir Rq1 !
[xxx:xxx]
Le constructeur par recopie, et l'opérateur d'affectation, gracieusement offerts par le compilateur, sont utilisables car les données membres ne contiennent pas de pointeurs.
La fonction select() est appelée fonction de multiplexage d'entrées/sorties.
Rappelons que les "fichiers" (au sens large d'Unix) peuvent être classés en deux catégories selon que la fonction système read() renvoie 0 lorsqu'ils sont vides (c'est le cas des fichiers disques "normaux") ou qu'elle bloque (normalement) le processus qui l'appelle lorsque le "fichier" est vide (c'est le cas du "fichier" clavier, des sockets, des pipes).
Rappelons aussi que la fonction select() est bloquante jusqu'à ce qu'au moins un événement attendu arrive, ou que le délai fixé en dernier paramètre arrive à expiration.
exo_01
Mettre à jour les fichiers MakeUtil et INCLUDE_H.
Ecrire un programme qui répète 5 fois une boucle dans laquelle il attend soit une frappe au clavier, soit l'écoulement d'un délai de 5 secondes.
Si l'événement est une frappe au clavier, afficher sous la forme suivante (par exemple) :
f : caractere lu au bout d'un délai de [1:380000] |
Mettre à jour le fichier Makefile. Compiler et tester les deux possibilités.
Corrigés : exo_01.cxx - Makefile - nsSysteme.h - nsSysteme.hxx - MakeUtil - INCLUDE_H
exo_02
Dans la fonction ppal() du fichier exo_02.cxx, faire une boucle jusqu'à ce que le délai, passé en argument de la commande, soit épuisé. Pour cela, appeler la fonction sleep(), en récupérant le délai restant.
Pour permettre d'interrompre la fonction sleep() sans terminer le programme, dérouter le signal SIGINT avant d'entrer dans la boucle.
Afficher un message à chaque retour de la fonction sleep(), par exemple :
SIGINT reçu; il reste 5 secondes |
Compiler et tester.
La fonction système nanosleep() a un comportement très analogue à la fonction C. Cependant, elle permet une granularité du temps plus fine, et utilise pour cela la structure struct timespec.
Ecrire le wrapper Nanosleep() de la fonction nanosleep(), en remplaçant le type timespec par CTimespecBase. Contrairement à la fonction système nanosleep(), il est souhaitable que son wrapper remette à zéro le temps restant lorsque nanosleep() se termine normalement.
Ajouter à ppal() une nouvelle boucle utilisant Nanosleep(), jusqu'à ce que le délai, passé en argument de la commande [3], soit épuisé. Comme précédemment, afficher le délai restant à chaque retour de la fonction Nanosleep().
Compiler et tester.
Première améliorationIl serait souhaitable de disposer d'une constante de type CTimespecBase, qu'on appellerait par exemple CstZero, à laquelle n'importe quel délai pourrait être comparé. On devrait pouvoir écrire :
CTimespecBase Delai (5);
... if (CstZero == Delai) ... |
Une première solution, triviale, consiste à laisser le soin à l'utilisateur de la construire lui-même :
const CTimespecBase CstZero (0); // ou simplement
: const CTimespecBase CstZero;
... if (CstZero == Delai) ... |
En réalité, les constantes d'une classe doivent être proposées par la classe elle-même. Il suffit de les déclarer et les définir comme membres statiques de la classe.
En conception objet, une dérivation correspond à une spécialisation : on dérive la classe mammifère en une classe chat par exemple. Dans la pratique de la programmation objets, la dérivation est parfois simplement utilisée pour ajouter quelques commodités à une classe de base. Peu recommandable dans le cas général, car en particulier génératrice de hiérarchies de classes trop volumineuses, cette possibilité est cependant nécessaire, comme nous allons le montrer ici.
La classe CTimespecBase vous étant fournie, vous ne devez pas en modifier le code. Il ne vous reste plus qu'à la dériver en CTimespec, dans le fichier Time.h placé dans le répertoire include, afin de lui ajouter la déclaration de cette constante. On en profitera pour la doter de quelques possibilités supplémentaires. Dans le fichier Time.cxx correspondant, du répertoire util, ajouter la définition de CstZero (une définition de constante ne doit jamais se trouver dans un fichier inclus, pour éviter d'éventuelles définitions multiples).
Son utilisation peut être la suivante :
CTimespec Delai (5);
... if (CTimespec::CstZero == Delai) ... |
Lorsqu'une classe est dérivée, la nouvelle classe ne possède que les constructeurs par défaut et par recopie. Si sa classe mère ne les possède pas, il peut être nécessaire de les supprimer. Si sa classe mère possède d'autres constructeurs, il peut être nécessaire de donner les mêmes à la classe fille. C'est le cas ici : la classe CTimespec devra proposer deux constructeurs et un destructeur virtuel.
Effectuer la même modification pour la classe CTimevalBase, dérivée en CTimeval, à laquelle est ajoutée la constante CstZero.
Dans le fichier exo_02.cxx ajouter une nouvelle boucle en utilisant cette possibilité.
Mettre à jour les fichiers Makefile, INCLUDE_H et MakeUtil.
Compiler et tester.
Seconde améliorationEn C/C++, la valeur 0 est considérée comme faux, toute autre valeur est considérée comme vrai, qui est défini par true = !false. L'origine en est l'assembleur et les deux instructions de rutpure de séquence conditionnelle : JZ et JNZ, respectivement Jump if Zero et Jump if Not Zero, très utiles pour scruter un registre. Dans la pratique, les programmeurs ont l'habitude d'élargir la signification de false à invalide, opération qui a échoué, ou tout simplement nul ou vide et inversement pour true. Par exemple :
for (int n = argc; --n; ) ... // tant que compteur non nul
ou if (Read (fdSource ...)) ... // si lecture non vide ou for (; cin >> i; ) ... // tant que opération valide ou char pZone = 0;
|
Dans cet esprit, il est normal de doter de cette possibilité toute classe dont un représentant peut être considéré comme nul.
C'est le cas de CTimespec et
CTimeval.
Il faut donc pouvoir écrire par exemple :
CTimespec Delai (5);
... if (Delai) ... |
Le C++ offre pour cela une très élégante solution, qui consiste à définir dans ces classes l' opérateur de conversion en booléen.
Après avoir introduit ces modifications dans les deux classes, ajouter une nouvelle boucle en utilisant cette possibilité, puis compiler et tester.
Corrigés : exo_02.cxx - Makefile - nsSysteme.h - nsSysteme.hxx - Time.h - Time.hxx - Time.cxx - INCLUDE_H - MakeUtil
exo_03
Recopier le fichier exo_02.cxx dans exo_03.cxx. Ecrire la fonction ppal() qui :
Dans l'espace de noms anonyme, écrire la fonction Chrono(), de profil :
void Chrono (ostream & os, const string & Msg, int Periode, int NbreCycles); |
qui répète NbreCycles fois la boucle formée de la séquence :
Dans ppal(), ajouter la séquence qui :
Corrigé : exo_03.cxx
[[xxx:xxx]:[xxx:xxx]]
Cependant, les trois timers fonctionnent de la même façon. Il est donc élégant de fournir un "modèle" de timer fournissant le prototype (classe abstraite). On ne peut pas parler ici de classe d'interface (réviser le cours de Java) car certaines des fonctions de la classe abstraite peuvent déjà être implémentées.
Cette classe CTimerAbstr vous est fournie dans les fichiers CTimerAbstr.h et CTimerAbstr.hxx (à récupérer dans les chemins d'accès aux sources des corrigés).
Obtenue par dérivation protégée de la classe CItimervalBase, elle fournit :
De plus, aucune sorte de timer ne doit autoriser la duplication, tant par le constructeur par recopie que par l'opérateur d'affectation. Il suffit donc que la classe mère ne les autorise pas : les classes filles auront bien ces fonctions par défaut, mais toute tentative d'utilisation appellera les fonctions correspondantes de la classe mère, ce qui déclanchera une erreur de compilation.
Dans les fichiers CTimer.h, CTimer.hxx et CTimer.cxx (répertoires include et util), créer les classes CTimerVirtual, CTimerProf et CTimerReal dérivées de CTimerAbstr. Lever une exception CException de code CstTropTimers = 143 pour toute tentative de création d'un timer déjà existant.
Compléter les fichiers INCLUDE_H (macros CTIMERABSTR_H, CTIMERABSTR_HXX, CTIMER_H et CTIMER_HXX), MakeUtil (compilation de CTimer.cxx) et Makefile (ajout de CTimer.o).
Corrigés : CTimerAbstr.h - CTimerAbstr.hxx - CTimer.h - CTimer.hxx - CTimer.cxx - CstCodErr.h - INCLUDE_H - MakeUtil - Makefile
exo_04
void Delete (CTimerAbstr * p); |
Corrigé : exo_04.cxx
Temps réel, temps virtuel
Recopier le fichier exo_04.cxx dans exo_05.cxx. Ecrire la fonction ppal() qui :
Corrigé : exo_05.cxx
Mode user, mode kernel
L'objectif de cet exercice est de calculer le temps que passe un processus à effectuer les commutations de contexte et à exécuter des fonctions système. Pour cela, utiliser le timer PROF (temps cumulé écoulé en mode user et en mode kernel) et le timer VIRTUAL (temps écoulé en mode user seulement).exo_06
Recopier le fichier exo_05.cxx dans exo_06.cxx. Supprimer le namespace anonyme et son contenu, tout ce qui concerne les signaux et le traitement final de l'exception. Remplacer les timers dynamiques par des timers "automatiques". Remplacer le timer REAL par un timer PROF. Comme ci-dessus, initialiser les deux timers au moyen de la même constante CTimeval, de valeur importante (par ex. 100 secondes), l'intervalle étant fixé à 0.
Remplacer la boucle par :
Compiler et tester. Relancer plusieurs fois l'exécution et comparer les temps moyens des différentes opérations.
Remarques :
[1] Il règne sous Linux une grande confusion dans la localisation dans les fichiers inclus des identificateurs concernant le temps :
[2]
le fichier <sys/time.h> comporte deux macros permettant de convertir des timeval en timespec et inversement.
#define TIMEVAL_TO_TIMESPEC(tv, ts) {
\
(ts)->tv_sec = (tv)->tv_sec; \ (ts)->tv_nsec = (tv)->tv_usec * 1000; \ } #define TIMESPEC_TO_TIMEVAL(tv, ts) {
\
|
Il contient aussi des macros de manipulation de timeval : timercmp, timeradd, timersub, etc...
[3] Rappel : une chaîne de caractères (NTCTS) peut être transformée en entier grâce à aux fonctions C atoi() ou strtol()
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique