Vous êtes sur la page 1sur 11

CHAPITRE 10 

: GESTION MEMOIRE.

Les développeurs de systèmes embarqués implémentent souvent des services de gestion de la


mémoire au-dessus de ceux fournis par le RTOS. La compréhension de la gestion mémoire est
importante dans le développement d’un système embarqué.
La connaissance des possibilités de gestion de la mémoire du système est utile lors de la
conception et permet d’éviter les pièges. Par exemple, dans de nombreuses applications
embarquées existantes, la routine d’allocation de mémoire dynamique, malloc, est souvent
employée. Elle peut engendrer des effets de bords indésirables appelé fragmentation
mémoire. Cette routine d’allocation mémoire générique, dépendante de son implémentation,
peut impacter les performances de l’application. De plus, elle peut ne supporter la méthode
d’allocation requise par l’application.

De nombreux systèmes embarqués (PDA, téléphones portables, caméras numériques,…) ont


un nombre limité de taches (d’application) qui peuvent « tourner » en parallèle à un instant
donné, mais ces dispositifs ont une faible quantité de mémoire physique embarquée. Les
systèmes embarqués de plus grande taille (comme les routeurs réseau et serveurs web),
dispose d’une mémoire physique plus importante, mais ces système fonctionnent dans des
environnements plus dynamiques, ce qui impose que la fragmentation mémoire soit
minimisée, des entête de service de gestion minimisés, et des temps d’allocation
déterministes.

1. L’allocation mémoire dynamique dans les systèmes embarqués.


Dans un système embarqué, les codes du programme, les données du programme, et la pile du
système occupent la mémoire physique après la fin du programme d’initialisation. Soit le
RTOS, soit le noyau utilise le reste de la mémoire physique pour l’allocation dynamique.
Cette mémoire est appelée tas (heap). La gestion mémoire dans le contexte de ce chapitre se
réfère à la gestion de blocs continus de la mémoire physique, même si le concept introduit
dans ce chapitre s’applique aussi aux blocs non continus. Ces concepts s’appliquent également
à la gestion de types variés de mémoire physique. En général, des fonctions de gestion
mémoire conservent les informations internes pour la heap dans une zone mémoire appelée
bloc de contrôle (control bloc). Les informations internes typiques sont :

 L’adresse du début du bloc mémoire physique utilisé pour l’allocation mémoire


dynamique.
 La taille mémoire de ce bloc de mémoire physique.
 La table d’allocation (allocation table) qui indique les zones mémoires en cours
d’utilisation, les zones mémoires libres, et la taille de chaque région libre.
Ce chapitre examine les aspects de la gestion mémoire à travers un exemple d’implémentation
des fonctions malloc et free dans un système embarqué.

1.1. Fragmentation et compaction mémoire.


Le heap est scindés en petits blocs de taille fixe. Chaque bloc possède une taille unité (unit
size) qui est une puissance de 2, par exemple une taille de 32 octets. La fonction d’allocation

1
mémoire dynamique, malloc, possède un paramètre d’entrée qui indique la taille de
l’allocation requise en octets. Malloc alloue un bloc de plus grande taille, qui est constitué
d’un ou de plusieurs blocs de taille fixe plus petits. La taille de ce grand bloc mémoire est au
moins aussi grande que la taille requise ; c’est le nombre le plus proche du multiple de la taille
unitaire (unit size). Par exemple, si une allocation demande 100 octets, le bloc retourné aura
une taille de 128 octets (4 unités * 32 octets par unités). En conséquence, le demandeur
n’utilisera pas 28 octets (128-100 octets), ce qui est appelé fragmentation mémoire (memory
fragmentation). Cette forme spécifique de fragmentation est appelée fragmentation interne car
elle est interne au bloc alloué.
La table d’allocation peut être représentée comme une table de bits (bitmap), dans laquelle
chaque bit représente une unité de 32 octets. La figure suivante montre l’état de la table
d’allocation après une série d’appels des fonctions malloc et free. Dans cet exemple, le heap
est de 256 octets.

