Obligement - L'Amiga au maximum

Dimanche 20 août 2017 - 21:04  

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 - création d'une bibliothèque - 1ère partie
(Article écrit par Frédéric Delacroix et extrait d'Amiga News - janvier 1995)


Ce mois-ci, nous abordons un sujet relativement ardu, à savoir la création de bibliothèques partagées par le biais d'un fichier à ajouter dans le tiroir Libs:. Pour ne pas me lancer dans un article de cinquante pages, je supposerai de votre part une assez bonne connaissance de l'organisation principale du système.

Généralités

Lorsqu'un programme ouvre une bibliothèque, le système (plus précisément Exec) commence par chercher dans la liste des bibliothèques en RAM, dont la tête (une structure ListHeader) se situe dans la bibliothèque ExecBase. Si aucune ne convient, c'est soit parce que ce n'est pas le même nom, soit parce que la version est insuffisante, soit parce que l'initialisation a échoué pour une raison ou pour une autre, la fonction OpenLibrary() retourne 0. Il est possible pour les applications d'ajouter des bibliothèques au système, notamment par le biais des fonctions MakeLibrary() et AddLibrary(), mais cela nécessite bien évidemment de lancer un programme.

Il existe une autre méthode, mais elle n'est valable qu'une fois que le système est complètement lancé, et plus précisément après que le module Ramlib ait rectifié les fonctions d'Exec pour lui permettre d'accéder, grâce aux routines du DOS, aux fichiers dans les répertoires Libs: et Devs:. Il s'agit précisément d'y ajouter de nouveaux fichiers, afin qu'Exec, après avoir épuisé la liste des bibliothèques en RAM, puisse charger automatiquement, grâce à la fonction LoadSeg() du DOS, un fichier bibliothèque depuis le disque. Ces fichiers suivent donc une syntaxe très précise, que je vais maintenant vous décrire.

Structure d'un fichier .library

Tout ce que je vais raconter maintenant est valable aussi bien, moyennant quelques adaptations comme un NT_LIBRARY à changer en NT_DEVICE, pour les bibliothèques que pour les devices, que nous étudierons peut-être dans un prochain article.

La première chose que le module Ramlib fera après avoir chargé un fichier sera de chercher un module résident dans ce fichier. Ce module résident est identique à ceux utilisés par le système lors d'une réinitialisation, mais dans un contexte différent bien sûr. Il s'agit donc d'une structure, connue sous le nom original de Resident :

struct Resident {
       UWORD rt_MatchWord;
       struct Resident *rt_MatchTag;
       APTR rt_EndSkip;
       UBYTE rt_Flags;
       UBYTE rt_Version;
       UBYTE rt_Type;
       BYTE rt_Pri;
       char  *rt_Name;
       char  *rt_IdString;
       APTR  rt_Init;
 }; 

Le champ rt_MatchWord doit contenir le mot $4AFC (correspondant à l'instruction ILLEGAL), le champ rt_MatchTag doit contenir un pointeur sur la structure Resident elle-même. Ce sont ces deux champs qui sont examinés pour la recherche d'une structure Resident. Le champ rt_EndSkip, bien qu'il ne soit pas réellement utilisé dans notre cas, doit pointer sur la fin du premier hunk du fichier. Le champ rt_Flags contient un masque de quelques drapeaux, dont le seul qui ait réellement une signification ici est RTF_AUTOINIT. J'y reviendrai.

rt_Version contient bien sûr le numéro de version du module, rt_Type doit être mis à NT_LIBRARY dans notre cas. rt_Pri n'a pas de signification ici. Le champ rt_Name contient un pointeur sur le nom de la bibliothèque, très important puisqu'il sera comparé à celui qui avait été demandé lors d'OpenLibrary(), et rt_IdString contient une chaîne de description au format : <nom> Version.Revision (Date).

Je fais ici un petit détour sur une contrainte souvent méconnue : les noms de bibliothèques doivent toujours être en minuscules (ex : intuition.library au lieu de Intuition.library). En effet, le DOS ne fait pas la différence pour les noms de fichiers, mais Exec, lorsqu'il inspecte la liste des bibliothèques, si (cette règle est valable aussi pour les devices, polices de caractères...).

Enfin, le champ le plus important, sans doute, est rt_Init. La valeur qu'il contient est un pointeur, qui sera interprété comme l'adresse d'une fonction à appeler si rt_Flags est à 0, ou comme l'adresse d'une table d'initialisation si le drapeau RTF_AUTOINIT est à 1. Dans le cas où ce drapeau n'est pas mis, la fonction d'initialisation doit se charger de tout : allocation et initialisation d'une structure bibliothèque, construction d'une table de saut ; c'est pourquoi je laisserai ce cas de côté. Le format de la table d'initialisation est donc le suivant (vous remarquerez qu'il s'agit simplement de l'énumération des paramètres pris par la fonction MakeLibrary(), qui sera appelée automatiquement) :
  • Un mot long contenant la taille de la partie positive de la structure de bibliothèque à réserver. Cette taille n'inclut pas la zone de la table de saut située aux offsets négatifs, celle-ci sera calculée automatiquement. Il va de soi que cette taille doit au moins être égale à la taille de la structure Library, mais on peut bien sûr l'étendre pour y ajouter des données personnelles.

  • Un pointeur sur une table de pointeurs sur des fonctions. C'est cette table qui sera utilisée pour construire la table de saut (grâce à une série de JMP). Petite particularité : il est possible de spécifier l'utilisation de pointeurs relatifs, sur 16 bits. Pour cela, le premier mot (de 16 bits donc) doit être -1, et les mots suivants les déplacements à effectuer pour pointer sur les fonctions par rapport à l'adresse de la table. On remarque que c'est là une belle économie car, outre le fait qu'un pointeur ne prend qu'un mot au lieu d'un mot long, cela dispense aussi de références, dans le fichier exécutable, à des adresses absolues, donc le DOS n'a pas besoin de stocker d'informations de relogement (dans les fameux hunks Reloc32), d'où une économie supplémentaire d'un mot long par fonction. Il y a toutefois deux inconvénients : avec des pointeurs relatifs, il est impossible de "traverser" les sections car on ne connaît l'adresse de chaque section qu'au chargement et non à l'assemblage, d'où impossible de prédire un offset fixe. Le second inconvénient est qu'il est impossible de charger des bibliothèques dans l'espace mémoire situé dans les 64 derniers kilooctets (là où les adresses commencent par $FFFF), puisque dans ce cas les pointeurs absolus seraient interprétés comme des déplacements relatifs. C'est là un point très mineur (qui possède 4 Go de mémoire ?)...

  • Une table d'initialisation, qui sera utilisée pour initialiser la structure nouvellement créée grâce à la fonction InitStruct(). Bien que cette fonction soit très compacte et très performante, on préfèrera le plus souvent utiliser les macros du fichier exec/initializers.i pour des questions de lisibilité. Cette table est utilisée pour initialiser les champs ln_type, ln_Name, lib_IdString, lib_Flags, lib_Version et lib_Revision, plus éventuellement d'autres champs personnels. Le champ lib_Flags aura obligatoirement le drapeau LIBF_CHANGED, probablement avec LIBF_SUMUSED, pour que la fonction SumLibrary() corrige la somme de contrôle sans déclencher d'alerte.

  • Un pointeur sur une routine qui sera appelée après que la structure ait été créée et initialisée, ainsi que la table de saut. Cette fonction aura quelques paramètres importants dans ses registres :
    • A0: pointeur sur la SegList de la bibliothèque. Cette valeur est à conserver précieusement, car elle servira à libérer la mémoire en cas de purge.
    • D0: base de la bibliothèque nouvellement créée.
    Le rôle de cette fonction est de se charger de toutes les initialisations supplémentaires requises : ouverture d'autres bibliothèques, allocations de mémoire, initialisation de listes et de sémaphores... Attention, il s'agit des données communes à toutes les tâches. Si des ressources doivent être allouées individuellement pour chaque tâche, ce sera fait dans la fonction d'ouverture !
En retour, la fonction doit fournir en D0 l'adresse de base de la bibliothèque si tout a bien marché, ou 0 si une initialisation a échoué. Dans ce cas, c'est à la fonction d'initialisation de libérer toutes les ressources qu'elle a elle-même allouées, ainsi que celles qui ont été allouées automatiquement par Execanbsp;: il s'agit de libérer la zone mémoire pointée par (Base - lib_NegSize) et de longueur (lib_NegSize + lib_PosSize).

Les fonctions

Comme vous le savez sans doute, les quatre premières fonctions aux offsets négatifs des bibliothèques sont réservées. Elles se nomment respectivement : LIB_OPEN(), LIB_CLOSE(), LIB_EXPUNGE() et LIB_RESERVED().

LIB_OPEN() est appelée lorsque la bibliothèque est ouverte, elle ne doit pas être confondue avec la fonction d'initialisation qui est appelée lorsque la bibliothèque est chargée. Son rôle est d'incrémenter le compteur de clients, c'est-à-dire le champ lib_OpenCnt, et d'effacer le drapeau LIBF_DELEXP (DELayed EXPunge), pour éviter que la bibliothèque ne soit purgée. La bibliothèque peut aussi allouer des ressources pour la tâche qui ne sont pas globales comme celles allouées par la fonction d'initialisation. LIB_OPEN() reçoit en paramètres la base de la bibliothèque en A6 et le numéro de version en D0, qui a déjà été comparé avec le champ lib_Version. En retour, elle doit fournir la base de la bibliothèque en D0 si tout s'est bien passé, ou 0 dans le cas contraire, auquel cas la fonction OpenLibrary() échouera.

LIB_CLOSE() est le contraire : elle décrémente le compteur de clients et libère les ressources allouées à une tâche particulière. Elle doit également tester le drapeau LIBF_DELEXP, et, s'il est à 1, appeler LIB_EXPUNGE(). Elle doit retourner 0 dans le cas contraire.

LIB_EXPUNGE() est la plus compliquée de ces fonctions : il s'agit de l'opposé de la fonction d'initialisation qui avait été appelée par MakeLibrary(), son rôle est de libérer la mémoire de la bibliothèque. La première chose à faire est d'examiner le compteur de clients, et de mettre le drapeau LIBF_DELEXP à 1, de façon à ce que la purge soit appelée par LIB_CLOSE() dès que le compteur de clients est à 0.

S'il est à 0, alors il faut libérer toutes les ressources que la fonction d'initialisation avait allouées, retirer la structure Library de la liste du système, ce qui se fait par la fonction Remove() d'Exec, libérer la mémoire occupée par la structure Library et la table de saut suivant la formule citée plus haut, et enfin retourner en D0 le pointeur sur la SegList qui avait été passé en paramètres à l'initialisation.

LIB_RESERVED() est inutilisée pour l'instant ; elle doit se contenter de mettre D0 à 0.

La purge

Lorsque le système manque de mémoire lors d'une allocation, le processus de purge intervient : il s'agit de libérer toute la mémoire occupée par les bibliothèques, devices, polices de caractères, inutilisés. Depuis le Kickstart 3.0, les applications peuvent aussi déclarer auprès d'Exec des "Low-Mem handlers" permettant des opérations similaires pour de la mémoire allouée pour des applications. En ce qui nous concerne, c'est le module Ramlib qui se charge d'appeler la fonction de purge de toutes les bibliothèques dont le compteur de clients (lib_OpenCnt) est à 0. C'est pourquoi d'ailleurs certaines bibliothèques comme asl.library v38 gardent ce compteur à 0, pour pouvoir libérer de la mémoire annexe.

Conclusion

Je vous laisse méditer ces explications techniques, le mois prochain nous attaquerons un exemple pratique. Dernière chose tout de même : n'oubliez pas que le code doit être réentrant, c'est-à-dire exécutable par plusieurs tâches à la fois, les variables globales non protégées par des sémaphores sont exclues ! De plus, les fonctions d'initialisation et de purge ne doivent en aucun cas casser l'état Forbid(), y compris par un appel au DOS !

Ceux qui souhaitent des informations complémentaires peuvent se procurer les RKM, les Fish 741 et 742, et, éventuellement, les derniers numéros du magazine défunt Amiga News Tech. Sur ce, je vous souhaite une bonne et heureuse année, au mois prochain !


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