Suivez-nous sur X

|
|
|
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
|
|
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
|
|
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
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : Assembleur - création d'une bibliothèque - 1re 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 segment 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 périphériques logiques, 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 décalages 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 segments 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 décalage 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 décalages 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, périphériques logiques,
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 !
|