L’étape 6 montre deux blocks libres de 32 octets chacun. L’étape 7, au lieu de conserver 3
blocs libres séparés, montre que tous les blocs libres ont été combinés en un bloc de 128
octets. Comme ce nouveau bloc fait 128 octets, une future allocation de 96 octets se traduira
par un succès.
La figure suivante montre un autre exemple de l’état de la table d’allocation. Notez la présence
de deux blocs libres de 32 octets. Un bloc est à l’adresse 0x10080, tandis que le second bloc
est en 0x101C0, qui ne peut être utilisé pour une allocation mémoire supérieure à 32 octets.
Comme ces blocs isolés ne peuvent pas participer à la réalisation d’un espace mémoire libre
contigu, leur existence pose des problèmes (impossibilité) en cas de demande d’allocation
mémoire d’une taille supérieure à 32 octets. On parle de fragmentation externe car la
fragmentation existe dans la table mais pas dans les blocs eux-mêmes.
Une façon d’éliminer ce type de fragmentation est de compacter les zones adjacentes à ces
deux blocs. Le contenu de la zone mémoire depuis l’adresse 0x100A0 (immédiatement après
le premier bloc libre) jusqu’à l’adresse 0x101BF (immédiatement précédent le second bloc
libre) est décalé de 32 octets vers le bas de la mémoire, ce qui donne une nouvelle zone allant
de 0x10080 jusqu’à 0x1019F, qui effectivement combine deux blocs libres en un bloc de 64
octets. Ce nouveau bloc libre est encore considéré comme de la fragmentation mémoire si de

2
futures allocations demandent une taille mémoire supérieure à 64 octets. En conséquence, la
compaction mémoire continue jusqu’à ce que tous les blocs libres soient combinés en un large
morceau.

Plusieurs problèmes peuvent apparaitre avec la compaction mémoire. Elle consume du temps
pour transférer les contenus mémoires d’un endroit vers un autre. Le cout de l’opération de
copie dépend de la longueur des blocs contigus utilisés. Les taches qui désire obtenir l’accès
concurrent à ces zones mémoires sont empêchés d’y accéder jusqu’à la fin de la copie. La
compaction mémoire est très rarement utilisée dans les systèmes embarqués. Les blocs
mémoire libres sont combinés seulement s’ils sont des voisins proches.
En conclusion, une gestion mémoire efficace doit réaliser les actions suivantes :

 Déterminer si un bloc libre assez grand existe pour satisfaire l’allocation requise. Ce
travail est une partie de la fonction malloc().
 Mettre à jour les informations de gestion interne. Ce travail est réalisé par malloc() et
par free().
 Déterminer si le bloc qui vient d’être libéré peut être combiné avec les blocs libres de
son voisinage pour former une zone plus large. C’est une partie du travail de la
fonction free().
La structure de la table d’allocation est la clé pour une gestion efficace de la mémoire, car la
structure détermine la manière dont les opérations listées plus tôt peuvent être implémentées.
La table d’allocation occupe de l’espace mémoire, il est important qu’elle soit de faible taille.

1.2. Un exemple de malloc et free.


On présente un exemple d’algorithme utilisé par malloc() pour réaliser des allocations
mémoires. Un tableau statique d’entiers, appelé tableau d’allocation (allocation array), est
utilisé pour implanter une table d’allocation (allocation map). Le rôle principal du tableau
d’allocation est de décider si les blocs libres voisins peuvent être agglomérés pour réaliser un
bloc libre de plus grande taille. Chaque entrée de ce tableau représente un bloc mémoire de

3
taille fixe correspondant. Dans ce sens, ce tableau est identique à celui présenté plus haut et
représenté une nouvelle fois ci-dessous, mais ce dernier utilise un schéma de codage différent.

Le nombre d’entrées contenues dans le tableau est le nombre de blocs de taille fixe disponible
dans la zone mémoire gérée. Par exemple, 1 MB de mémoire peut être divisé 32768 blocs de
32 octets chacun. En conséquence, le tableau aura 32768 entrées.

Afin de simplifier l’exemple pour une meilleure compréhension de l’algorithme employé, on


prend le cas où l’on peut utiliser seulement 12 unités mémoire. La figure suivante montre un
tableau d’allocation .

4
Commençons avec l’index du tableau d’allocation débutant à 0. Avant l’allocation d’une zone
mémoire, un bloc libre de grande taille est présent, il contient 12 unités mémoire disponibles.
Le tableau d’allocation utilise un schéma d’encodage simple pour repérer les blocs mémoires
libres et ceux qui sont alloués. Pour indiquer la plage des blocs libres contigus, un nombre
positif est placé dans la première entrée et la dernière entrée pour représenter la plage. Dans
notre exemple, dans le premier tableau présenté à gauche, le nombre d’unités libres (12 dans
notre cas) est placé dans les entrées d’index 0 et d’index 11.

