Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Tiens, causons un peu de mémoire...

4 réponses
Avatar
Arnold McDonald \(AMcD\)
Bon, j'ai vu que ça causait de mémoire et de processeurs un peu plus bas. Je
m'autorise un psot un peu longuet, c'est l'occasion de faire quelques
rappels, pour les ceussent qui ne sont pas spécialistes. En même temps, ça
éclaircira plusieurs points j'espère.



Gestion mémoire

============



L'architecture système des Intel modernes supporte l'adressage direct
physique de la mémoire, ou l'adressage logique, via la mémoire virtuelle (et
le concept de pages).



Dans le cas de l'adressage direct, l'adresse d'un objet, que l'on appelle l'adresse
linéaire est directement traduite en adresse physique.



Dans le cas de l'adressage virtuel, toute une cuisine doit être faite. Voir
plus bas.



Modes d'opération du processeur

========================



Contrairement à ce qui a été écrit plus bas, il n'y a pas 3 mais 5 modes d'opération.



1) Le mode adressage réel, histoire de simuler le vieux 8086.

2) Le mode protégé.

3) Le mode 8086 virtuel, un peu pareil que le 1) ci-dessus, sauf qu'on peut
ici utiliser les avantages du mode protégé avec le mode 8086.

4) Le mode gestion système. Le SMM quoi. C'est un mode utilisé pour la
gestion de l'énergie. Apparu avec feu le 80386 SL.

5) Le mode IA-32e, séparé en deux, le mode 64-bit, qui permet un adressage
linéaire sur 64 bits et le mode dit compatibilité, qui sert a exécuter les
« vieilles » applications en mode protégé sans les modifier.



Je vais pas parler des modes 3) et 4), qui n'ont aucun intérêt ici. Au pire,
consultez les manuels Intel I, IIIa et IIIb.



Mode protégé

==========



C'est le mode qui a permi au OS de devenir plus sûr. Il utilise la
segmentation et la pagination. Basiquement, la segmentation permet de
diviser l'espace mémoire en plusieurs zones indépendantes, ce qui permet d'isoler
par exemple le code des données. La pagination permet de mettre en place une
gestion de mémoire virtuelle, c'est-à-dire, de gérer virtuellement plus de
mémoire physique qu'il n'en est réellement disponible. Basiquement, on
utilise des pages. Les pages requises sont chargées physiquement en mémoire
lorsqu'on en a besoin. C'est un peu comme si la mémoire physique était une
armoire de 10 tiroirs et la mémoire virtuelle une armoire de 20 tiroirs. 10
tiroirs peuvent simultanément êtres remplis dans l'armoire physique. Si un
onzième est requis, on vide un tiroir et on le rempli ensuite avec le
onzième. On a toujours que 10 tiroirs physiques, mais il n'empêche qu'on
peut en « gérer » 20.



Segmentation

--------------



Bon, ça sert donc à découper l'espace d'adresse du processeur en plusieurs
zones appelées. segments (on s'en serait douté) ! Un segment peut contenir
au choix du code, des données, ou une pile. Pour les puristes, une pile c'est
des données, pour les hackers, on se marre quand on lit les puristes.
Revenons à nos moutons. La segmentation permet non seulement d'isoler
diverses structures (code et données par exemple), mais aussi de faire du
multitâche (enfin ça aide à simuler du multitâches). Par exemple, si deux
applications tournent ensembles, le processeur leur alloue un segment à
chacune, ce qui permet donc d'isoler le code de chacune d'elles l'une de l'autre.
Bref, en mettant les droits qu'il faut sur les segments, le code d'une
application ne peut donc pas interférer avec le code d'une autre, situé donc
dans un autre segment. Voici donc une avancé de sécurité intéressante et on
applaudi avec les deux oreilles. On notera également que cette «
protection » permises par la segmentation a donné son nom au mode qui permet
sa gestion, appelé donc « protected mode ».



Un peu de vocabulaire

---------------------------



L'espace adressable par le processeur est appelé l'espace d'adresses
linéaires. Un octet, une donnée dans un processus est quand à lui référencé
par une adresse dite logique. Les plus attentifs savent donc déjà qu'une
adresse logique est constituée d'une référence de segment (puisqu'il peut en
exister plusieurs) et d'un offset, qui est donc l'adresse de la donnée dans
ce segment. En fait c'est un chouilla plus compliqué, voir plus bas. Heu et
puis non, voyons le de suite. Comment le processeur garde la trace de tous
les segments ? Il utilise une table, dénommée GDT, ou table globale de
descripteurs. Un descripteur de segment est une structure qui contient,
entre autre, les droits d'accès du segment (lecture, écriture, etc.), son
type (code, données, etc.), son niveau de privilège (de 0 à 3 sur un Intel)
sa taille et, surtout, son adresse (c'est-à-dire celle de son premier
octet). Cette adresse est appelée base segment address et est relative à l'espace
d'adresses linéaire du processeur.



