Cette question vise à combler le vide de bonnes informations gratuites sur le sujet.
Je crois qu'une bonne réponse rentrera dans une grande SO réponse ou au moins dans quelques réponses.
L'objectif principal est de donner aux débutants complets juste assez d'informations pour qu'ils puissent prendre le manuel par eux-mêmes et être en mesure de comprendre les concepts de base du système d'exploitation liés à la pagination.
Lignes directrices suggérées:
Questions connexes et pourquoi je pense que ce ne sont pas des dupes:
Comment fonctionnent les tableaux de pages x86? : le titre est presque le même que cette question, mais le corps pose des questions spécifiques liées à cr3 et TLB. Cette question est un sous-ensemble de celle-ci.
Comment fonctionne la virtualisation x86 : le corps ne demande que des sources.
Version de cette réponse avec une belle table des matières et plus de conten .
Je corrigerai toute erreur signalée. Si vous souhaitez apporter des modifications importantes ou ajouter un aspect manquant, faites-les sur vos propres réponses pour obtenir une représentation bien méritée. Les modifications mineures peuvent être fusionnées directement dans.
Exemple minimal: https://github.com/cirosantilli/x86-bare-metal-examples/blob/5c672f73884a487414b3e21bd9e579c67cd77621/paging.S
Comme tout le reste en programmation, la seule façon de vraiment comprendre cela est de jouer avec un minimum d'exemples.
Ce qui en fait un sujet "difficile", c'est que l'exemple minimal est grand car vous devez créer votre propre petit système d'exploitation.
Bien qu'il soit impossible de comprendre sans exemples à l'esprit, essayez de vous familiariser avec les manuels dès que possible.
Intel décrit la pagination dans le Intel Manual Volume 3 System Programming Guide - 325384-056US septembre 2015 Chapter 4 "Paging".
La Figure 4-4 "Formats de CR3 et entrées de structure de pagination avec pagination 32 bits", qui donne les structures de données clés, est particulièrement intéressante.
La pagination est effectuée par la partie Memory Management Unit (MMU) de la CPU. Comme beaucoup d'autres (par exemple coprocesseur x87 , APIC ), cela se faisait par puce séparée les premiers jours, ce qui était plus tard intégré dans le CPU. Mais le terme est toujours utilisé.
Les adresses logiques sont les adresses de mémoire utilisées dans le code utilisateur "normal" (par exemple, le contenu de rsi
dans mov eax, [rsi]
).
La première segmentation les traduit en adresses linéaires, puis la pagination traduit ensuite les adresses linéaires en adresses physiques.
(logical) ------------------> (linear) ------------> (physical)
segmentation paging
La plupart du temps, nous pouvons considérer les adresses physiques comme une indexation réelle RAM cellules de mémoire matérielle, mais ce n'est pas vrai à 100% en raison de:
La pagination n'est disponible qu'en mode protégé. L'utilisation de la pagination en mode protégé est facultative. La pagination est activée si le bit PG
du cr0
le registre est défini.
Une différence majeure entre la pagination et la segmentation est que:
C'est le principal avantage de la pagination, car des morceaux de taille égale rendent les choses plus faciles à gérer.
La pagination est devenue tellement plus populaire que la prise en charge de la segmentation a été abandonnée en x86-64 en mode 64 bits, le principal mode de fonctionnement des nouveaux logiciels, où elle n'existe qu'en mode de compatibilité, qui émule IA32.
La pagination est utilisée pour implémenter des espaces d'adresses virtuelles de processus sur un système d'exploitation moderne. Avec les adresses virtuelles, le système d'exploitation peut adapter deux ou plusieurs processus simultanés sur un seul RAM d'une manière qui:
Historiquement, la pagination est venue après la segmentation et l'a largement remplacée pour l'implémentation de la mémoire virtuelle dans les systèmes d'exploitation modernes tels que Linux, car il est plus facile de gérer les blocs de mémoire de taille fixe des pages au lieu des segments de longueur variable.
Comme la segmentation en mode protégé (où la modification d'un registre de segment déclenche une charge à partir du GDT ou du LDT), le matériel de pagination utilise des structures de données en mémoire pour faire son travail (tableaux de pages, répertoires de pages, etc.).
Le format de ces structures de données est fixé par le matériel , mais c'est à l'OS de configurer et de gérer ces structures de données sur RAM correctement, et pour indiquer au matériel où les trouver (via cr3
).
Certaines autres architectures laissent la pagination presque entièrement entre les mains du logiciel, donc une erreur TLB exécute une fonction fournie par le système d'exploitation pour parcourir les tables de pages et insérer le nouveau mappage dans la TLB. Cela laisse les formats de table de pages à être choisis par le système d'exploitation, mais le rend peu probable que le matériel puisse chevaucher les pages-pages avec l'exécution dans le désordre d'autres instructions, comme le fait x86 .
Voici un exemple du fonctionnement de la pagination sur une version simplifiée de l'architecture x86 pour implémenter un espace mémoire virtuel.
L'OS pourrait leur donner les tableaux de pages suivants:
Tableau de pages donné au processus 1 par l'OS:
RAM location physical address present
----------------- ----------------- --------
PT1 + 0 * L 0x00001 1
PT1 + 1 * L 0x00000 1
PT1 + 2 * L 0x00003 1
PT1 + 3 * L 0
... ...
PT1 + 0xFFFFF * L 0x00005 1
Tableau de pages donné au processus 2 par l'OS:
RAM location physical address present
----------------- ----------------- --------
PT2 + 0 * L 0x0000A 1
PT2 + 1 * L 0x0000B 1
PT2 + 2 * L 0
PT2 + 3 * L 0x00003 1
... ... ...
PT2 + 0xFFFFF * L 0x00004 1
Où:
PT1
et PT2
: position initiale des tables 1 et 2 sur la RAM.
Exemples de valeurs: 0x00000000
, 0x12345678
, etc.
C'est l'OS qui décide de ces valeurs.
L
: longueur d'une entrée de table de pages.
present
: indique que la page est présente en mémoire.
Les tables de pages sont situées sur la RAM. Ils pourraient par exemple se situer comme:
--------------> 0xFFFFFFFF
--------------> PT1 + 0xFFFFF * L
Page Table 1
--------------> PT1
--------------> PT2 + 0xFFFFF * L
Page Table 2
--------------> PT2
--------------> 0x0
Les emplacements initiaux sur RAM pour les deux tables de pages sont arbitraires et contrôlés par l'OS. C'est à l'OS de s'assurer qu'ils ne se chevauchent pas!
Chaque processus ne peut pas toucher directement les tables de pages, bien qu'il puisse faire des requêtes au système d'exploitation qui entraînent la modification des tables de pages, par exemple en demandant des segments de pile ou de segment de mémoire plus importants.
Une page est un bloc de 4 Ko (12 bits), et comme les adresses ont 32 bits, seuls 20 bits (20 + 12 = 32, donc 5 caractères en notation hexadécimale) sont nécessaires pour identifier chaque page. Cette valeur est fixée par le matériel.
Un tableau de pages est ... un tableau d'entrées de tableau de pages!
Le format exact des entrées de table est fixé par le matériel .
Dans cet exemple simplifié, les entrées de la table de pages ne contiennent que deux champs:
bits function
----- -----------------------------------------
20 physical address of the start of the page
1 present flag
dans cet exemple, les concepteurs de matériel auraient pu choisir L = 21
.
La plupart des entrées de table de pages réelles ont d'autres champs.
Il ne serait pas pratique d'aligner les choses à 21 octets car la mémoire est adressable par octets et non par bits. Par conséquent, même dans seulement 21 bits nécessaires dans ce cas, les concepteurs de matériel choisiraient probablement L = 32
pour rendre l'accès plus rapide et réserver les bits aux bits restants pour une utilisation ultérieure. La valeur réelle de L
sur x86 est de 32 bits.
Une fois les tables de pages configurées par l'OS, la traduction d'adresse entre les adresses linéaires et physiques est effectuée par le matériel .
Lorsque le système d'exploitation souhaite activer le processus 1, il définit le cr3
à PT1
, le début de la table pour le processus un.
Si le processus 1 souhaite accéder à l'adresse linéaire 0x00000001
, le circuit matériel de pagination effectue automatiquement les opérations suivantes pour le système d'exploitation:
diviser l'adresse linéaire en deux parties:
| page (20 bits) | offset (12 bits) |
Donc, dans ce cas, nous aurions:
regardez dans le tableau de page 1 parce que cr3
le montre.
regarder l'entrée 0x00000
car c'est la partie de la page.
Le matériel sait que cette entrée se trouve à RAM address PT1 + 0 * L = PT1
.
puisqu'il est présent, l'accès est valide
par le tableau des pages, l'emplacement du numéro de page 0x00000
est à 0x00001 * 4K = 0x00001000
.
pour trouver l'adresse physique finale, il suffit d'ajouter le décalage:
00001 000
+ 00000 001
-----------
00001 001
car 00001
est l'adresse physique de la page recherchée sur la table et 001
est le décalage.
Comme son nom l'indique, l'offset est toujours simplement ajouté à l'adresse physique de la page.
le matériel obtient alors la mémoire à cet emplacement physique.
De la même manière, les traductions suivantes se produiraient pour le processus 1:
linear physical
--------- ---------
00000 002 00001 002
00000 003 00001 003
00000 FFF 00001 FFF
00001 000 00000 000
00001 001 00000 001
00001 FFF 00000 FFF
00002 000 00002 000
FFFFF 000 00005 000
Par exemple, lors de l'accès à l'adresse 00001000
, la partie de la page est 00001
le matériel sait que son entrée de table de pages se trouve à RAM adresse: PT1 + 1 * L
(1
à cause de la partie page), et c'est là qu'il le recherchera.
Lorsque le système d'exploitation souhaite passer au processus 2, il lui suffit de faire cr3
pointez sur la page 2. C'est aussi simple que cela!
Maintenant, les traductions suivantes se produiraient pour le processus 2:
linear physical
--------- ---------
00000 002 00001 002
00000 003 00001 003
00000 FFF 00001 FFF
00001 000 00000 000
00001 001 00000 001
00001 FFF 00000 FFF
00003 000 00003 000
FFFFF 000 00004 000
La même adresse linéaire se traduit par différentes adresses physiques pour différents processus , en fonction uniquement de la valeur à l'intérieur de cr3
.
De cette façon, chaque programme peut s'attendre à ce que ses données commencent à 0
et se termine à FFFFFFFF
, sans se soucier des adresses physiques exactes.
Que faire si le processus 1 essaie d'accéder à une adresse dans une page qui n'est pas présente?
Le matériel notifie le logiciel via une exception de défaut de page.
Il appartient ensuite généralement au système d'exploitation d'enregistrer un gestionnaire d'exceptions pour décider de ce qui doit être fait.
Il est possible que l'accès à une page qui n'est pas sur la table soit une erreur de programmation:
int is[1];
is[2] = 1;
mais il peut y avoir des cas où cela est acceptable, par exemple sous Linux lorsque:
le programme veut augmenter sa pile.
Il essaie simplement d'accéder à un certain octet dans une plage possible donnée, et si le système d'exploitation est satisfait, il ajoute cette page à l'espace d'adressage du processus.
la page a été échangée sur le disque.
Le système d'exploitation devra faire un peu de travail derrière les processus pour récupérer la page dans la RAM.
L'OS peut découvrir que c'est le cas sur la base du contenu du reste de l'entrée de table de pages, car si le drapeau actuel est vide, les autres entrées de l'entrée de table de pages sont complètement laissées à l'OS pour ce qu'il veut.
Sous Linux par exemple, lorsqu'il est présent = 0:
si tous les champs de l'entrée de table de pages sont 0, adresse non valide.
sinon, la page a été échangée sur le disque et les valeurs réelles de ces champs codent la position de la page sur le disque.
Dans tous les cas, le système d'exploitation a besoin de savoir quelle adresse a généré l'erreur de page pour pouvoir résoudre le problème. C'est pourquoi les développeurs de Nice IA32 ont défini la valeur de cr2
à cette adresse chaque fois qu'un défaut de page se produit. Le gestionnaire d'exceptions peut alors simplement regarder dans cr2
pour obtenir l'adresse.
Simplifications de la réalité qui rendent cet exemple plus facile à comprendre:
tous les circuits de radiomessagerie réels utilisent la radiomessagerie à plusieurs niveaux pour économiser de l'espace, mais cela a montré un schéma simple à un niveau.
les tables de pages ne contiennent que deux champs: une adresse de 20 bits et un indicateur de présence de 1 bit.
Les tableaux de pages réelles contiennent un total de 12 champs, et donc d'autres fonctionnalités qui ont été omises.
Le problème avec un schéma de pagination à un niveau est qu'il prendrait trop de RAM: 4G/4K = 1M d'entrées par processus. Si chaque entrée est longue de 4 octets, cela ferait 4M par processus , ce qui est trop même pour un ordinateur de bureau: ps -A | wc -l
dit que j'exécute 244 processus en ce moment, ce qui prendrait environ 1 Go de RAM!
Pour cette raison, les développeurs x86 ont décidé d'utiliser un schéma à plusieurs niveaux qui réduit l'utilisation de RAM.
L'inconvénient de ce système est qu'il a un temps d'accès légèrement plus élevé.
Dans le schéma de pagination simple à 3 niveaux utilisé pour les processeurs 32 bits sans PAE, les 32 bits d'adresse sont divisés comme suit:
| directory (10 bits) | table (10 bits) | offset (12 bits) |
Chaque processus doit avoir un et un seul répertoire de pages qui lui sont associés, il contiendra donc au moins 2^10 = 1K
entrées de répertoire de pages, bien mieux que le minimum de 1M requis sur un schéma à un niveau.
Les tables de pages sont uniquement allouées selon les besoins de l'OS. Chaque table de pages a 2^10 = 1K
entrées du répertoire des pages
Les répertoires de pages contiennent ... des entrées de répertoire de pages! Les entrées de répertoire de page sont les mêmes que les entrées de table de page sauf que elles pointent vers RAM adresses des tables de pages au lieu des adresses physiques des tables Étant donné que ces adresses n'ont qu'une largeur de 20 bits, les tables de pages doivent se trouver au début des pages de 4 Ko.
cr3
pointe maintenant vers l'emplacement sur RAM du répertoire des pages du processus en cours au lieu des tables de pages.
Les entrées des tables de pages ne changent pas du tout d'un schéma à un niveau.
Les tables de pages changent d'un schéma à un niveau car:
La raison d'utiliser 10 bits sur les deux premiers niveaux (et non, disons, 12 | 8 | 12
) signifie que chaque entrée de la table des pages a une longueur de 4 octets. Ensuite, les 2 ^ 10 entrées des répertoires de pages et des tables de pages s'intégreront parfaitement dans les pages de 4 Ko. Cela signifie qu'il est plus rapide et plus simple d'allouer et de désallouer des pages à cette fin.
Répertoire de pages donné au processus 1 par le système d'exploitation:
RAM location physical address present
--------------- ----------------- --------
PD1 + 0 * L 0x10000 1
PD1 + 1 * L 0
PD1 + 2 * L 0x80000 1
PD1 + 3 * L 0
... ...
PD1 + 0x3FF * L 0
Tables de pages fournies au processus 1 par l'OS à PT1 = 0x10000000
(0x10000
* 4K):
RAM location physical address present
--------------- ----------------- --------
PT1 + 0 * L 0x00001 1
PT1 + 1 * L 0
PT1 + 2 * L 0x0000D 1
... ...
PT1 + 0x3FF * L 0x00005 1
Tables de pages fournies au processus 1 par l'OS à PT2 = 0x80000000
(0x80000
* 4K):
RAM location physical address present
--------------- ----------------- --------
PT2 + 0 * L 0x0000A 1
PT2 + 1 * L 0x0000C 1
PT2 + 2 * L 0
... ...
PT2 + 0x3FF * L 0x00003 1
où:
PD1
: position initiale du répertoire de pages du processus 1 sur la RAM.PT1
et PT2
: position initiale du tableau de pages 1 et du tableau de pages 2 pour le processus 1 sur la RAM.Ainsi, dans cet exemple, le répertoire de pages et la table de pages peuvent être stockés dans RAM quelque chose comme:
----------------> 0xFFFFFFFF
----------------> PT2 + 0x3FF * L
Page Table 1
----------------> PT2
----------------> PD1 + 0x3FF * L
Page Directory 1
----------------> PD1
----------------> PT1 + 0x3FF * L
Page Table 2
----------------> PT1
----------------> 0x0
Traduisons l'adresse linéaire 0x00801004
pas à pas.
Nous supposons que cr3 = PD1
, c'est-à-dire qu'il pointe vers le répertoire de pages qui vient d'être décrit.
En binaire, l'adresse linéaire est:
0 0 8 0 1 0 0 4
0000 0000 1000 0000 0001 0000 0000 0100
Regroupement en tant que 10 | 10 | 12
donne:
0000000010 0000000001 000000000100
0x2 0x1 0x4
qui donne:
Le matériel recherche donc l'entrée 2 du répertoire des pages.
La table de répertoire de pages indique que la table de pages se trouve à 0x80000 * 4K = 0x80000000
. Il s'agit du premier accès RAM du processus.
Étant donné que l'entrée de table de pages est 0x1
, le matériel examine l'entrée 1 du tableau des pages à 0x80000000
, qui lui indique que la page physique se trouve à l'adresse 0x0000C * 4K = 0x0000C000
. Il s'agit du deuxième accès RAM du processus.
Enfin, le matériel de pagination ajoute l'offset et l'adresse finale est 0x0000C004
.
D'autres exemples d'adresses traduites sont:
linear 10 10 12 split physical
-------- --------------- ----------
00000001 000 000 001 00001001
00001001 000 001 001 page fault
003FF001 000 3FF 001 00005001
00400000 001 000 000 page fault
00800001 002 000 001 0000A001
00801008 002 001 008 0000C008
00802008 002 002 008 page fault
00B00001 003 000 000 page fault
Des erreurs de page se produisent si une entrée de répertoire de pages ou une entrée de table de pages n'est pas présente.
Si le système d'exploitation veut exécuter un autre processus simultanément, il donnerait au deuxième processus un répertoire de pages distinct et lierait ce répertoire à des tables de pages distinctes.
64 bits est encore trop d'adresse pour les tailles actuelles RAM, donc la plupart des architectures utiliseront moins de bits.
x86_64 utilise 48 bits (256 TiB), et le PAE du mode hérité autorise déjà les adresses 52 bits (4 PiB).
12 de ces 48 bits sont déjà réservés pour le décalage, ce qui laisse 36 bits.
Si une approche à 2 niveaux est adoptée, la meilleure répartition serait de deux niveaux à 18 bits.
Mais cela signifierait que le répertoire de la page aurait 2^18 = 256K
entrées, ce qui prendrait trop de RAM: proche d'une pagination à un seul niveau pour les architectures 32 bits!
Par conséquent, les architectures 64 bits créent encore plus de niveaux de page, généralement 3 ou 4.
x86_64 utilise 4 niveaux dans un 9 | 9 | 9 | 12
schéma, pour que le niveau supérieur ne prenne que 2^9
entrées de niveau supérieur.
Extension d'adresse physique.
Avec 32 bits, seuls 4 Go RAM peuvent être adressés.
Cela a commencé à devenir une limitation pour les grands serveurs, Intel a donc introduit le mécanisme PAE dans Pentium Pro.
Pour résoudre le problème, Intel a ajouté 4 nouvelles lignes d'adresse, afin de pouvoir traiter 64 Go.
La structure de la table des pages est également modifiée si PAE est activé. La manière exacte dont il est modifié dépend de la météo PSE est activé ou désactivé.
PAE est activé et désactivé via le bit PAE
de cr4
.
Même si la mémoire adressable totale est de 64 Go, les processus individuels ne peuvent utiliser que jusqu'à 4 Go. Le système d'exploitation peut cependant mettre différents processus sur différents morceaux de 4 Go.
Extension de taille de page.
Permet aux pages d'avoir une longueur de 4M (ou 2M si PAE est activé) au lieu de 4K.
PSE est activé et désactivé via le bit PAE
de cr4
.
Si PAE et PSE sont actifs, différents schémas de niveau de pagination sont utilisés:
pas de PAE ni de PSE: 10 | 10 | 12
pas de PAE et PSE: 10 | 22
.
22 est le décalage dans la page de 4 Mo, car l'adresse de 22 bits 4 Mo.
PAE et sans PSE: 2 | 9 | 9 | 12
La raison pour laquelle 9 est utilisé deux fois au lieu de 10 est que les entrées ne peuvent plus tenir sur 32 bits, qui ont tous été remplis par 20 bits d'adresse et 12 bits de drapeau significatifs ou réservés.
La raison en est que 20 bits ne suffisent plus pour représenter l'adresse des tables de pages: 24 bits sont désormais nécessaires en raison des 4 fils supplémentaires ajoutés au processeur.
Par conséquent, les concepteurs ont décidé d'augmenter la taille des entrées à 64 bits et de les faire tenir dans une table d'une seule page, il est nécessaire de réduire le nombre d'entrées à 2 ^ 9 au lieu de 2 ^ 10.
Le début 2 est un nouveau niveau de page appelé Page Directory Pointer Table (PDPT), car il pointe vers les répertoires de page et remplit l'adresse linéaire 32 bits. Les PDPT ont également une largeur de 64 bits.
cr3
pointe désormais vers les PDPT qui doivent être sur les quatre premiers 4 Go de mémoire et alignés sur des multiples de 32 bits pour l'efficacité de l'adressage. Cela signifie que maintenant cr3
a 27 bits significatifs au lieu de 20: 2 ^ 5 pour les 32 multiples * 2 ^ 27 pour compléter les 2 ^ 32 du premier 4 Go.
PAE et PSE: 2 | 9 | 21
Les concepteurs ont décidé de conserver un champ large de 9 bits pour l'adapter à une seule page.
Cela laisse 23 bits. Laisser 2 pour le PDPT pour garder les choses uniformes avec le boîtier PAE sans PSE laisse 21 pour l'offset, ce qui signifie que les pages ont une largeur de 2M au lieu de 4M.
Le tampon de traduction Lookahead (TLB) est un cache pour les adresses de pagination.
Puisqu'il s'agit d'un cache, il partage bon nombre des problèmes de conception du cache du processeur, tels que le niveau d'associativité.
Cette section doit décrire un TLB entièrement associatif simplifié avec 4 entrées d'adresse unique. Notez que comme les autres caches, les vrais TLB ne sont généralement pas entièrement associatifs.
Après une traduction entre une adresse linéaire et physique, elle est stockée sur le TLB. Par exemple, un TLB à 4 entrées démarre dans l'état suivant:
valid linear physical
------ ------- ---------
> 0 00000 00000
0 00000 00000
0 00000 00000
0 00000 00000
Le >
indique l'entrée actuelle à remplacer.
et après une adresse linéaire de page 00003
est traduit en une adresse physique 00005
, le TLB devient:
valid linear physical
------ ------- ---------
1 00003 00005
> 0 00000 00000
0 00000 00000
0 00000 00000
et après une deuxième traduction de 00007
à 00009
il devient:
valid linear physical
------ ------- ---------
1 00003 00005
1 00007 00009
> 0 00000 00000
0 00000 00000
Maintenant si 00003
doit être traduit à nouveau, le matériel recherche d'abord le TLB et découvre son adresse avec un seul RAM accès 00003 --> 00005
.
Bien sûr, 00000
n'est pas sur le TLB car aucune entrée valide ne contient 00000
comme clé.
Lorsque TLB est rempli, les anciennes adresses sont remplacées. Tout comme pour le cache CPU, la politique de remplacement est une opération potentiellement complexe, mais une heuristique simple et raisonnable consiste à supprimer l'entrée la moins récemment utilisée (LRU).
Avec LRU, à partir de l'état:
valid linear physical
------ ------- ---------
> 1 00003 00005
1 00007 00009
1 00009 00001
1 0000B 00003
ajouter 0000D -> 0000A
donnerait:
valid linear physical
------ ------- ---------
1 0000D 0000A
> 1 00007 00009
1 00009 00001
1 0000B 00003
L'utilisation du TLB accélère la traduction, car la traduction initiale prend un accès par niveau TLB , ce qui signifie 2 sur un schéma 32 bits simple, mais 3 ou 4 sur des architectures 64 bits.
Le TLB est généralement implémenté comme un type coûteux de RAM appelée mémoire adressable au contenu (CAM). CAM implémente une carte associative sur le matériel, c'est-à-dire une structure qui donne une clé (adresse linéaire) , récupère une valeur.
Les mappages peuvent également être implémentés sur les adresses RAM, mais les mappages CAM peuvent nécessiter beaucoup moins d'entrées qu'un mappage RAM.
Par exemple, une carte dans laquelle:
pourrait être stocké dans un TLB avec 4 entrées:
linear physical
------- ---------
00000 00001
00001 00010
00010 00011
FFFFF 00000
Cependant, pour l'implémenter avec de la RAM, il faudrait avoir 2 ^ 20 adresses :
linear physical
------- ---------
00000 00001
00001 00010
00010 00011
... (from 00011 to FFFFE)
FFFFF 00000
ce qui serait encore plus cher que d'utiliser un TLB.
Quand cr3
change, toutes les entrées TLB sont invalidées, car une nouvelle table de pages pour un nouveau processus va être utilisée, il est donc peu probable que l'une des anciennes entrées ait une signification.
Le x86 propose également l'instruction invlpg
qui invalide explicitement une seule entrée TLB. D'autres architectures offrent encore plus d'instructions aux entrées TLB invalidées, comme invalider toutes les entrées sur une plage donnée.
Certains processeurs x86 vont au-delà des exigences de la spécification x86 et fournissent plus de cohérence qu'elle ne garantit, entre la modification d'une entrée de table de pages et son utilisation, alors qu'elle n'était pas déjà mise en cache dans le TLB . Apparemment, Windows 9x s'appuyait sur cela pour l'exactitude, mais les processeurs AMD modernes ne fournissent pas de parcours de page cohérents. Les processeurs Intel le font, même s'ils doivent détecter des spéculations erronées pour ce faire. Profiter de cela est probablement une mauvaise idée, car il n'y a probablement pas grand-chose à gagner et un gros risque de causer de subtils problèmes de synchronisation qui seront difficiles à déboguer.
Le noyau Linux utilise largement les fonctions de pagination de x86 pour permettre des commutations de processus rapides avec une petite fragmentation des données.
Dans v4.2
, regarder sous Arch/x86/
:
include/asm/pgtable*
include/asm/page*
mm/pgtable*
mm/page*
Il ne semble pas y avoir de structure définie pour représenter les pages, seulement des macros: include/asm/page_types.h
est particulièrement intéressant. Extrait:
#define _PAGE_BIT_PRESENT 0 /* is present */
#define _PAGE_BIT_RW 1 /* writeable */
#define _PAGE_BIT_USER 2 /* userspace addressable */
#define _PAGE_BIT_PWT 3 /* page write through */
Arch/x86/include/uapi/asm/processor-flags.h
définit CR0
, et en particulier la position du bit PG
:
#define X86_CR0_PG_BIT 31 /* Paging */
Libre:
rutgers-pxk-416 chapitre "Gestion de la mémoire: notes de cours"
Bon examen historique des techniques d'organisation de la mémoire utilisées par les anciens systèmes d'exploitation.
Non libre:
bovet05 chapitre "Adressage mémoire"
Introduction raisonnable à l'adressage de la mémoire x86. Manque quelques bons et simples exemples.
Voici une réponse très courte et de haut niveau:
Un processeur x86 fonctionne dans l'un des nombreux modes possibles (en gros: réel, protégé, 64 bits). Chaque mode peut utiliser l'un des nombreux modèles d'adressage de mémoire possibles (mais tous les modes ne peuvent pas utiliser tous les modèles), à savoir: l'adressage en mode réel, l'adressage segmenté et l'adressage linéaire linéaire.
Dans le monde moderne, seul l'adressage linéaire linéaire en mode protégé ou 64 bits est pertinent, et les deux modes sont essentiellement les mêmes, la principale différence étant la taille du mot machine et donc la quantité de mémoire adressable.
Maintenant, le mode d'adressage de la mémoire donne un sens aux opérandes de la mémoire des instructions de la machine (comme mov DWORD PTR [eax], 25
, qui stocke un entier 32 bits (alias dword
) de valeur 25 dans la mémoire dont l'adresse est stockée dans le registre eax
32 bits). Dans l'adressage linéaire linéaire, ce nombre dans eax
est autorisé à s'exécuter sur une seule plage contiguë, de zéro à la valeur maximale (dans notre cas, c'est 232- 1).
Cependant, l'adressage linéaire linéaire peut être soit paginé ou non paginé. Sans pagination, l'adresse fait directement référence à la mémoire physique. Avec paging, l'unité de gestion de la mémoire du processeur (ou MMU) alimente de manière transparente l'adresse souhaitée (maintenant appelée adresse virtuelle) dans un mécanisme de recherche, le soi-disant tableaux de pages, et obtient une nouvelle valeur, qui est interprétée comme une adresse physique. L'opération d'origine fonctionne désormais sur cette nouvelle adresse traduite dans la mémoire physique, même si l'utilisateur ne voit que l'adresse virtuelle.
Le principal avantage de la pagination est que les tables de pages sont gérées par le système d'exploitation. Ainsi, le système d'exploitation peut modifier et remplacer les tables de pages de manière arbitraire, comme lors de la "commutation de tâches". Il peut conserver toute une collection de tables de pages, une pour chaque "processus", et chaque fois qu'il décide qu'un processus particulier va s'exécuter sur un processeur donné, il charge les tables de pages du processus dans ce processeur MMU (chaque CPU possède son propre ensemble de tables de pages). Le résultat est que chaque processus voit son propre espace d'adressage virtuel qui a la même apparence quelles que soient les pages physiques libres lorsque le système d'exploitation devait lui allouer de la mémoire. Il ne connaît jamais la mémoire d'aucun autre processus, car il ne peut pas accéder directement à la mémoire physique.
Les tables de pages sont des structures de données arborescentes imbriquées stockées dans la mémoire normale, écrites par le système d'exploitation mais lues directement par le matériel, de sorte que le format est fixe. Ils sont "chargés" dans le MMU en définissant un registre de contrôle CPU spécial pour pointer vers la table de niveau supérieur. Le CPU utilise un cache appelé TLB pour se souvenir des recherches, donc des accès répétés à les mêmes pages sont beaucoup plus rapides que les accès dispersés, pour des raisons de manque de TLB ainsi que pour les raisons habituelles de cache de données. Il est courant de voir le terme "entrée TLB" utilisé pour faire référence aux entrées de table de pages même lorsqu'elles ne sont pas mises en cache dans le TLB.
Et dans le cas où vous craignez qu'un processus puisse simplement désactiver la pagination ou essayer de modifier les tables de pages: cela n'est pas autorisé, car x86 implémente niveaux de privilège (appelés "anneaux"), et le code utilisateur s'exécute à un niveau de privilège trop bas pour lui permettre de modifier les tables de pages du processeur.