Placer un nombre négatif dans la première entrée et un zéro dans la dernière indique une zone
de blocs alloués. Le nombre placé dans la première entrée est égal à -1 fois le nombre de blocs
alloués.

Dans notre exemple, la première allocation nécessite 3 unités. Le tableau avec le label 1 de la
figure précédente représente l’état du tableau alloué après la première allocation. La valeur -3
est placée à l’index 9 et la valeur 0 est placée à l’index 11, ce qui marque la zone des blocs
alloués. La taille des blocs libres est réduite à 9.

Le tableau portant le label 3 montre l’état du tableau d’allocation après la fin de trois requetés
d’allocations. Cet arrangement du tableau et le marquage des blocs alloués simplifie les
opération de fusion qui prendront place durant l’opération free(), ce qui sera expliqué un peu
plus loin.

Non seulement le tableau d’allocation indique les blocs libres, mais il indique aussi les
adresses de départ de chaque bloc, car il existe une relation simple entre les indices du tableau
et les adresses de départ :

Adresse de départ = offset + taille unité du bloc * index.

Lors de l’allocation d’un bloc mémoire, malloc() utilise cette formule pour calculer l’adresse
de départ du bloc. Par exemple, dans la figure précédente, la première allocation de trois
unités commence à l’index 9. Si l’offset est 0x10000 et la taille unité du bloc est 0x20 (32 en
décimal), l’adresse retournée pour l’index 9 est :

0x10000 + 0x29 = 0x10120.

1.3. Trouver les blocs libres rapidement.


Dans cette technique de gestion mémoire, malloc() alloue toujours à partir de la plus grande
zone de blocs libres. La tableau d’allocation décrit n’est pas organisé pour permettre à
malloc() de réaliser cette tache rapidement. Les entrées représentant les zones libres ne sont
pas triées en fonction de leur taille. Trouver la zone de plus grande taille implique une
recherche complète dans toute la table. Pour cette raison, une seconde structure de données est
utilisée pour accélérer la recherche de blocs libres lors d’une démaque d’allocation mémoire.
Les tailles des blocs libres dans le tableau d’allocation sont maintenues en utilisant la structure
de données du tas (heap), présentée ci-dessous.
Il s’agit d’un arbre binaire complet avec une propriété : la valeur contenue dans un nœud n’est
pas plus petite que celle des nœuds enfants (child node).

5
La taille de chaque bloc libre est déterminante dans l’arrangement du tas. Le bloc libre de plus
grande taille est placé au sommet du heap. L’algorithme employé par malloc() découpe
l’allocation du bloc libre de plus grande taille. La portion restante est réinsérée dans le heap.
Puis le heap est réarrangé, ce qui constitue la dernière étape de l’allocation mémoire.
Bien que la taille de chaque zone libre soit déterminante dans l’arrangement du tas, chaque
nœud du heap est en réalité une structure de données contenant au moins deux informations :
la taille de la zone libre et l’index de départ dans le tableau d’allocation. L’opération malloc()
implique les étapes suivantes :

 Examiner le heap pour déterminer s’il existe un bloc libre assez grand pour
l’allocation requise.
 Si il n’y a pas de blocs libre assez grand, renvoyer une erreur à l’appelant.
 Récupérer l’index de départ de la zone libre du tableau d’allocation en partant du
sommet du heap.
 Mettre à jour le tableau d’allocation en marquant le bloc récemment alloué, comme
indiqué dans l’avant dernière figure.
 Si le bloc entier est utilisé pour satisfaire l’allocation, mettre à jour le heap en effaçant
le plus grand nœud. Sinon, mettre à jour la taille.
 Réarranger le heap.

Avant que n’importe quelle mémoire soit allouée, le heap a juste un nœud, ce qui signifie que
la mémoire est disponible (grand bloc libre). Le heap continue d’avoir un nœud unique soit si
la mémoire est allouée consécutivement sans aucune opération free() ou si chaque opération
free() provoque une fusion du bloc dé alloué avec ses voisins immédiats. La structure du heap,
présentée à figure précédente, représente les blocs libres entrelacés avec des blocs utilisés ;
elle est similaire à l’avant avant dernière figure.
Le heap peut être implémenté en utilisant un autre tableau statique appelé heap array
présenté à la figure précédente. Les index du tableau commencent à 1 au lieu de 0 pour
simplifier le codage en langage C. Dans cet exemple, 6 blocs libres de 20, 18, 12,11, 9 et 4