Donc, on peut préciser un peu plus maintenant. Quand on a une adresse
logique, on a en fait un descripteur de segment et un offset. On va lire l'adresse
du segment correspondant dans les données de la GDT, on y ajoute l'offset et
on obtient donc l'adresse dite linéaire, puisque relative à l'espace d'adresses
linéaire du processeur, suivez un peu quoi ! Bon, si on utilise pas la
segmentation (ce n'est en rien obligatoire en mode protégé et, avis perso,
une belle inutilité), cette adresse linéaire correspondra à l'adresse
physique.



Mais voilà, on a beau avoir 4 GB de mémoire sir son PC, il y a toujours un
gestionnaire de base de données qui voudra 16 GB. On a donc rajouté la
pagination pour gérer ce que l'on appelle la mémoire virtuelle. Le principe
est simple. On divise un segment en unités plus petites, appelées pages. Une
page peut être stockée en mémoire physique ou sur disque dur. C'est là l'astuce.
Si un programme utilise 4GB de mémoire et qu'on a que 1GB, eh bien on crée
3GB de pages sur disque. Lorsque les pages nécessaires sont requises, on les
charge en mémoire (après avoir virées celles qui s'y trouvaient avant bien
sûr). On ne peut pas avoir simultanément plus de pages que la mémoire
physique mais virtuellement l'application peut en gérer bien plus. Je m'autorise
une digression. Je n'aime pas la pagination parce que n'importe qui s'étant
amusé à coder un mini-OS (comme tout bon geek qui se doit) pourra vous le
confirmer, les performances sont dégradées de manière spectaculaire dès lors
qu'on active la pagination. Il faut à chaque fois tout un bazar pour gérer
la pagination. Une page doit être chargée en mémoire ? On commence par
générer une exception « page fault », on vérifie les droits, sauvegarde des
contextes, etc. Tout ça prend un temps fou. Bref, j'aime pas. Fin de la
digression. On verra le détail de la pagination plus loin.



Un dernier mot sur les segments. On peu utiliser le modèle dit plat (flat),
dans lequel on utilise un espace d'adresses non segmenté finalement,
autrement dit un seul quoi (relisez bien la phrase). Le mécanisme de
segmentation est donc réduit au strict minimum. On se sert en réalité de
deux segments, un pour le code, l'autre pour les données. Il existe le
modèle plat protégé. Qui dit protégé dit donc prise en compte de la mémoire
physique. La différence entre les deux est que pour le second les limites de
segments sont calculées par rapport à la mémoire physique réellement
présente. Pour le flat standard, CS, SS, DS, ES etc pointent tous sur le
même segment. Les données et le code sont en général situées en bas du
segment (adresse 0) et la pile en haut (pour les segments de 4 GB, on part
donc de FFFFFFFFh). Voilà qui doit rappeler des souvenirs aux vieux codeurs
MS-DOS. Pour le modèle flat protégé, CS pointe sur un segment, celui du
code, et SS, ES, DS, etc sur un deuxième. Donc, en basic flat, un
descripteurs, en protected flat, deux. C'est bien, j'en vois qui suivent.



