Obligement - L'Amiga au maximum

Mardi 25 juillet 2017 - 22:40  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

 · Accueil
 · A Propos
 · Articles
 · Galeries
 · Glossaire
 · Hit Parade
 · Liens
 · Liste jeux Amiga
 · Quizz
 · Téléchargements
 · Trucs et astuces


Articles

 · Actualité (récente)
 · Actualité (archive)
 · Comparatifs
 · Dossiers
 · Entrevues
 · Matériel (tests)
 · Matériel (bidouilles)
 · Points de vue
 · En pratique
 · Programmation
 · Reportages
 · Tests de jeux
 · Tests de logiciels
 · Tests de compilations
 · Articles divers

 · Articles in english
 · Articles in other languages


Twitter

Suivez-nous sur Twitter




Liens

 · Sites de téléchargements
 · Associations
 · Pages Personnelles
 · Moteurs de recherche
 · Pages de liens
 · Constructeurs matériels
 · Matériel
 · Autres sites de matériel
 · Réparateurs
 · Revendeurs
 · Presse et médias
 · Programmation
 · Développeurs logiciels
 · Logiciels
 · Développeurs de jeux
 · Jeux
 · Autres sites de jeux
 · Scène démo
 · Divers
 · Informatique générale


Jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Partenaires

Annuaire Amiga

Amedia Computer

Relec

Hit Parade


Contact

David Brunet

Courriel

 