6
blocs sont disponibles. La prochaine allocation mémoire utilise une zone de 20 blocs
indépendamment de la taille requise dans la requête d’allocation. Notez que le heap array est
une manière compacte d’implémenter un arbre binaire. Il ne stocke pas de pointeurs vers les
nœuds enfants ; au lieu de cela, les relations enfants-parents sont indiquées par les positions
des nœuds dans le tableau.

1.4. L’opération free.


Notez que la couche basse des implémentations de malloc() et free() est indiquée dans les
deux figures précédentes (dernière et avant dernière). En d’autre terme, une autre couche
logicielle enregistre, par exemple, les adresses des blocs alloués et leurs tailles. Supposons que
cette couche logicielle existe et que l’exemple n’est pas concerné par cela sauf que cette
couche alimente la fonction free() avec des informations nécessaires.
La principale opération de la fonction free() est de déterminer si un bloc qui vient d’être libéré
peut être fusionné avec ses voisins. Les règles de cette fusion sont :

 Si l’index de départ du bloc n’est pas 0, vérifier la valeur du tableau à l’index -1. Si la
valeur est positive (donc pas négative ou 0), ce voisin peut être fusionné.
 Si l’index + nombres de blocs ne dépasse la valeur maximale de l’index du tableau,
vérifier que la valeur du tableau à l’index + nombres de blocs. Si la valeur est positive,
ce voisin peut être fusionné.
Ces règles sont illustrées à la figure suivante.

Cette figure montre deux situations importantes. Dans la première, le bloc débutant à l’index 3
est libéré. En suivant la règle 1, on regarde la valeur à l’index 2. Cette valeur est 3. Donc, le
bloc voisin peut être fusionné. La valeur 3 indique que le bloc voisin a une taille de 3 unités.
Le bloc qui vient d’être libéré à une taille de 4, donc en appliquant la règle 2, on regarde la
valeur à l’index 7. La valeur vaut -2, donc le bloc voisin est encore utilisé ne peut pas être
fusionné. Le résultat de l’opération free() dans la première situation est montré dans la
seconde table de la figure ci-dessus.
Dans la seconde situation, le bloc à l’index 7 vient d’être libéré. En suivant la règle 1, on
observe que la valeur à l’index 6 est 0. Cette valeur indique que le bloc voisin est encore

7
occupé. En suivant la règle 2, on regarde la valeur à l’index 9, elle vaut -3. Une fois de plus,
cette valeur indique que ce bloc est occupé. Les blocs nouvellement libérés restent des pièces
indépendantes. Après avoir appliqué les deux règles de fusion, la prochaine opération free()
sur le bloc débutant à l’index 3 donne une table d’allocation indiquée sur la droite de la figure
précédente.
Lorsqu’un bloc est libéré, le heap doit également être mis à jour. L’opération free() implique
les étapes suivantes :

 Mise à jour du tableau d’allocation et fusion avec les blocs voisins si possible.
 Si le nouveau bloc libéré ne peut être fusionné avec un de ses voisins, ajouter une
nouvelle entrée dans le heap array.
 Si le nouveau bloc libéré peut être fusionné avec l’un de ses voisins, l’entrée dans le
heap représentant le bloc voisin doit être mise à jour, et l’entrée mise à jour réarrangée
en fonction de sa nouvelle taille.
 Si le nouveau bloc libéré peut être fusionné avec tous ses voisins, l’entrée dans le
heap représentant un des blocs voisins doit effacée du heap, et l’entrée du heap
représentant les autres blocs voisins doit être mise à jour réarrangée en fonction de sa
nouvelle taille.

2. La gestion mémoire de taille fixée dans les systèmes embarqués.


Une autre approche pour la gestion mémoire utilise la méthode des groupes de mémoires
(memory pools) de tailles fixes. Cette approche est souvent rencontrée dans les codes réseaux
de l’embarqué (ex : pile protocolaires).
Comme indiqué à la figure suivante, l’espace mémoire disponible est divisé en groupe
mémoire de tailles variées. Tous les blocs du même groupe mémoire ont la même taille. Dans
cet exemple, l’espace mémoire est divisé en trois groupes, de tailles respectives 32, 50 et 128.