Mais tout ça c'est de la petite bière. Il existe évidemment bien plus trapu,
le modèle multi-segmenté. Dans ce dernier cas, une application peut utiliser
plusieurs segments simultanément. Pratique pour un OS par exemple. Certains
segments peuvent être accessibles à plusieurs autres (simple exemple, les
données globales d'un OS), d'autres non. Bien sûr, chaque segment est
référencé par son propre descripteur. En mémoire, on utilisera plusieurs
registres qui pointeront chacun sur un segment différent (DS, ES, FS, GS,
etc.). C'est ici qu'il faut caser une remarque sur le mode ia-32e 64 bits
mentionné plus haut. Dans ce mode, on utilise un espace d'adresses linéaire
plat. Donc, 64 bits pour les adresses.



Bien, rentrons dans les détails.



GDT, LDT

========



Que contient réellement la table des segments ? Des descripteurs de
segments, nous l'avons vu. Un descripteur de segment fait 8 octets de long.
Cela fait 64 bits de données que nous n'allons pas détailler bit par bit,
mais bon, c'est l'occasion de montrer la gestion olé-olé de mise en
informatique, que ce soit Microsoft ou Intel ou IBM ou. Un segment est avant
tout référencé par son adresse de base, c'est-à-dire l'adresse de son
premier octet dans l'espace d'adresses linéaires du processeur. L'espace d'adresse
fait 32 bits, donc l'adresse de base d'un segment a besoin de 32 bits.
Croyez-vous que dans le descripteur de segment ces 32 bits soient successifs
? Ben tiens, t'as qu'à croire (quand je vous disais que j'aime pas le mode
protégé.) ! Les 32 bits de l'adresse de base du segment sont répartis comme
suit dans le descripteur de segment :



Bits 0-15 (bits 16-31 du descripteur de segment)

Bits 16-23 (bits 32-39)

Bits 24-31 (bits 56-63)



Pourquoi faire simple quand on peut faire compliqué ?



Au passage, les petits malins pourront se demander où figure cette GDT
concrètement. Eh bien dans l'espace d'adresses linéaires du processeur et c'est
le registre GDTR qui contient l'adresse linéaire de la GDT et sa limite
(concrètement, le nombre de ses entrées). Vous avez bien sûr mémorisé qu'une
entrée de la GDT, qui contient des descripteurs de segments, fait 8 octets.



Rien n'empêche une application (enfin, faut pas pousser, un OS plutôt) d'avoir
des segments supplémentaires. Le cas classique est une table de segments par
tâche exécutée par exemple. Ces tables de segments sont dites locales,
puisque, bien sûr, propres à chaque tâche. Une table de segments locale, ou
LDT, est référencée par une entrée dans la GDT. C'est marrant parce qu'une
LDT est donc située dans un segment. Pour ceux qui ont du mal avec les gags
de geeks, on peut utiliser un descripteur de segment (une entrée de la GDT)
pour définir un segment contenant donc une table de segments (une LDT). Les
petits futés vont rétorquer que si on utilise une LDT, ça va faire pas mal
de boulot de traduction et calculs pour le processeur. Bah oui, vu le bazar
d'un descripteur de segment. Allons, les ingénieurs d'Intel ont rusé. Lorsqu'on
utilise une LDT, le registre LDTR contient le sélecteur de segment de la
LDT, son adresse de base, sa limite etc.



Noter qu'une table de segments peut contenir 8.192 entrées. En fait, pour la
GDT, 8.191, le premier descripteur jouant un peut le rôle de l'adresse 0
pour la mémoire.



Pages

====



Bon, vous devez en avoir ras la patate des segments, alors, on passe aux
pages. Et là, un gugusse lève le bras. « Hey, j'ai bien compris la
pagination, mais les pages, comment on les référence ? ». Haaa, c'est beau
quand il y en a qui suivent et posent des questions intelligentes. Bon, on a
vu que la pagination consiste à découper l'espace d'adressage en régions. On
charge ces régions en mémoire selon les besoins. Ces régions s'appellent des
pages. La page requise est déjà en mémoire ? OK. On fait rien. Elle n'y est
pas ? On génère une exception. Le processeur va en gros chercher la page
stockée sur le disque dur (la mémoire virtuelle quoi) et la charge en
mémoire. Oui, je sais, je me répète mais bon, plus on répète et mieux on
comprends. Pour mieux appréhender le bazar, on prend un exemple.



On est en mode protégé. Donc, une adresse d'octet est logique. Elle est
constituée d'un sélecteur de segment de 16 bits et de 32 bits d'offset. Le
processeur va devoir traduire cette adresse logique en adresse linéaire pour
commencer (étape 1). En adresse physique ensuite (étape 2). Pour cela, il se
sert du sélecteur de segment comme d'une entrée dans la table des
descripteurs de segments, la GDT. Bien sûr, à cette entrée, se trouve le
descripteur de segment, structure dont on a causé plus haut. 16 bits pour un
sélecteur de segment ? Oui, mais en fait, seul 13 servent d'index (bits 3 à
15), les autres bits servent à déterminer si on utilise la GDT ou une LDT et
le niveau de privilège du segment. 2^13 = 8.192. On peut donc adresser 8.192
segments ? Bah oui, on 'la vu plus haut, une GDT ou LDT peut contenir 8.192
entrées. Soyez un peu à la discussion quoi. Le processeur prend donc les 13
bits et va voir dans la GDT à l'entrée correspondante. Bon, on passe sur la
vérification des droits, privilèges toussa. Le processeur récupère surtout l'adresse
de base du segment, autrement dit, l'adresse du segment relativement à l'espace
d'adresses linéaire du processeur. On à le point de départ, le segment. On s'y
déplace comment ? N'oubliez pas l'offset de l'adresse, les 32 bits. Ces 32
bits sont divisés en 3 parties. Les bits 22-31 désignent le répertoire de
pages. 10 bits, soit 1.024 répertoires de pages possibles. Les bits 12-21
désignent la table des pages, 10 bits, soient 1.024 tables de pages. Les
bits 0-11, 12 bits = 4.096 désignent en fait l'offset.



