Obligement - L'Amiga au maximum

Vendredi 29 mars 2024 - 03:21  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

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

Articles in english


Réseaux sociaux

Suivez-nous sur X




Liste des 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,
ALL


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


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Programmation : Assembleur - introduction à Exec
(Article écrit par Gilles Dridi et extrait d'Amiga News - février 1997)


Je vous propose avec cet article, d'explorer les mécanismes du noyau de l'Amiga : Exec. Nous commencerons par une introduction du matériel, bien que nous ne traiterons que des aspects matériels nécessaires à la compréhension du noyau, et nous terminerons par une revue des modes de communication offerts par le noyau.

Les informations exposées ici sont tirées de livres sur le 68000, des ROM Kernel Manuals, de la Bible Amiga et surtout du document commenté des ROM 1.2 de Markus Wandel. J'ai utilisé ce dernier pour essayer de vous expliquer et vous donner une vue d'ensemble du noyau.

Comprendre le noyau

Le mécanisme d'interruption

On peut parfois et même souvent, se demander comment fonctionne le noyau ? C'est de ce vaste sujet, puisque nous sommes au coeur de l'Amiga, dont je vais traiter.

A l'origine est l'interruption. En effet, la majorité des microprocesseurs 8 bits tel que le 6502 ne disposent que d'une entrée d'interruption (une patte du microprocesseur) par laquelle l'environnement extérieur au microprocesseur peut l'interrompre. En fait, c'est le microprocesseur qui après chaque exécution du microprogramme d'une instruction (ce que doit faire l'instruction) teste un bit lui indiquant si une interruption est survenue. Si c'est le cas, le microprocesseur se déroute de l'exécution du programme en cours et saute à une routine de traitement de l'interruption. On se rend bien compte que sans ce mécanisme, nous serions obligés de tester en permanence un bit et donc d'insérer ce test régulièrement dans le séquencement du programme. Or ce mécanisme existe en interne dans le microprocesseur et il nous suffit de savoir à quel endroit (adresse) la routine d'interruption s'exécute pour y ficher la nôtre.

Après l'exécution de cette routine, le cours normal du programme interrompu peut être repris. Pour cela, le microprocesseur, avant de sauter à la routine d'interruption, sauvegarde l'endroit où il devra reprendre son exécution ou PC et son registre d'état ou SR (mais pas tous les registres de travail du microprocesseur). Enfin, le microprocesseur dispose d'une instruction spécifique que l'on utilise pour terminer la routine d'interruption qui restaure PC et SR.

Pour bloquer temporairement le mécanisme d'interruption, le microprocesseur dispose d'un bit supplémentaire utilisé pour "masquer l'interruption". L'opération logique "et" est effectuée entre ces deux bits et rangée dans le bit d'interruption testé.

Avec le 68000, le principe d'interruption reste vrai avec quelques différences. En effet, le 680x0 a sept niveaux d'interruption ou priorités. Si une interruption (à encoder sur les trois lignes d'entrée du 680x0) survient, puis une deuxième avec une priorité supérieure avant que la première se termine, la deuxième est traitée immédiatement suivie du retour à la première. A chaque interruption, un masque est mis automatiquement correspondant au niveau d'interruption pour bloquer temporairement le mécanisme. Elle empêche ainsi d'entrer dans une routine d'interruption de niveau inférieur. A cause de cela, plus l'interruption est prioritaire, plus sa routine doit être courte et s'exécuter rapidement par rapport aux routines des niveaux inférieurs, à l'instar des poupées russes.

Autres particularités du 68000 et que l'on doit connaître : d'une part, lors d'une interruption, le 68000 passe automatiquement, toujours et avant, tout en mode superviseur (un bit est affirmé dans le microprocesseur) ; l'environnement extérieur peut savoir que le microprocesseur est dans cet état par des pattes de sortie. Le microprocesseur démarre par un RESET (réinitialisation) qui est une interruption et sera d'abord en mode superviseur. L'instruction spécifique qui permet de terminer la routine d'interruption est RTE. Les autres instructions qui modifient le SR sont MOVE, ANDI, EORI to SR.

D'autre part, on dira que le mode utilisateur est celui du programme quand il exécute des instructions de l'utilisateur, sinon le mode "normal" du 68000 est superviseur !

Enfin, étant donnés les sept niveaux d'imbrication des routines d'interruption, on peut, suite à l'instruction de fin de routine d'interruption RTE, se trouver toujours en mode superviseur. On passera en mode utilisateur lorsque le premier niveau de priorité se terminera.

Dernière particularité, le 68000 sauvegarde et restaure (RTE) le PC et son SR par l'intermédiaire du registre A7 appelé pointeur de pile ou SP, qui est suivant le bit du mode Superviseur/Utilisateur SSP ou USP ; par conséquent A7 pourra pointer sur deux piles différentes. Conséquence : lors d'une interruption, le programme qui s'exécute est arrêté et la routine de traitement d'interruption est exécutée avec une pile différente de celle du programme en cours : la pile superviseur et non celle de l'utilisateur.

Quant à l'Amiga, il centralise ses interruptions (écran, série, disque, audio ou périphériques supplémentaires) à l'aide d'un circuit nommé Paula qui contient deux registres INTENAble et INTEREQuest. Ce circuit transmettra une demande d'interruption (Request) si elle est autorisée (ENAble) au 680xO, en les encodant sur ses trois entrées IPLX.

Remarque 1 : Exec utilise le masquage (déjà expliqué) au niveau du 68000 (MOVE SZXOQSR) uniquement pour différer des interruptions après des portions de codes internes (non directement liées aux appels systèmes offerts) et critiques. Exec utilise aussi Enable()/Disable() qui active ou désactive toutes les interruptions grâce au registre INTENA de Paula.

En final, l'Exec de l'Amiga gère des priorités d'interruption supplémentaires d'une manière logicielle. Les routines d'interruption "fichées" par Exec, vont en premier voir si les interruptions sont autorisées "chez Paula", puis tester les bits de INTEREQuest. Pour chacun des sept niveaux du 680xO, une routine d'interruption va grâce aux informations du registre INTEREQuest et du rang d'examen (le séquencement des instructions), définir des priorités logiques. Par exemple, la routine d'interruption pour le niveau 1 va tester les bits, O puis 1 puis 2 de INTEREQ, et lorsqu'un bit est affirmé sauter à une routine effective de traitement de l'interruption que l'on nommera "handler".

Notons qu'il faut un mécanisme matériel (ou logiciel ?) pour relancer et traiter la ou les deux interruptions restant lorsque deux ou trois interruptions, de même niveau 680x0, sont simultanément présentes chez Paula. Enfin, ce "handler" peut ne pas être la routine effective de traitement et s'appelle alors un serveur. Celui-ci définit de nouveau des priorités de traitement par le parcours d'une liste ! Il appelle l'une après l'autre, les routines-clientes de traitement pour un niveau logique d'interruption, l'adresse de chacune d'elles étant stockée dans un élément de cette liste. Chaque "client" se termine par une instruction RTS nous faisant revenir au code de l'itérateur de la liste du serveur.

Le mécanisme de la coroutine

Cette gestion d'interruption présente un premier cas, où plusieurs "programmes" peuvent coexister. De la même façon que le microprocesseur gère les interruptions et nous évite de tester régulièrement l'arrivée d'événements, on voudrait écrire, parce qu'on se place dans un contexte de multiprogrammation, des programmes qui coexistent et se partagent la puissance du microprocesseur, sans être des routines d'interruption.

Pourquoi ? D'une part, on a vu que le mécanisme des interruptions du 680x0 fonctionne à l'instar des poupées russes et que la plupart de nos programmes ne satisferont probablement pas ces contraintes.

D'autre part, comme on l'a vu, le mode superviseur est réservé à la gestion du système et nos programmes s'exécuteront "peinards" en mode utilisateur. Dans le mécanisme d'interruption, nous avons vu que seuls PC/SR sont empilés mais pas les registres de travail du 680x0. En effet, une interruption peut survenir entre deux instructions, par exemple entre CMP D0,D1 et BEQ label, le drapeau testé par l'instruction BEQ, en retour de la routine d'interruption est le "flag Z de SR" et ne doit pas être modifié. Pour cela, on sauvegarde aussi le registre d'état SR car il est fort probable qu'il soit modifié par les instructions de la routine d'interruption. La commutation de tâches ou "task-switching" correspond essentiellement à sauvegarder les registres de travail du microprocesseur puisque utilisés par le programme en cours puis restaurer ceux d'un autre programme à exécuter. C'est le rôle respectif, de la paire de fonctions Switch()/Dispatch() d'Exec (Commuter/Mettre à l'ordre du jour ou Expédie).

Aparté

Celles-ci peuvent être considérées comme des fonctions virtuelles, comme on l'a vu dans l'article du mois de novembre 1996 : "Les fonctions virtuelles avec C/C++", une table virtuelle (des fonctions déclarées virtuelles) est initialisée dans le constructeur et le principe en est une indirection. Ici, Exec construit d'abord une bibliothèque, l'équivalent de cette table. Selon le type du microprocesseur (tests des instructions disponibles) fixe les pointeurs de fonctions de la table avec les routines Switch(), Dispatch(), Add-Task() correspondant au type du microprocesseur. Le noyau utilise ensuite une fonction en appelant "son vecteur" dans la bibliothèque, comme pour les fonctions virtuelles de C/C++. Ci-contre, l'arbre d'héritage que l'on aurait pu construire à partir de classes C++ :

Exec (classe de base), Exec68000, Exec68020, Exec68020+FPU et Exec68020+FPU+MMU.

Arbre d'héritage

Pour une implantation évolutive qui nous amènera du mécanisme de coroutine à celui de micronoyau pour du pseudo-parallélisme, nous avons besoin d'une liste (ou table) des programmes à exécuter et d'un "pointeur sur le courant". Celui qui s'exécute sur le processeur. Chaque élément de cette liste sera appelé descripteur de tâche et devra référencer une pile utilisateur, celle du programme. On pourra au besoin sauvegarder/restaurer les registres de travail du microprocesseur sur cette pile. Le choix d'utiliser un espace commun qui fonctionne comme une pile a été fait pour le passage de paramètres et surtout l'appel de sous-programmes aussi bien en assembleur de la série 680x0, qu'en langages évolués comme Pascal ou C/C++.

Avec ce passage de témoin, nous avons, pour l'instant, un noyau qui fonctionne de façon coopérative ; il est nécessaire qu'un programme passe explicitement le processeur à un autre programme. Cette implantation est un peu différente du mécanisme de coroutine, dans lequel c'est un sous-programme que l'on connaît et que l'on appelle explicitement par "son vecteur" contenant une adresse de retour préalablement sauvegardée.

Nous pouvons lui ajouter un mécanisme d'interruption périodique pour que la répartition du temps soit équitable envers chaque programme ou tâche. Ce mécanisme se charge de la commutation du contexte et nous permet de ne plus faire explicitement la sauvegarde/restauration des registres du microprocesseur.

Toutefois, cette commutation n'est pas, en pratique, aussi triviale : il nous faudra ajouter, dans le descripteur de tâche, un pointeur sur l'indice courant dans la pile, là où sont stockés PC, SR, les registres du microprocesseur. Ainsi que d'autres amusements avec les informations des piles SSP et USP... Un tel noyau pourra fonctionner de façon préemptive.

Quant à Exec, il a choisi, nous verrons après pourquoi, de gérer en plus d'un pointeur sur la tâche courante (son descripteur) et d'une liste des tâches prêtes (à exécuter), une liste de tâches en attente. Nous avons donc deux listes et un pointeur.

Premièrement, on trouvera dans la liste des tâches en attente celles qui ont appelé la fonction Wait (événements) : c'est un appel explicite pour passer son tour lorsque les événements attendus ne sont pas déjà présents.

Deuxièmement, la liste des tâches prêtes est en fait une suite ordonnée par priorité. Nous la nommons suite et utiliserons le mot file plutôt pour une liste, sans notion de priorité, où l'on enfile à un bout et l'on défile à l'autre, par opposition à une pile. A la tâche élue, celle précédemment en tête de suite, sera affectée un laps de temps. Une fois ce laps de temps écoulé, Exec continuera à l'exécuter avec un nouveau laps de temps si c'est la seule disponible ou si elle est toujours la plus prioritaire. Exec est un noyau préemptif avec priorité statique. Certaines tâches seront prioritaires par rapport à d'autres, essentiellement celles du système où la priorité joue le rôle de régulateur, à l'instar d'un agent de police à un carrefour ou de véhicules prioritaires à un rond-point. Les tâches des utilisateurs seront au niveau de priorité 0. Étant donné que la priorité est statique (elle ne diminue pas au fur et à mesure que la tâche s'exécute), c'est toujours la tâche avec la priorité la plus élevée et prête à s'exécuter qui sera choisie par Exec, simplement en enlevant le descripteur en tête de la liste tâches prêtes.

Toutefois, la plupart des tâches sont en attentes d'événements et bloquées dans la fonction Wait (masque d'événements) après l'avoir appelée, si un des événements n'est pas déjà présent. On devrait dire des tâches de cette liste qu'elles sont endormies contrairement à celles de la suite des tâches prêtes, en file "d'attente" d'exécution.

Exercice I : comment définir la priorité d'une tâche ? Quelques faits :

I) seule une tâche qui n'effectue que du calcul ou "boucle", peut effondrer le système si nous augmentons assez sa priorité ; au-dessus de celles des tâches du système. II) Exec est à priorité statique, rien ne sert de donner une priorité élevée à une tâche pour qu'elle s'exécute de façon exclusive quand elle est souvent dans la liste des tâches en attente ; elle fait par exemple beaucoup de sorties à l'écran.

Dans ces cas, fixer la priorité du Shell à 1 par "ChangeTaskPri 1" où s'exécute le programme est une solution simple et effective pour accélérer l'affichage, et indirectement définir une priorité dynamique de traitement pour les commandes lancées dans ce Shell ; sa tâche va se trouver plus souvent élue que celle lancée dans des Shells en priorité 0.

Lancer la commande "dir all" dans deux Shells et sur le même lecteur, vous voyez alors que dans le Shell le plus prioritaire (ChangeTaskPri 1) les fichiers apparaissent plus vite. Vous vous apercevez que cette tâche passe parfois dans la liste des tâches en attente puisque, dans l'autre Shell, quelques fichiers apparaissent aussi.

Enfin, si un groupe de tâches prêtes à même priorité, celles-ci se répartissent équitablement le microprocesseur. Ceci est dû à la fonction Enqueue() qui insèrera une tâche prête dans la suite, après toutes celles qui ont la même priorité. Ainsi, Exec les élira tour à tour en choisissant une tâche différente en tête de suite. Pour des priorités identiques, la fonction Enqueue() s'applique bien a une file et correspond à enfiler "en queue" de liste, de file. On nommera l'opération défiler celle qui prend en tête de file.

Remarque 2 : seules les communications sont explicites, et pourront à nouveau être déléguées à une couche logicielle supplémentaire à Exec : AmigaDOS.

Conclusion

Le même principe de délégation du mécanisme d'interruption au microprocesseur s'applique pour la délégation de la partie sauvegarde/restauration du contexte au noyau. C'est Exec, le noyau préemptif du système d'exploitation qui s'en occupe.


[Retour en haut] / [Retour aux articles]


Soutenez le travail de l'auteur