Chaque groupe mémoire possède une structure de contrôle qui maintient les informations
telles que la taille du bloc, le nombre total de blocs, et le nombre de blocs libres. Dans cet
exemple, les groupes mémoires sont liés ensemble et classé par la taille. Pour trouver la plus
petite taille adéquate à une allocation, il faut effectuer une recherche dans la liaison et
examiner chaque structure de contrôle pour le premier bloc de taille adéquate.
Lorsque allocation réussie provoque la suppression d’une entrée du groupe mémoire. Un dé
allocation réussie provoque la réinsertion de l’entrée dans le groupe mémoire. La structure du
groupe mémoire présenté à la figure précédente est une liste liée uniquement. Cette méthode

8
n’est pas aussi efficace que l’algorithme présenté au paragraphe 2 et possède des
inconvénients. Dans les systèmes temps réels embarqués, le besoin mémoire d’une tache
dépend de son environnement. Cet environnement peut être dynamique. Cette méthode ne
fonctionne pas bien pour des applications embarquées qui opèrent de façon constante dans un
environnement dynamique car il est presque impossible d’anticiper les tailles des blocs
mémoires qu’une tache peut couramment utiliser. Ce problème génère l’augmentation de la
fragmentation mémoire par allocation. De plus, le nombre de blocs à allouer pour chaque taille
est aussi impossible à prédire. Dans de nombreux cas, les groupes mémoires ont une
construction basée sur un nombre d’hypothèses. Il en résulte que certains groupe mémoires
sont peu ou pas utilisés, alors que d’autres le sont trop.
D’un autre côté, cette méthode d’allocation mémoire peut vraiment réduire la fragmentation
interne et fournir une utilisation intensive pour les applications embarquées statiques. Ce sont
les applications où l’environnement est prédictible, où le connait le nombre de taches au
démarrage de l’application, et où on connait les tailles des blocs mémoire nécessaires.
Un avantage de cette méthode de gestion mémoire est qu’elle est plus déterministe que
l’algorithme basé sur le heap. Dans la méthode basée sur le heap, chaque opération malloc()
ou free() peut potentiellement déclencher une réorganisation du heap. Dans la méthode des
groupes mémoire, les blocs sont pris ou retourné depuis le commencement de la liste de telle
sorte que les opérations prennent un temps constant. La méthode de gestion par groupe
mémoire ne nécessite pas de restructuration.

3. Les fonctions mémoire bloquantes vs Non bloquantes.


Les fonctions malloc() et free() ne permettent pas la tache appelante de se bloquer en attente
d’une disponibilité de la mémoire. Dans de nombreux systèmes temps réels embarqués, les
taches sont en compétition pour la taille mémoire disponible et limitée. Souvent,
l’indisponibilité de la mémoire est temporaire. Pour certaines tâches, lorsque la requête
d’allocation mémoire échoue, la tache doit reprendre l’exécution depuis une situation
antérieure. Cela est indésirable, car l’opération peut être couteuse. Si les taches ont la
possibilité interne de détecter la condition d’apparition de congestion mémoire mais seulement
temporaire, les taches peuvent être conçues pour être plus flexible. Si une telle tache peut
tolérer un délai d’allocation, la tache peut choisir d’attendre que la mémoire soit disponible au
lieu d’échouer entièrement ou de reprendre son exécution depuis un point antérieur.
Par exemple, les transmissions sur un réseau de type Ethernet se font en rafales. Un nœud
réseau embarqué peut recevoir quelques paquets pendant une durée puis être subitement
assaillis de paquets qui arrivent très rapidement et en grande quantité. Durant cette rafale de
données, les taches dans le nœud embarqué qui traite l’envoi de donnée peut se retrouver en
manque de mémoire, cette dernière étant monopolisée pour la réception des données. Les
taches envoyant les données peuvent attendre une situation plus favorable puis reprendre leurs
opérations.
En pratique, une fonction d’allocation mémoire bien conçue devrai permettre un blocage pour
toujours, un blocage durant une période donnée ou ne pas bloquer du tout. On utilise dans ce
cours une approche de type groupe mémoire pour implémenter une fonction d’allocation
mémoire en mode bloqué.

9
Comme le montre la figure suivante, une fonction d’allocation mémoire bloquante peut être
implémentée en utilisant à la fois un sémaphore à compte et un mutex. Ces primitives de
synchronisation sont créées pour chaque groupe mémoire et conservées dans une structure de
contrôle.