C'est imbitable hein ? Alors, on détaille. Le répertoire de page, qu'il
serait plus judicieux d'appeler répertoire de tables de pages, eh bien
imaginez une table qui référence d'autres tables. Les codeurs C/C++
frétillent déjà à l'idée de pointeur de pointeur. Ben c'est tout simplement
ça. Donc, le répertoire des tables de pages est une table qui pointe vers
des tables. Elle a 1.024 entrés maximum. Donc, on peut pointer vers 1.024
tables. Bien. Ces tables pointées là, c'est quoi ? Tout simplement des
tables contenant les références des fameuses pages dont on parle ! Chacune
de ces tables peut avoir 1.024 entrés, donc, chaque table peut référencer
1.024 pages !



Hola, récapitulons. 1.024 tables de tables x 1.024 références de pages par
table = 1.048.576 = 2^20 pages possibles. Bien, mais la taille de ces pages
là ? Eh bien cela peut aller de 4 KB à 4M. Si on prend des pages de 4 KB,
2^20 x 4.096 = 4 GB. Voilà donc comment on peut adresser 4 GB d'adresses de
l'espace d'adresses linéaires du processeur. Notez que si la table d'un
segment est variable, la taille d'une page est fixée une bonne fois pour
toute. Je peux avoir un segment de 4 MB et un de 256 MB dans la GDT, mais
les deux ne peuvent utiliser que des pages d'une même taille. Tiens, tant
que j'y pense, on peut activer la segmentation, mais pas forcément la
pagination hein (rappelez-vous le modèle flat).



Bon, adresse logique = segment (16 bits) + offset (32 bits). On trouve le
segment, on trouve la table de page (10 bits), la page (10 bits). Mais
comment on se déplace dans cette page ? Ben faut suivre un peu les gars, il
reste 12 bits ! Si, si, relisez plus haut. 12 bits permettant d'adresser
4.096 valeurs, on peut donc adresser n'importe quel octet des 4 GB de l'espace
d'adresses linéaires.



