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
|
|
|
|
Dossier : Gestion de la mémoire sur AmigaOS
(Article écrit par Philippe Vautrin et extrait d'Amiga News Tech - septembre 1991)
|
|
L'Amiga étant un système multitâche, la gestion de la mémoire par le système est autrement plus compliquée que
celle d'autres systèmes plus banals, comme l'IBM PC ou l'Atari ST. Il lui faut en effet notamment gérer les
problèmes de fragmentation qui découlent des multiples allocations successives par plusieurs tâches différentes.
Pour savoir à tout instant quelles sont les zones de mémoire utilisées par des applications et celles disponibles
ainsi que leur taille, Exec doit maintenir à jour une liste de pointeurs vers ces zones. Quand une application lui
demande de la mémoire, la plupart du temps, via AllocMem(), Exec parcourt ces listes à la recherche de la première
zone suffisamment grande pour la contenir. C'est d'ailleurs l'une des premières raisons de la fragmentation :
même si plus loin dans la liste, se trouvait une zone de mémoire libre d'exactement la même taille que la requête,
Exec allouerait tout de même la première zone suffisamment grande.
Une fois cette mémoire trouvée dans la liste, Exec l'en retire, jusqu'à ce qu'un appel à FreeMem() l'autorise à
l'y replacer. Naturellement, les applications doivent libérer toute la mémoire qu'elles allouent, et seulement
celle-ci. En fait, Exec ne rechignera pas à libérer une zone mémoire allouée par quelqu'un d'autre ; simplement,
lorsque son légitime propriétaire essaiera à son tour de la libérer, Exec se rendra compte que cette zone est
déjà de retour dans la "free list" et assumera que quelque chose ne tourne plus rond dans la machine...
Du coup, plutôt que de laisser des applications entrer en conflit, il provoquera un petit gourou (n°#80000009)
pour signifier aux petits rigolos concernés qu'on ne la lui fait pas...
Alternativement, Exec ne garde aucune trace de qui alloue quoi. Cela signifie que si une application "oublie"
(sans jeu de mots) de rendre sa mémoire, il n'y aura aucun moyen autre que la réinitialisation, de la récupérer.
Encore une liste
Comme beaucoup (trop ?) de choses dans l'Amiga, la mémoire est gérée à partir d'une liste, dont la base se trouve
dans la structure ExecBase. Son nom est MemList, à ne pas confondre avec la structure MemList, définie dans les
includes "exec/memory.h" et"execimemory.i". Les noms similaires pourraient facilement vous induire en erreur.
Rappel : vous obtenez un pointeur sur la structure ExecBase soit en ouvrant explicitement la bibliothèque exec.library,
soit en allant le chercher à l'adresse 4.
La liste de la mémoire libre est organisée comme une "liste de listes". Le champ MemList pointe en fait sur une liste
de régions de mémoire, une "région" n'étant finalement rien d'autre qu'une zone de mémoire libre dans laquelle Exec
peut aller piocher à sa guise. A l'initialisation du système, une région est d'abord créée pour la mémoire Chip
puis pour chaque extension de mémoire connectée (c'est d'ailleurs là l'utilité du programme MergeMem du répertoire
System : si deux extensions occupent des adresses contiguës, MergeMem regroupe les deux listes en une seule).
Chaque région est décrite par une structure C baptisée MemHeader et définie, elle aussi, dans "exec/memory.h" et "exec/memory.i" :
Donc, pour obtenir l'adresse de la première région de la liste :
Normalement, nous avons là un pointeur sur la liste de la mémoire Chip. La figure 1 montre cela un peu plus clairement.
A l'intérieur de cette structure MemHeader, le noeud (node) sert à relier entre elles les différentes régions.
Le champ ln_Succ pointe sur la prochaine, le champ ln_Pred, sur la précédente.
La structure MemHeader définit l'adresse la plus basse (mh_Lower) et l'adresse la plus haute (mh_Upper) de la
région, ainsi que la taille de mémoire disponible dans cette région (mh_Free). C'est ce champ, mh_Free,
qui est décrémenté et incrémenté au fur et à mesure des allocations et des libérations de mémoire par les
applications. Le MemHeader contient également un pointeur sur le premier bloc mémoire disponible dans cette région (mh_First).
Un bloc mémoire est appelé en terminologie Amiga, un "chunk", et est bien entendu défini par une structure C
que l'on trouve, encore une fois, dans "exec/memory.h" et "execimemory.i" :
Un champ mc_Next à NULL indique la fin de la liste des blocs. Toute la mémoire située entre la fin de la région et
son dernier chunk est libre.
A l'initialisation du système, chaque région ne contiendra qu'un seul bloc. Dans le MemHeader, mh_First et mh_Lower
sont égaux, ainsi que mh_Upper et mh_Free.
La plus petite quantité de mémoire allouable dans une région est de huit octets, car c'est exactement la taille
d'un MemChunk (quatre octets pour le pointeur mc_Next et quatre autres pour le mot long mc_Bytes).
De toute façon, AllocMem() aussi bien que FreeMem() arrondissent et la taille demandée et l'adresse de la mémoire
réservée à leur plus grand multiple de 8. La plus grande quantité est évidemment égale à la taille de cette région.
A la première allocation dans une région donnée, la taille du bloc demandé est additionnée à mh_First
et soustraite de mh_Free. Dans le MemChunk, mc_Next reste à NULL et la taille demandée est soustraite de mc_Bytes.
Si une deuxième demande d'allocation pour cette même région survient, la taille du bloc demandé sera à nouveau
additionnée à mh_First et soustraite de mh_Free et de mc_Bytes. Il n'y aura toujours qu'un chunk dans
la liste, simplement sa taille aura diminué.
Le premier cas de fragmentation de la mémoire survient lorsque le premier bloc est libéré, alors que le second reste
alloué. Dans ce cas, mh_First sera d'abord copié dans mc_Next puis remis à sa valeur initiale (égale à mh_Lower)
et mh_Free sera augmenté de la taille de ce bloc. Cette taille sera également placée dans mc_Bytes.
Arrivés à ce stade, nous aurons :
Pour le MemHeader
- mh_First pointant sur le MemChunk
- mh_Free égal à la taille de la région moins la taille du second bloc.
Pour le MemChunk
- mc_Next pointant sur la fin du second bloc.
- mc_Bytes égal à la taille du premier bloc.
La figure 2 visualise tout cela de manière un peu plus concise.
Quand le premier bloc sera à son tour libéré (en assumant qu'aucune autre allocation n'ait eu
lieu dans cette région), le MemHeader et le MemChunk retrouveront leur configuration originale,
et le second MemChunk sera tout simplement oublié.
Les fonctions d'Exec
Exec offre plusieurs paires de routines pour allouer et libérer la mémoire, la plus connue
et la plus employée étant bien évidemment la paire AllocMem()/FreeMem().
On précise lors de l'appel à ces fonctions, la taille de mémoire que l'on désire et ses
attributs, c'est-à-dire Chip, Fast et/ou Public. La mémoire Chip est celle accessible par
les processeurs spécialisés (Denise, Agnus et Paula) et devra être employée pour tout ce
qui concerne les images devant être "blittées", le son, les tampons mémoire des disquettes,
etc. La mémoire Fast est celle qui n'est pas Chip ; ces deux attributs sont donc
contradictoires et s'annulent l'un l'autre. Enfin, la mémoire Public est la mémoire destinée à
être partagée par plusieurs tâches, ou bien par une tâche donnée et des interruptions...
Elle peut aussi bien être Chip que Fast. Dans la version actuelle d'Exec (jusqu'à la V34
correspondant au Kickstart 1.3), la mémoire Public n'est pas gérée et cet attribut est
tout simplement ignoré, mais il est vivement conseillé de le mettre quand besoin est, pour
des raisons de compatibilité avec les futures versions du système.
Lorsqu'aucun attribut particulier n'est requis, Exec essaie d'abord d'allouer de la mémoire
Fast (sous réserve qu'il y en ait de disponible et en quantité suffisante), sinon de la Chip.
Par conséquent, il est quasiment inutile de préciser que l'on désire de la Fast, sauf cas
exceptionnel. Si aucune mémoire n'est disponible, un pointeur NULL est renvoyé, laissant à la
charge du programme appelant le soin de réagir en conséquence (normalement, sortie "propre" avec
avertissement à l'utilisateur).
Notez au passage que si Exec essaie d'abord d'allouer de la mémoire Fast, c'est simplement parce
que celle-ci a une priorité plus élevée que la mémoire Chip. Avec un petit programme adéquat,
on peut très bien forcer l'inverse.
Il existe également la paire AllocEntry() et FreeEntry(), qui de manière interne, appele AllocMem()
et FreeMem(). Ces fonctions permettent d'allouer et de libérer plusieurs blocs de mémoire en
un seul appel, grâce à une structure MemEntry définie, encore une fois, dans "exec/memory.h"
et "exec/memory.i" :
Rappel : en langage C, une union, à l'inverse d'une structure, ne réserve pas de mémoire pour
chacun de ses membres, mais seulement pour le plus long ; ainsi, elle peut prendre à tour de
rôle le type de chacun de ses membres. Ici, la structure MemEntry occupe donc 8 octets de mémoire,
et non 12 comme on pourrait le penser à première vue.
AllocEntry() et FreeEntry0 utilisent également une seconde structure, connue sous le nom de
MemList (encore une fois, ne confondez pas avec le champ MemList de la structure ExecBase !
Les deux n'ont en commun que le nom !). Cette structure est de taille variable, et est définie ainsi :
Le noeud n'est là que pour vous permettre de lier plusieurs MemLists entre elles ;
il est totalement ignoré par AllocEntry() et FreeEntry(). Vient ensuite le nombre de structures
MemEntry dans cette MemList, puis les MemEntrys elles-mêmes. Cette possibilité n'est que
très rarement utilisée sur l'Amiga.
Troisième paire de routines, Allocate() et Deallocate(). Elles agissent de manière similaire
à AllocMem() et à FreeMem(), mais permettent de prendre en compte une région de mémoire propre à
l'utilisateur. En général, ce sera un gros bloc alloué avec AllocMem(), dans lequel allouera
d'autres petits blocs en fonction des besoins. C'est l'idéal, par exemple, pour un interpréteur
BASIC. L'un des programmes de démonstration utilise ces deux fonctions pour mettre en évidence
le principe de l'allocation de mémoire développé dans cet article.
Pour information, sachez tout de même qu'AllocMem() et FreeMem( appellent, de manière interne, ces fonctions, qui
sont donc la base de toute la gestion de la mémoire par Exec.
Enfin, on peut citer par soucis d'exhaustivité, d'autres routines d'allocations que l'on
trouve dans d'autres bibliothèques qu'Exec. Je pense notamment à AllocRemember()
et FreeRemember() d'intuition.library, à AllocRaster() et FreeRaster()
de la graphics.library... Ces fonctions spécialisées sont destinées à faciliter l'allocation
de certaines mémoires particulières (plans de bits, etc.) et appellent toutes, de façon interne,
AllocMem() et FreeMem().
|