Le sémaphore à compte est initialisé avec le nombre total de blocs mémoire disponibles lors
de la création du groupe mémoire. Les blocs mémoires sont alloués et libérés à partir de début
de la liste.
De multiples taches peuvent accéder à la liste des blocs libres des groupes mémoire. La
structure de contrôle est mise à jour chaque fois qu’une allocation ou dé allocation apparait.
En conséquence, le verrouillage du mutex est utilisé pour garantir à la tache un accès exclusif
à la liste des blocs libres et à la structure de contrôle. Une tache peut attendre qu’un bloc soit
disponible, acquérir le bloc, puis continuer son exécution. Dans ce cas, un sémaphore à
compte est utilisé.
Pour qu’une requête d’allocation réussisse, la tache doit d’abord acquérir avec succès le
sémaphore à compte, puis acquérir avec succès le mutex.
L’acquisition du sémaphore à compte réserve un morceau de blocs disponible depuis le pool.
Une tache tente d’abord s’acquérir le sémaphore à compte. Si aucun bloc est disponible, la
tache se bloque sur le sémaphore à compte, en supposant que la tache est conçue pour
l’attendre. Si une ressource est disponible, la tache réussit à acquérir le sémaphore à compte.
La valeur du sémaphore à compte est maintenant décrémentée d’une unité. A ce niveau, la
tache a réservé un ensemble de blocs disponibles mais doit cependant obtenir le bloc.
Ensuite, la tache tente de verrouiller le mutex. Si une autre tache est en train d’acquérir un bloc
en dehors du pool ou si une autre tache est en train de libérer un bloc à destination du pool, le
mutex est dans l’état verrouillé. Les taches sont dans l’état bloqué en attendant la libération du
mutex. Après que la tache verrouille le mutex, la tache retire la ressource de la liste. Le
sémaphore à compte est relâché lorsque la tache finit d’utiliser le bloc mémoire.

10
Le pseudo code pour l’allocation mémoire avec sémaphore à compte et mutex est présenté ci-
dessous.

Le pseudo code pour la dé-allocation mémoire avec sémaphore à compte et mutex est
présenté ci-dessous.

Les deux pseudos code précédents sont des fonctions d’allocation mémoire et dé-allocation
fiables pour du traitement multitâches. L’utilisation du sémaphore à compte et du mutex
élimine les problèmes d’inversion de priorité lorsqu’un blocage d’allocation mémoire est
validé avec ces primitives de synchronisation.

4. Les unités de management mémoire hardware.


Jusqu’à présent les discussions sur la gestion mémoire se concentrait sur des mémoires
physiques. Un autre sujet est la gestion de mémoire virtuelle. Il s’agit d’une technique dans
laquelle le stockage de masse (par ex disque dur) est fait pour apparaitre dans une application
comme si le stockage de masse était en RAM. L’espace mémoire d’adresse virtuelle (nommé
espace d’adresse logique ou logical address space) est plus grand que l’espace mémoire
physique réel. Cette caractéristique permet au programme de taille plus importante que la
mémoire physique de s’exécuter. L’unité de gestion mémoire appelée MMU (Memory
Managment Unit) fournit plusieurs fonctions. D’abord, la MMU translate les adresses
virtuelles en adresses physiques lors de chaque accès mémoire.
Les fonctions de translation d’adresses différent en fonction de la conception des MMU. De
nombreux RTOS commerciaux ne supportent pas l’implémentation d’adresses virtuelles, donc
ce chapitre ne traitera pas de la translation d’adresses. En revanche, ce chapitre présente le
dispositif de protection mémoire des MMU car de nombreux RTOS les supportent.
Si une MMU est validée sur un système embarqué, la mémoire physique est typiquement
divisée en pages. Un ensemble d’attribut est associé avec chaque page mémoire.
L’information sur les attributs peut inclure les éléments suivants :

 Si la page contient du code (instructions exécutables) ou des données.


 Si la page est en lecture, écriture, exécutable, ou une combinaison de ces propriétés.
 Si la page peut être accédée lorsque la CPU n’est pas en mode privilégié pour
l’exécution, ou accédée uniquement en mode privilégié, ou les deux.
Tous les accès mémoire sont faits au travers de la MMU lorsqu’elle est validée. En
conséquence, le hardware doit faire exécuter les accès mémoire selon les attributs de la page.
Par exemple, si la tache tente d’écrire dans une région mémoire qui permet uniquement un
accès en lecture, l’opération est considérée comme illégale, et le MMU ne la permet pas. Le
résultat de cette tentative avortée est le déclenchement d’une exception d’accès mémoire.

11

Vous aimerez peut-être aussi