Pour les pages de 4 MB, pagination activée, l'offset d'une adresse logique
est toujours constitué de 32 bits, 10 pour un sélecteur de répertoire de
table de pages, et 22 pour l'offset dans la page. Il n'y a pas de table de
pages quoi. On a 1.024 pages (contre 1.048.576 pour la pagination par page
de 4 KB).



Ouf.



Bien. Quelques remarques. Et si on n'active pas la pagination ? En ce cas, l'adresse
logique est constituée des 16 bits du sélecteur de segment (ça, ça change
pas) et les 32 bits d'offset sont tout simplement l'offset en entier. 2^32 =
4 GB, on peut donc toujours adresser les 4 GB d'adresses de l'espace d'adresses
linéaires du processeur.



Bon, je ne vais pas détailler le format des entrées d'une table de
répertoire ou d'une table de page. Sachez simplement que chaque entrée
possède 32 bits, que pour des pages de 4 KB 20 bits servent de bits les plus
significatifs de l'adresse physique d'une page. Eh oui, 20 bits, comme le
bon vieux mode segmenté 16 bits du MS-DOS. Cela dit, la ruse est subtile.
Puisque une page fait 4 Ko, il est inutile de « mémoriser » les bits de 0 à
4.095, il suffit de multiplier (décaler en binaire) du nombre de bits
correspondant pour atteindre une page. C'est le même principe que les
adresses de 20 bits créées à partir de 16 bits avec le vieux mode segmenté d'il
y a quelques décennies. Pour des pages de 4 MB, même principe, seuls 10 bits
sont utilisés, puisqu'on décalera de 22 bits (2^22 = 4.194.304 = 4 MB).
Bref, parlons peu, parlons bien, les pages doivent être alignées sur des
adresses de 4 KB ou de 4 MB !



On peut également mixer (sur les processeurs modernes !) des pages de 4 KB
et 4 MB à partir d'un même répertoire de table de pages, mais bon, j'ai pas
le temps là. Il existe aussi des pages de 2 MB.



64 GB ??

=======



Quand on lit les documentations des processeurs, on peut lire que l'on peut
adresser 64 GB octets ! Comment c'est possible ? Bien sûr, les processeurs
IA-32 ne gèrent que 32 bits d'adresse, donc, on peut avoir une adresse
physique max de 2^32-1, soit 4.294.967.295 ou 4 GB d'octets adressables. Les
fameux 4 GB de la légende. Depuis le Pentium Pro, on peut agrandir cette
limite en utilisant le PAE qui signifie Physical Address Extension.
Techniquement, le processeur utilise 4 broches de plus pour les adresses. 32
+ 4 = 36, donc on peut aller jusqu'à 36 bits pour une adresse. 2^36 = 64 GB.
Bon, il y a un gag tout de même, on ne peut pas accéder directement. Par
contre, on peut switcher des segments de 4 GB dans cet espace de 64.



Avec le PAE, on peut utiliser des pages de 2 MB ou 4 MB. Bien évidemment, il
y a quelques modifications effectuées en interne. Principalement, une
nouvelle table est créée, la table des pointeurs de répertoires de pages.
Elle possède 4 entrées de 64 bits et est utilisée comme suit. Attention les
djeuns, ça devient hard !



Lorsque PAE est activé, l'adresse linéaire comporte 4 parties. On a toujours
12 bits pour l'offset dans une page (4.096 valeurs), mais 9 bits pour l'index
de la table de pages et 9 pour l'index de répertoire de table de page. Avec
9 bits on a 512 entrées. Donc, on peut adresser 512 x 512 = 256 MB pages.
Mais il reste deux bits, ceux qui permettent d'adresser donc jusqu'à 4
entrées de la table des pointeurs de répertoires de pages mentionnée plus
haut ! Donc, 4 x 256 MB = 1 MB pages. Soit 2^20 pages. Donc, ceux qui
suivent notent qu'avec PAE activé, on peut également adresser 2^20 pages,
comme lorsque PAE est désactivé !



