Obligement - L'Amiga au maximum

Samedi 18 novembre 2017 - 05: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 : Assembleur - exemple de programme avec Exec (CheckMem 2.0)
(Article écrit par Gilles Soulet et extrait d'Amiga News - juin 1993)


Ce mois-ci, comme promis dans le précédent article, nous allons encore avancer dans la découverte d'Exec et d'Intuition, avec une nouvelle version de notre utilitaire de "surveillance" des vecteurs du système, le bien nommé CheckMem.

Une mise à jour indispensable

CheckMem a beau être un petit outil discret, qui ne se manifeste que très rarement, il n'est pas question d'en rester à la version 1.0 du point de vue de "l'interface" (ou de son absence !). Afficher un dégradé de couleurs, c'est peut-être joli, mais l'Amiga ne peut plus se contenter d'un tel effet, et il est grand temps de commencer à communiquer réellement avec l'utilisateur.

La bibliothèque concernée est bien évidemment Intuition, et elle propose un nombre incroyable de fonctions pour gérer le dialogue entre l'homme et sa machine. Cette bibliothèque, écrite en C et assembleur, représente une grosse partie du système d'exploitation de l'Amiga. Nous allons découvrir Intuition petit à petit, car la route est longue et semée d'embûches. Nous allons aussi en profiter pour approfondir nos connaissances sur Exec, en particulier sur les tâches et les signaux.

Avant de s'attaquer aux gros morceaux de cette bibliothèque, nous allons voir aujoud'hui une fonction très pratique et très simple à utiliser : la fameuse fonction DisplayAlert() que vous connaissez bien puisque c'est elle qui affiche l'infâme gourou ! Cette fonction est utilisée pour avertir l'utilisateur qu'il se passe quelque chose de grave dans le système, et qu'il faut prendre des mesures d'urgence. Lorsqu'elle est invoquée, l'Amiga interrompt le cours normal de son fonctionnement et affiche un texte rouge encadré d'un rectangle clignotant du plus bel effet. Les interruptions ne sont pas inhibées, et la commutation de tâches n'est pas stoppée, mais l'utilisateur est prié de cliquer sur un bouton de la souris pour sortir de cette situation.

Il existe deux types d'alertes : l'alerte récupérable (recoverable alert) qui se signale par le texte en orange, et dont on peut sortir en cliquant sur un bouton souris, et l'alerte fatale (dead_end alert), dont la couleur rouge indique une future mise à mort du système. Dans ce cas, cliquer sur un bouton ne doit conduire qu'à redémarrer complètement l'Amiga !

La fonction DisplayAlert() est utilisée en cas de grave erreur système, comme par exemple, si la MemList n'est pas correcte. Dans ce cas, Exec interdit la commutation de tâches, mais pas les interruptions. La fonction DisplayAlert() prend dans le registre d0 le type de l'alerte que l'on veut envoyer. "0" correspond à une alerte récupérable, tandis que "1" correspond à une alerte fatale. Le registre d1 contient la hauteur (en pixels) du rectangle clignotant que l'on veut afficher, et a0 pointe sur une zone mémoire contenant les informations sur le texte encadré par le rectangle.

On trouve tout d'abord dans cette zone un WORD (non aligné) donnant l'abscisse de la première ligne de caractères, puis un OCTET donnant l'ordonnée, et ensuite la première ligne de texte, terminée par 0. Ensuite, un octet de "continuation" indique, s'il est à 1, que le texte contient une autre ligne, et s'il est à 0, qu'il n'y a plus de lignes. S'il y a une autre ligne, on trouve alors à nouveau un WORD d'abscisse puis un OCTET d'ordonnée, puis une chaîne, puis à nouveau un octet de continuation, etc. On peut ainsi afficher un texte de plusieurs lignes sur tout l'écran si on veut.

Si l'alerte est récupérable, la fonction DisplayAlert() renvoie (dans le registre d0) 0 si l'utilisateur a cliqué sur le bouton droit, et 1 s'il a cliqué sur le bouton gauche. Si l'alerte est fatale, DisplayAlert() renvoie toujours O. Cette fonction permet donc de consulter rapidement et simplement l'utilisateur. Elle est bien adaptée à notre cas : nous voulons savoir si, un vecteur ayant été modifié, il faut impérativement le corriger, ou plutôt prendre en compte cette modification. DisplayAlert() est faite pour nous !

Oui, mais...

En effet, "oui mais", car c'est ici que les choses se gâtent. Nous n'allons pas pouvoir utiliser DisplayAlert() depuis notre interruption, car cette merveilleuse fonction ouvre un écran (noir). Donc elle alloue de la mémoire.

Et il est absolument interdit de procéder à une allocation mémoire pendant une interruption, car les routines de gestion des MemLists n'inhibent pas les interruptions. Donc si on alloue de la mémoire dans une interruption, on s'expose à ce que ceci ce fasse pendant qu'une tâche était justement en train d'allouer ! Ceci provoquerait immédiatement de gros problèmes dans la gestion de la MemList...

C'est pour cela que je vous disais qu'il fallait trouver un moyen de "sortir" de l'interruption. Le seul moyen possible est de créer une nouvelle tâche qui va se charger de prévenir l'utilisateur. Mais quand cette nouvelle tâche doit-elle être créée ? Pendant l'interruption, au moment où l'on trouve un vecteur modifié ? Non ! Il est également interdit de créer une nouvelle tâche sous interruption (décidément !) car après l'appel à la fonction AddTask(), Exec procède à un ReSchedule() qui bidouille lui aussi avec les interruptions ! Ne cherchez pas d'informations sur les fonctions Schedule() et ReSchedule() : Commodore n'en a presque pas donné ! Disons qu'elles servent à gérer la répartition du temps machine entre les différentes tâches.

Par contre, on peut très bien créer une tâche au départ, qui va s'endormir aussitôt, puis réveiller ensuite cette tâche avec un signal. Ce signal sera envoyé depuis l'interruption lorsqu'un vecteur est modifié, et la tâche réveillée se chargera d'appeler DisplayAlert(). Voilà la solution !

La création d'une tâche

Notre petit programme CheckMem va donc procéder dès le départ à la création d'une tâche destinée à avertir l'utilisateur, sauf si elle s'y trouve déjà (rappelons que ce programme fonctionne comme un interrupteur On/Off). Cette petite tâche agit comme un "démon". Ce n'est pas un processus, mais une simple tâche (Task) qui reste endormie, invisible. Elle ne prend pas de temps machine, et n'est réveillée qu'en cas de nécessité.

La création se fait au moyen de la fonction AddTask(). Cette fonction prend trois paramètres en entrée : A2 pointe sur une zone qui contient le code machine à exécuter dès le départ (Initial PC). A3 pointe sur le code à exécuter lorsque la tâche rencontrera le dernier RTS. Si A3 est nul, le système place au sommet de la pile de la tâche l'adresse standard de fin de programme, qui libère la mémoire allouée par AllocEntry() et élimine la tâche par un RemTask(). Enfin, on place dans A1 l'adresse d'une nouvelle structure "Task" correctement initialisée. Voici le détail de cette structure :

struct Task {
struct Node tc_Node;
UBYTE tc Flags;            drapeaux (utilisés par le système)
UBYTE tc_State;            état (waiting, running, ready, etc.)
BYTE tc_iDNestCnt;         compteur pour disable()
BYTE tc_TDNestCnt;         compteur pour forbid()
ULONGSigAlloc;             signaux alloués
ULONG tc_SigWait;          signaux attendus
ULONG tc_SigRervd;         signaux reçus
ULONG tc_SigExcept;        signaix provoquant une exception
UWORD tc_TrapAlloc;        traps alloués (1 bit par trap)
UWORD tc_TrapAble;         traps autorisés (16 traps possibles)
APTR tc_ExceptData;        -> données pour exception
APTR tc_ExceptCode;        -> code pour exception
APTR te TrapData;          -> données pour trap
APTR tc_TrapCode;          -> code pour trap
APTR tc_SPReg;             pointeur de pile task 
APTR tc_SPLower;           -> limite basse de la pile task
APTR tc_SPUpper;           -> limite haute
VOID (*tc_Switch)();       -> routine invoquée avant perte processeur
VOID (*tcLaunch)();        -> routine invoquée avant gain processeur
struct List tc_MemEntry;   liste de zones allouées avec AllocEntry()
APTR tc_UserData;          pointeur disponible pour le programmeur

La signification des différents champs est relativement claire, mais certains d'entre eux doivent être explicités. Ainsi, tc_ExceptData et tc_ExceptCode sont utilisés en cas d'exception, au sens Exec (et non au sens de l'exception du 68000). Une exception Exec est provoquée par un signal (soft) qui déroute la tâche vers un traitement particulier, comme par exemple quand on tape "control C" pour arrêter un programme. tc_TrapData et tc_TrapCode sont utilisés en cas d'exception 68000, c'est-à-dire une erreur logicielle (division par zéro, violation de privilège, etc.) ou un appel à l'instruction TRAP du 68000.

Les champs tc_IDNestCnt et tc_TDNestCnt servent respectivement à compter le nombre d'appels aux fonctions disable()/enable() et forbid()/permit(). Une dernière précision sur les champs tc_Switch et tc_Launch : ils pointent chacun vers une région mémoire qui contient une routine devant être exécutée lorsque la tâche perd ou reprend le contrôle du 68000 lors de la commutation de tâche (task-switching).

Peu de champs doivent être initialisés au départ : tc_SPUpper doit pointer vers la limite supérieure de la pile de la tache, tc_SPLower pointe sur la limite inférieure de cette pile, et tc_SPReg est initialisé avec l'adresse de départ du pointeur de pile task, qui se trouve bien évidemment entre les deux limites précédentes ! Il faut donc avoir préalablement alloué de la mémoire pour la pile task. Une valeur de 1000 octets est souvent suffisante. Le CLI exécute toutes ses commandes avec une pile standard de 4000 octets.

Les signaux

Après avoir créé notre nouvelle tâche et ajouté l'interruption qui espionne certains vecteurs, notre programme se termine et quitte le système. La nouvelle tâche créée par AddTask() s'exécute immédiatement après sa création. Elle possède sa propre pile et sa propre zone de code, qui ont toutes deux été recopiées par le programme principal dans une zone mémoire allouée. Cette nouvelle tâche ne doit pas consommer de temps machine : elle attend simplement d'être réveillée par l'interruption si un vecteur est modifié. Elle sera réveillée à la réception d'un signal envoyé depuis l'interruption.

Exec gère un système de signaux pour les tâches : chacune peut se réserver jusqu'à 32 signaux différents, et attendre un ou plusieurs signaux à la fois. On utilisera la fonction AllocSignal() qui permet soit d'allouer un signal bien spécifié, dont le numéro (de 0 à 31) est transmis dans d0, ou bien le premier signal disponible sur le masque, lorsqu'on met "-1" dans d0.

Nous laisserons à Exec le choix du signal que l'on va utiliser, en mettant "-1" dans d0. Ceci nous garantit au moins l'obtention d'un signal, c'est pourquoi nous ne testerons pas si AllocSignal() a planté. Le résultat renvoyé est le numéro du signal alloué, que nous transformerons en masque binaire qui sera stocké dans la case Signal Alloue, visible depuis l'interruption.

Ensuite, la tâche n'a plus qu'à s'endormir paisiblement en attendant d'être réveillée par l'interruption. Dès le réveil, elle s'assure qu'elle a bien été réveillée par l'interruption, en comparant le masque reçu avec celui attendu, puis elle déclenche la fonction DisplayAlert() pour avertir l'utilisateur. Les adresses des vecteurs modifiés et originels sont transmises dans les cases VectMod et VectOrg par l'interruption. Cette dernière case nous sert également de sémaphore : si elle est différente de zéro, c'est qu'on a déjà réveillé la tâche et qu'on vient de lui transmettre les vecteurs. Dans ce cas, on ne fait rien. Ceci permet de n'envoyer depuis l'interruption qu'un seul signal de réveil, et non 50 signaux par seconde, ce qui finirait par planter tout le système !

Petit exercice...

Je ne vous en dis pas plus pour aujourd'hui. Regardez attentivement le listing du programme : il est abondamment commenté et très facile à comprendre. Il comporte aussi une petite erreur de conception, assez facile à déceler, et qui pourra nous gêner sur les prochaines versions de CheckMem. Un numéro gratuit d'Amiga News du mois juillet sera offert au premier lecteur qui trouve l'erreur !

Listing

CheckMem
CheckMem
CheckMem
CheckMem
CheckMem
CheckMem
CheckMem
CheckMem


[Retour en haut] / [Retour aux articles]