Programmation : C/C++ - gestion des ressources par "moniteur"
(Article écrit par Gilles Dridi et extrait d'Amiga News - juin 1997)


Le partage d'une fonction non réentrante

Après l'article d'introduction à Exec (noyau exécutif du système d'exploitation de l'Amiga), nous allons aborder des notions de gestion de ressources. D'une part, nous réutilisons la classe Exception que nous avons définie avec le langage C++ et qui regroupe les fonctions de gestion du signal d'exception. D'autre part, nous allons résoudre le partage d'une fonction de la bibliothèque C, que l'on considérera comme non réentrante : printf(). Nous allons essayer de la partager entre le programme principal : la fonction main() et la fonction routine() de la classe Exception.

Nous verrons les techniques de gestion de ressources : par masquage d'interruption, de moniteur, d'exclusion mutuelle et nous conclurons par le modèle client-serveur. Nous donnons, en plus, un exemple de conception logicielle orientée par les objets avec le langage C++.

Une étude

Avantage et inconvénient du signal d'exception

L'avantage évident du signal d'exception tel que nous l'exploitons est d'avoir un mécanisme d'interruption locale à la tâche : le cours normal du programme est interrompu, les registres de travail sauvegardés et la routine de la classe Exception s'exécutent en mode utilisateur et pile du programme. Nous sommes donc dans un contexte d'interruption mais locale à la tâche : la commutation de tâches (le multitâche) est autorisée. Le programme a pu être interrompu alors qu'il exécutait une fonction système ou autre. Nous ne devons pas réutiliser cette fonction dans la routine à moins qu'elle ne soit réentrante. Et pour cela, nous avons explicité la section qui n'est pas critique nous avons encadré la boucle de la classe Contrôleur, par la paire active()/desactive() afin de n'autoriser le signal d'exception que dans cette partie du programme.

Dans l'exemple de l'article d'introduction à Exec, nous fixons juste un booléen nommé "fin". Par conséquent, remarquez l'utilisation du mot-clé volatile qui permet en principe de stipuler que la variable ne sera pas dans un registre. Nous verrons plus tard le pourquoi. Comme nous pouvons redéfinir la fonction virtuelle routine() dans la classe Contrôleur, héritière de la classe Exception : que se passe-t-il si nous voulons en plus faire un affichage à partir de cette routine, sur la console avec la fonction printf() ?

Le Gourou ? (Guru)

Définition : en Inde, chef religieux d'un village.

Pratiquement, si on utilise la fonction printf() à la fois dans le main() et dans la routine() on obtient la méditation du Guru (voir définition ci-dessus). On pourrait tout de suite se dire qu'il est impossible que deux processus puissent faire des affichages simultanément à l'écran et donc, se partager la console.

Ceci est faux (voir exercice ci-dessous). Il montre deux processus AmigaDOS, c'est-à-dire deux tâches avec leurs ports de communication. L'un et l'autre ont ouvert le périphérique logiciel console ou "console.device" et dialoguent par requêtes (passage de messages) avec celui-ci. Nous venons de donner une solution générale au problème de gestion de ressource. Toutefois, comme elle est construite à partir des connaissances des mécanismes de communication d'Exec, nous allons "l'oublier" et essayer de résoudre le problème autrement.

Exercice : lancez dans le même Shell, les deux commandes suivantes (le même Shell, c'est par analogie aux affichages du programme que l'on souhaite réaliser) :

run list : all lformat
"<Esc>[7;39;39>0m%s%s<Esc>[0;39:39>0m" Ctrl-J list : all lformat "%s%s"

On voit apparaître un mélange fait de textes en mode vidéo inversé et normal, respectivement de la première et la seconde commande "list". Pour arrêter l'exécution, ouvrir un autre Shell et taper la commande "status". Vous aurez la liste des processus et pour chacun, le nom de la commande lancée et un numéro. Tapez alors "break numéro C", où "numéro" est celui de la ligne où le nom de la commande "list" apparaît.

Quelques explications sur les séquences de contrôle d'une console ANSI

Les significations des champs sont <CSI>style;couleur_caractère;couleur_cellule>couleur_fondm.

Le <ControlSequenceIntroducer> est de deux caractères : <Esc>[ pour 0x1B et 0x5B en valeurs hexadécimales, c'est le cas ici. Plus rapide à transmettre, le <CSI> peut être d'un caractère Ox9B.

Les plus connues de ces séquences de contrôle ont un caractère. Par exemple, en fin d'une chaine "C" comme paramètre d'un printf(), on utilise souvent le LINEFEED (\n), CARRIAGE RETURN (\r), HORIZONTAL TAB (\t). D'autres séquences pour contrôler une console au standard ANSI sont introduites par le <CSI> (voir RKM à console.device ou IFF Specification FTXT).

La séquence pour rétablir les valeurs par défaut, utilisée après "%s%s" dans la première commande "list", est introduite par <Esc>[ : <Esc>[0;39:39>0m. Mais pour "réinitialiser" la console, ce n'est pas un <CSI>, la séquence est juste introduite par <Esc> : <Esc>c.

Le RKM nous explique qu'une sortie texte à l'écran utilise la ressource système "blitter". Clairement, c'est un problème d'interblocage, d'après lui. Nous ne nous attarderons pas sur le terme de ressource "blitter" puisque cela pourrait faire le sujet d'un article entier. Juste que le "BLock Image Transfert"ER ou "Bitmap Lump Image Transfert"ER est vu par la graphics.library comme une ressource et que la bibliothèque graphique propose de le gérer soit par exclusion mutuelle soit par des files d'attente (FIFO).

Alors, sachant qu'il ne faut pas utiliser deux fois la même fonction, nous nous proposons d'utiliser la fonction printf() uniquement dans la routine. Même comme cela, nous n'avons pas un programme qui fonctionne correctement. Nous pensons que la fonction printf() provoque un appel à la fonction système Wait() (et/ou SetSignal()?) et que nous utilisons aussi cette fonction système dans le main(). Wait() est-elle réentrante ? Vis-à-vis des autres tâches, oui, puisque c'est une fonction d'Exec : système multitâche. On peut alors supposer que la fonction Wait() modifie une ou des variables du descripteur de tâche qui ne sont plus locales à la tâche mais partagées ou globales au regard des deux appels de Wait() ; par le Wait() du printf() dans la routine(), et par le Wait() dans le main(). Par conséquent, c'est l'utilisation, que nous en faisons, qui ne rend plus réentrante la fonction système Wait().

Nous considérerons, dans la suite de cet article, que la fonction printf() n'est pas réentrante.

Qu'est-ce qu'une fonction réentrante ?

Nous devrions utiliser les mots "code ou programme réentrant", puisque si l'on pense à réutiliser du code, une fonction sera par définition, réentrante. L'idée vient des sous-routines qui doivent permettre au programmeur de réutiliser du code.

Plaçons-nous d'abord dans un système monotâche. Une fonction peut être vue par ses entrées et ses sorties mais aussi par les "données" qu'elle modifie. Or, on comprend qu'avec un langage machine ou assembleur, on soit obligé de faire attention à ne pas écraser certains registres de travail, qui pourraient constituer une partie du niveau extérieur à l'appel de la sous-routine (ou fonction). Lorsqu'on utilise un langage structuré, le compilateur devra se charger de sauvegarder ces registres et donc de préserver l'environnement avant l'appel de la fonction, et de restaurer celui-là après. On peut donc dire que toute fonction écrite avec un compilateur est réentrante une fois, plusieurs fois mais dans un système monotâche.

Plaçons-nous maintenant dans un système multitâche. Étant donné que nous n'empêchons pas notre fonction d'accéder et de modifier des variables globales (Cf. les "données" qu'elle modifie), il se peut que notre code soit interrompu (les registres de travail sont, tout de même, sauvegardés par le commutateur) et qu'un autre appel à notre fonction ait lieu, celle-ci peut écraser ces variables, dites alors globales, et la fonction n'est plus réentrante. Pour palier à ce problème, nous pouvons définir des fonctions auxquelles "on ajoute" un ou plusieurs paramètres supplémentaires qui seront des variables locales, non plus globales. Cela signifie qu'il faudra réserver un environnement et cantonner la fonction dans celui-ci.

Avec un langage structuré, on peut préalablement à l'appel de la fonction, réserver et passer comme paramètre l'adresse de cet espace mémoire (tas ou pile) contenant la ou les variables (c'est toutes les structures à allouer puis à passer comme paramètres aux fonctions des bibliothèques de l'Amiga).

Avec un système ou langage fonctionnel, nous sommes "contraints", par le style de programmation, à utiliser plutôt des variables locales. Alors, on dira que si on définit une fonction avec un langage ou système de la programmation fonctionnelle, utilisé comme tel, elle est réentrante y compris dans un système multitâche.

Attention, nous avons la possibilité qu'un langage puisse permettre à une fonction de s'appeler elle-même. Ceci est à différencier de la notion de réentrance même si on peut dire que cette fonction est "réentrante", elle l'est de son propre point de vue et pas de l'extérieur : elle s'appelle elle-même.

Ainsi, comme nous venons de le voir, la fonction sera réentrante dans un système monotâche, mais cela n'implique pas qu'elle le soit dans un système multitâche. Enfin, n'oublions pas que nous avions défini des "sous-routines", pas uniquement pour réduire la taille du code mais surtout pour réutiliser du code (Cf. début du paragraphe).

Attention, nous avons parlé du codage uniquement, pas de spécifications (invariants, pré/post-conditions) et encore moins d'analyse (fonctionnelle, objet, événementielle).

Conclusion de l'étude

Nous pouvons dire que nous avons deux fonctions, représentées par main() et Exception::routine() qui font appel à une même fonction printf() que l'on a considérée comme non réentrante. En particulier, remarquons que la routine ne peut être interrompue ; elle fait partie de la même tâche, du même descripteur de tâche. De plus, un autre signal d'exception ne peut pas non plus l'interrompre, ceci, grâce à la conception du signal d'exception d'Exec.

Une conception

Partie commune aux solutions des programmes exa.cp, exb.cp et exbb.cp.

Rappel : le passage des paramètres en C se fait par la pile.

Gestion des ressources
Gestion des ressources

Rappelons-nous que la classe exception a défini externe asm-Routine() et chgLed().

Ce qui change dans le code ci-dessus c'est Exception_routineVerif() en Exception2_routineVerif(). Maintenant, nous allons réutiliser la classe Exception en la plaçant dans un fichier d'inclusion. Nous nous apercevons d'un défaut, qui empêche la réutilisabilité : la variable "n", précédemment dans une section privée.

Gestion des ressources
Gestion des ressources

Voici maintenant la classe Exception2 qui traite deux signaux d'exception.

Gestion des ressources

Nous devons absolument notifier à l'utilisateur qu'il ne peut pas utiliser les deux classes en même temps. Soit il utilisera exception.h et asmEx.a (voir asmEx2.a), soit il utilisera exception2.h et asmEx2.a. En effet, l'éditeur de liens lui signalera un doublon de asmRoutine() et il ne saura pas si c'est Exception_routineVerif() ou Exception2_routineVerif() qui est appelée lors d'une exception. Voir le XREF du code assembleur.

Attention, pour réduire le texte, nous avons défini la fin de chacun des programmes dans deux fichiers d'inclusion supplémentaires. Ils utilisent la classe Moniteur qui ne sera définie que dans chacun des programmes. Parties communes :

Gestion des ressources
Gestion des ressources

Erratum : ici nous utilisons non plus Ctrl_E pour arrêter la boucle mais Ctrl_C. L'implantation de la fonction printf() pour ce compilateur gère aussi le Ctrl_C pour permettre d'arrêter le programme lorsqu'il fait un affichage, disons que la fonction printf() teste avant d'afficher, le Ctrl_C par SetSignal(). Ainsi, le programme fait un "goto" et appelle la fonction exit() ; sans exécuter ce qui suit le printf(). L'appel automatique au destructeur de la classe Exception(2) n'est pas fait. Il aurait fallu pour cela l'enregistrer comme fonction à appeler avant de quitter par exit(), avec la fonction atexit() de la bibliothèque C. Nous n'avons pas choisi cette solution et nous désactivons plutôt la gestion du Ctrl_C de ce compilateur et rappelons que cela n'avait pas été fait pour sécuriser le programme d'exemple de l'article d'introduction à Exec. Si l'utilisateur tapait Ctrl_C avant d'arrêter la boucle par Ctrl_E, l'ancien vecteur d'exception n'était pas réinstallé et cela pouvait à terme provoquer un Guru.

Nous vous laissons avec une solution qui ne fonctionne pas, mais en vous disant qu'il faut entourer le printf() de afficheValeur() appelée par la fonction main(), par active()/desactive().

Gestion des ressources

(suite le mois prochain)


[Retour en haut] / [Retour aux articles] [Article suivant]