Et les pages de 2 MB alors ? Haha, en ce cas, on n'a plus 12 mais 21 bits
pour l'offset dans une page. 2^21 = 2 MB. Toujours 9 bits pour le répertoire
de table de pages (512) et 2 bits pour la table des pointeurs de répertoires
de pages. Bilan ? 4 x 512 = 2.048 pages (2^11).



2^20 x 4 KB = 4 GB.

2^11 x 2 MB = 4 GB.



Rappelez-vous les pages de 4 MB. 22 bits d'offset et 10 de répertoire de
tables de pages.



2^10 x 4 MB = 4 GB.



Bref, on a toujours 4 GB adressables quel que soit la taille d'une page ou l'activation
ou pas du PAE.



Mais comment accéder à plus de 4 GB ? Deux possibilités :



- Pointer vers une autre table des pointeurs de répertoires de pages.

- Changer les entrées de cette table des pointeurs de répertoires de pages.



Si vraiment vous aimez la franche déconnade, regardez le détail des entrées
de ces tables au chapitre 3.8.5 du volume IIIa de la documentation des
processeurs Intel.



Si j'avais le temps, je vous parlerai du mode ia-32e et de ses adresses
linéaires sur 64 bits. On peut accéder à 2^36 pages de 4 KB. Ou 2^27 pages
de 2 MB. Vous pouvez sortir vos calculatrices, ça permet de faire mumuse
avec un espace d'adresses de 2^48 octets.



Bon, j'arrête là. Ce post est assez long et personne ne le lira jusqu'au
bout. Sachez quand même que cela c'est la partie matérielle de la gestion de
la mémoire, la partie processeur quoi. Il faudrait rajouter un post, celui
du côté logiciel, autrement dit, vu du coté de Windows. Comme c'est 10 fois
plus le bazar, je le ferai une autre fois :-).


--
Arnold McDonald (AMcD) - Help #55/2006

http://arnold.mcdonald.free.fr/

4 réponses

Avatar
Patrick 'Zener' Brunet
Bonjour.

Je réponds à Arnold McDonald (AMcD)
qui dans 4505eb9c$0$914$ a écrit :
Bon, j'ai vu que ça causait de mémoire et de processeurs un peu plus
bas. Je m'autorise un psot un peu longuet, c'est l'occasion de faire
quelques rappels [...]
Bon, j'arrête là. Ce post est assez long et personne ne le lira
jusqu'au bout. [...]



Ben si je l'ai lu jusqu'au bout, après une journée à rafistoler le code p...
d'un système pro, ça détend.

Il y a exactement 15 ans, j'ai passé des mois à faire ça pour faire tourner
un moteur en C 32bits sur Windows3.0 :-)

L'air de rien, ça devrait rester au programme des écoles, parce qu'après ça
on garde de bons réflexes de programmation optimale et robuste, même avec
des outils supposés le faire pour vous.

Merci et bonne soirée - @+

--
/***************************************
* Patrick BRUNET
* E-mail: lien sur http://zener131.free.fr/ContactMe
***************************************/
Avatar
Arnold McDonald \(AMcD\)
Chouette un lecteur :-) !
Avatar
MagicByte
> Chouette un lecteur :-) !



En fait je pense qu'il y en a eu beaucoup, mais comme c'était très complet,
il n'y avait pas beaucoup de questions à poser.
D'ailleurs j'en profite pour faire du HS (enfin pas tout à fait si on
considère Windows CE) : est-ce que quelqu'un peut faire une comparaison
entre l'architecture X86 et ARM sur la gestion de la mémoire et la sécurité
en général. Par exemple y a-t-il un mode superviseur comme en 68000 ?

MB.
Avatar
Thierry
"Arnold McDonald (AMcD)" écrivait
news:4505eb9c$0$914$:

Il faudrait
rajouter un post, celui du côté logiciel, autrement dit, vu du coté de
Windows. Comme c'est 10 fois plus le bazar, je le ferai une autre fois
:-).



Dommage parce que c'est le plus interessant et le plus en charte (de parler
de Windows plutot qu'Intel où tourne X OS). La c'est trop general et
trop long pour faire la part entre ce que je connais deja et ce que
j'aurais aimé apprendre.

Mets a jour ton site :-}