106 votes

Comment fonctionne la pagination sur x86 ?

Cette question vise à combler le manque d'informations gratuites de qualité sur le sujet.

Je pense qu'une bonne réponse s'inscrira dans une grande réponse à l'OS 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 capables de comprendre les concepts de base du système d'exploitation liés à la pagination.

Directives suggérées :

  • Les réponses doivent être adaptées aux débutants :
    • des exemples concrets, mais éventuellement simplifiés, sont très importants
    • les applications des concepts présentés sont les bienvenues
  • citer des ressources utiles, c'est bien
  • les petites digressions sur la façon dont les systèmes d'exploitation utilisent les fonctions de pagination sont les bienvenues.
  • Les explications de PAE et PSE sont les bienvenues
  • les petites digressions sur x86_64 sont les bienvenues

Questions connexes et pourquoi je pense qu'il ne s'agit pas de doublons :

157voto

Ciro Santilli Points 3341

Version de cette réponse avec un beau TOC et plus de contenu .

Je corrigerai toute erreur signalée. Si vous voulez faire des modifications importantes ou ajouter un aspect manquant, faites-les sur vos propres réponses pour obtenir une réputation bien méritée. Les modifications mineures peuvent être fusionnées directement.

Exemple de code

Exemple minimal : https://github.com/cirosantilli/x86-bare-metal-examples/blob/5c672f73884a487414b3e21bd9e579c67cd77621/paging.S

Comme pour tout ce qui concerne la programmation, la seule façon de vraiment comprendre est de jouer avec des exemples minimaux.

Ce qui rend ce sujet "difficile", c'est que l'exemple minimal est vaste car vous devez créer votre propre petit OS.

Manuel Intel

Bien qu'il soit impossible de comprendre sans avoir d'exemples en tête, essayez de vous familiariser avec les manuels dès que possible.

Intel décrit la pagination dans le Manuel Intel Volume 3 Guide de programmation du système - 325384-056US Septembre 2015 Chapitre 4 "Paging".

La Figure 4-4 "Formats des entrées CR3 et Paging-Structure avec pagination 32 bits" est particulièrement intéressante, car elle donne les principales structures de données.

MMU

La pagination est effectuée par le Unité de gestion de la mémoire (MMU) de l'unité centrale. Comme beaucoup d'autres (par ex. co-processeur x87 , APIC ), il s'agissait au début d'une puce séparée, qui a ensuite été intégrée à l'unité centrale. Mais le terme est toujours utilisé.

Faits généraux

Les adresses logiques sont les adresses de mémoire utilisées dans le code "normal" de l'utilisateur (par exemple, le contenu de l'écran de l'ordinateur). rsi en mov eax, [rsi] ).

La segmentation les traduit d'abord 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 penser que les adresses physiques indexent les cellules de mémoire matérielle RAM réelles, mais ce n'est pas vrai à 100% à cause de :

La radiomessagerie n'est disponible qu'en mode protégé. L'utilisation de la radiomessagerie en mode protégé est facultative. La pagination est activée si le PG de la cr0 est activé.

Radiomessagerie et segmentation

L'une des principales différences entre la pagination et la segmentation est la suivante :

  • La pagination divise la RAM en morceaux de taille égale appelés pages.
  • La segmentation divise la mémoire en morceaux de taille arbitraire.

C'est le principal avantage de la pagination, puisque des morceaux de taille égale rendent les choses plus faciles à gérer.

La pagination a tellement gagné en popularité 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.

Application

La pagination est utilisée pour mettre en œuvre les espaces d'adressage virtuels des processus sur les systèmes d'exploitation modernes. Grâce aux adresses virtuelles, le système d'exploitation peut faire tenir deux ou plusieurs processus simultanés sur une seule mémoire vive :

  • les deux programmes ne doivent rien savoir de l'autre
  • la mémoire des deux programmes peut augmenter et diminuer selon les besoins.
  • le passage d'un programme à l'autre est très rapide
  • un programme ne peut jamais accéder à la mémoire d'un autre processus

Historiquement, la pagination est venue après la segmentation, et l'a largement remplacée pour la mise en œuvre de la mémoire virtuelle dans les systèmes d'exploitation modernes tels que Linux, car il est plus facile de gérer les morceaux de mémoire de taille fixe que sont les pages que les segments de longueur variable.

Mise en œuvre matérielle

Comme la segmentation en mode protégé (où la modification d'un registre de segment déclenche un chargement depuis le GDT ou le LDT), le matériel de pagination utilise des structures de données en mémoire pour faire son travail (tables de pages, répertoires de pages, etc.).

Le format de ces structures de données est fixe par le matériel mais c'est au système d'exploitation de mettre en place et de gérer correctement ces structures de données dans la mémoire vive, et d'indiquer au matériel où les trouver (par l'intermédiaire de la fonction cr3 ).

Certaines autres architectures laissent la pagination presque entièrement entre les mains du logiciel, de sorte qu'un défaut de 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 tables de pages à la discrétion du système d'exploitation, mais rend la fonction de pagination plus efficace. Il est peu probable que le matériel puisse chevaucher les passages de page avec l'exécution hors ordre d'autres instructions, comme le fait le x86. .

Exemple : schéma de pagination simplifié à un seul niveau

Ceci est un exemple de la façon dont la pagination fonctionne sur une simplifié version de l'architecture x86 pour mettre en œuvre un espace mémoire virtuel.

Tableaux de pages

L'OS pourrait leur donner les tableaux de pages suivants :

Table des pages donnée 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

Table des pages donnée 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 y PT2 Position initiale des tables 1 et 2 sur la RAM.

    Valeurs de l'échantillon : 0x00000000 , 0x12345678 etc.

    C'est le système d'exploitation qui décide de ces valeurs.

  • L Longueur d'une entrée de la table des pages.

  • present : indique que la page est présente en mémoire.

Les tables de pages sont situées dans la RAM. Elles peuvent par exemple être situées comme suit :

--------------> 0xFFFFFFFF

--------------> PT1 + 0xFFFFF * L
Page Table 1
--------------> PT1

--------------> PT2 + 0xFFFFF * L
Page Table 2
--------------> PT2

--------------> 0x0

Les emplacements initiaux de la RAM pour les deux tables de pages sont arbitraires et contrôlés par le système d'exploitation. C'est au système d'exploitation de s'assurer qu'elles ne se chevauchent pas !

Chaque processus ne peut pas toucher directement aux tables de pages, bien qu'il puisse faire des demandes au système d'exploitation qui entraînent la modification des tables de pages, par exemple en demandant des segments de pile ou de tas plus grands.

Une page est un morceau de 4KB (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.

Entrées de la table des pages

Une table de pages est... une table d'entrées de tables de pages !

Le format exact des entrées de la table est fixé par le matériel .

Dans cet exemple simplifié, les entrées de la table des pages ne contiennent que deux champs :

bits   function
-----  -----------------------------------------
20     physical address of the start of the page
1      present flag

donc dans cet exemple, les concepteurs du matériel auraient pu choisir L = 21 .

La plupart des entrées de la table des pages réelles comportent d'autres champs.

Il ne serait pas pratique d'aligner les choses à 21 bits puisque la mémoire est adressable par octets et non par bits. Par conséquent, même si seulement 21 bits sont 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 restants pour une utilisation ultérieure. La valeur réelle de L sur x86 est de 32 bits.

Traduction d'adresses dans un schéma à un seul niveau

Une fois que les tables de pages ont été mises en place par le système d'exploitation, la traduction des adresses entre les adresses linéaires et physiques est effectuée. par le matériel .

Lorsque le système d'exploitation veut activer le processus 1, il définit le paramètre cr3 a PT1 le début de la table pour le processus 1.

Si le processus 1 veut accéder à l'adresse linéaire 0x00000001 le paging matériel informatique Le circuit 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 :

    • page = 0x00000
    • décalage = 0x001
  • regardez le tableau 1 de la page car cr3 l'indique.

  • entrée de look 0x00000 parce que c'est la partie de la page.

    Le matériel sait que cette entrée est située à l'adresse RAM PT1 + 0 * L = PT1 .

  • puisqu'il est présent, l'accès est valide

  • par la table 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

    parce que 00001 est l'adresse physique de la page recherchée dans la table et 001 est le décalage.

    Comme son nom l'indique, le décalage 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 dans la table des pages est située à l'adresse RAM : PT1 + 1 * L ( 1 à cause de la partie page), et c'est là qu'il va le chercher.

Quand le système d'exploitation veut passer au processus 2, il lui suffit de faire cr3 point à 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 des adresses physiques différentes pour des processus différents. qui dépend uniquement de la valeur à l'intérieur de cr3 .

De cette façon, chaque programme peut s'attendre à ce que ses données débutent à 0 et se terminent à FFFFFFFF sans se soucier des adresses physiques exactes.

Défaut de page

Que se passe-t-il si le processus 1 essaie d'accéder à une adresse dans une page qui n'existe pas ?

Le matériel informe le logiciel par une exception de défaut de page.

C'est alors généralement au système d'exploitation d'enregistrer un gestionnaire d'exception pour décider de ce qu'il faut faire.

Il est possible que l'accès à une page qui ne figure pas dans le tableau soit une erreur de programmation :

int is[1];
is[2] = 1;

mais il peut y avoir des cas dans lesquels il est acceptable, par exemple dans 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é transférée sur le disque.

    Le système d'exploitation devra effectuer un certain travail derrière les processus pour ramener la page dans la RAM.

    Le système d'exploitation peut découvrir que c'est le cas en se basant sur le contenu du reste de l'entrée de la table des pages, puisque si l'indicateur present est clair, les autres entrées de l'entrée de la table des pages sont entièrement laissées à la discrétion du système d'exploitation.

    Sous Linux par exemple, lorsque présent = 0 :

    • si tous les champs de l'entrée de la table des pages sont à 0, adresse invalide.

    • Sinon, la page a été transféré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 doit savoir quelle adresse a généré le défaut de page pour pouvoir traiter le problème. C'est pourquoi les gentils développeurs de IA32 ont fixé la valeur de cr2 à cette adresse chaque fois qu'un défaut de page se produit. Le gestionnaire d'exception peut alors simplement regarder dans cr2 pour obtenir l'adresse.

Simplifications

Simplifications de la réalité qui rendent cet exemple plus facile à comprendre :

  • Tous les circuits de pagination réels utilisent la pagination multi-niveaux pour économiser de l'espace, mais ceci a montré un schéma simple à un seul niveau.

  • Les tableaux de pages ne contenaient que deux champs : une adresse de 20 bits et un drapeau de présence de 1 bit.

    Les tableaux de pages réelles contiennent un total de 12 champs, et donc d'autres caractéristiques qui ont été omises.

Exemple : système de pagination à plusieurs niveaux

Le problème avec un système de pagination à un seul niveau est qu'il prendrait trop de RAM : 4G / 4K = 1M d'entrées. par processus. Si chaque entrée a une longueur de 4 octets, cela ferait 4M par processus ce qui est trop, même pour un ordinateur de bureau : ps -A | wc -l indique que j'exécute 244 processus en ce moment, ce qui représente environ 1 Go de ma RAM !

Pour cette raison, les développeurs de x86 ont décidé d'utiliser un schéma à plusieurs niveaux qui réduit l'utilisation de la 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 répartis comme suit :

| directory (10 bits) | table (10 bits) | offset (12 bits) |

Chaque processus doit avoir un et un seul répertoire de pages associé à lui, il contiendra donc au moins 2^10 = 1K pages de répertoire, ce qui est bien mieux que le minimum de 1M requis sur un schéma à un seul niveau.

Les tables de pages ne sont allouées que si le système d'exploitation en a besoin. 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 du répertoire des pages sont identiques aux entrées de la table des pages, à l'exception des éléments suivants ils pointent vers les adresses RAM des tables de pages au lieu des adresses physiques des tables . Comme ces adresses n'ont qu'une largeur de 20 bits, les tables de pages doivent se trouver au début des pages de 4KB.

cr3 pointe maintenant vers l'emplacement en RAM du répertoire de pages du processus actuel au lieu des tables de pages.

Les entrées des tables de pages ne changent pas du tout par rapport à un schéma à un seul niveau.

Les tables de pages changent d'un schéma à un seul niveau parce que :

  • chaque processus peut avoir jusqu'à 1K tables de pages, une par entrée de répertoire de pages.
  • chaque table de pages contient exactement 1K entrées au lieu de 1M entrées.

La raison de l'utilisation de 10 bits sur les deux premiers niveaux (et non pas, disons, de 10 bits sur les deux premiers niveaux) est la suivante 12 | 8 | 12 ) est que chaque entrée de la table des pages a une longueur de 4 octets. Alors les 2^10 entrées des répertoires de pages et des tables de pages s'adapteront parfaitement aux pages de 4Kb. Cela signifie qu'il est plus rapide et plus simple d'allouer et de désallouer des pages dans ce but.

Traduction d'adresses dans un système à plusieurs niveaux

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

Tableaux de pages donnés 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

Tableaux de pages donnés 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 en RAM.
  • PT1 y PT2 Position initiale de la table de pages 1 et de la table de pages 2 pour le processus 1 sur la RAM.

Ainsi, dans cet exemple, le répertoire des pages et la table des pages pourraient être stockés dans la RAM de la manière suivante :

----------------> 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 étape par étape.

Nous supposons que cr3 = PD1 c'est-à-dire qu'il pointe vers le répertoire de pages que nous venons de décrire.

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

ce qui donne :

  • entrée du répertoire des pages = 0x2
  • entrée de la table des pages = 0x1
  • décalage = 0x4

Le matériel cherche donc l'entrée 2 du répertoire de la page.

La table du répertoire des pages indique que la table des pages se trouve à l'adresse suivante 0x80000 * 4K = 0x80000000 . Il s'agit du premier accès à la RAM du processus.

Puisque l'entrée de la table des pages est 0x1 le matériel regarde l'entrée 1 de la table des pages à 0x80000000 ce qui lui indique que la page physique est située à l'adresse 0x0000C * 4K = 0x0000C000 . Il s'agit du deuxième accès à la RAM du processus.

Enfin, le matériel de pagination ajoute le décalage, 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

Les défauts de page se produisent si une entrée de répertoire de page ou une entrée de table de page n'est pas présente.

Si le système d'exploitation veut exécuter un autre processus simultanément, il doit donner au second processus un répertoire de pages distinct et lier ce répertoire à des tables de pages distinctes.

Architectures 64 bits

64 bits, c'est encore trop d'adresse pour les tailles actuelles de RAM, donc la plupart des architectures utiliseront moins de bits.

x86_64 utilise 48 bits (256 TiB), et le PAE du mode legacy autorise déjà les adresses de 52 bits (4 PiB).

12 de ces 48 bits sont déjà réservés pour l'offset, ce qui laisse 36 bits.

Si l'on adopte une approche à deux niveaux, la meilleure répartition serait de deux niveaux de 18 bits.

Mais cela voudrait dire que le répertoire des pages 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 des niveaux de page supplémentaires, généralement 3 ou 4.

x86_64 utilise 4 niveaux dans un 9 | 9 | 9 | 12 de manière à ce que le niveau supérieur ne prenne que 2^9 les entrées de niveau supérieur.

PAE

Extension de l'adresse physique.

Avec 32 bits, on ne peut adresser que 4 Go de RAM.

Cela commençait à devenir une limitation pour les grands serveurs, c'est pourquoi Intel a introduit le mécanisme PAE dans le Pentium Pro.

Pour remédier à ce problème, Intel a ajouté 4 nouvelles lignes d'adresse, de sorte que 64 Go puissent être adressés.

La structure de la table des pages est également modifiée si le PAE est activé. La manière exacte dont elle est modifiée dépend de l'activation ou de la désactivation de PSE.

Le PAE est activé et désactivé via le bouton PAE un peu de cr4 .

Même si la mémoire adressable totale est de 64 Go, les processus individuels ne peuvent utiliser que 4 Go au maximum. Le système d'exploitation peut cependant placer différents processus sur différents morceaux de 4 Go.

PSE

Extension de la taille de la page.

Permet aux pages d'avoir une longueur de 4M (ou 2M si PAE est activé) au lieu de 4K.

Le PSE est activé et désactivé par l'intermédiaire du PSE un peu de cr4 .

Schémas de table de pages PAE et PSE

Si le PAE et le PSE sont actifs, différents schémas de niveaux de paging sont utilisés :

  • pas de PAE et pas de PSE : 10 | 10 | 12

  • pas de PAE et PSE : 10 | 22 .

    22 est le décalage dans la page de 4Mb, puisque 22 bits adressent 4Mb.

  • PAE et pas de PSE : 2 | 9 | 9 | 12

    La raison pour laquelle le chiffre 9 est utilisé deux fois au lieu de 10 est que les entrées ne peuvent plus tenir sur 32 bits, qui étaient tous 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 maintenant nécessaires à cause 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 pour les faire tenir dans une seule page de table, il est nécessaire de réduire le nombre d'entrées à 2^9 au lieu de 2^10.

    Le point de départ 2 est un nouveau niveau de Page appelé Page Directory Pointer Table (PDPT), car il points pour paginer les répertoires et remplir l'adresse linéaire de 32 bits. Les PDPT ont également une largeur de 64 bits.

    cr3 pointe maintenant vers les PDPTs qui doivent être sur les quatre premiers 4GB 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 4GB.

  • PAE et PSE : 2 | 9 | 21

    Les concepteurs ont décidé de conserver un champ de 9 bits de large pour qu'il tienne sur une seule page.

    Il reste donc 23 bits. En laissant 2 pour le PDPT pour garder les choses uniformes avec le cas PAE sans PSE, il reste 21 pour le décalage, ce qui signifie que les pages ont une largeur de 2M au lieu de 4M.

TLB

Le Translation Lookahead Buffer (TLB) est un cache pour les adresses de pagination.

Puisqu'il s'agit d'un cache, il partage de nombreux problèmes de conception du cache du CPU, comme le niveau d'associativité.

Cette section décrit une TLB simplifiée entièrement associative avec 4 entrées à adresse unique. Notez que, comme les autres caches, les TLB réelles ne sont généralement pas totalement associatives.

Fonctionnement de base

Après une traduction entre l'adresse linéaire et l'adresse physique, celle-ci est stockée dans la TLB. Par exemple, une TLB à 4 entrées commence dans l'état suivant :

  valid   linear   physical
  ------  -------  ---------
> 0       00000    00000
  0       00000    00000
  0       00000    00000
  0       00000    00000

El > indique l'entrée courante à 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 a 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 consulte d'abord la TLB et trouve son adresse en un seul accès à la RAM. 00003 --> 00005 .

Bien sûr, 00000 n'est pas sur la TLB puisqu'aucune entrée valide ne contient 00000 comme une clé.

Politique de remplacement

Lorsque la TLB est remplie, les anciennes adresses sont écrasées. Comme pour le cache du 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, en partant de l'état :

  valid   linear   physical
  ------  -------  ---------
> 1       00003    00005
  1       00007    00009
  1       00009    00001
  1       0000B    00003

en ajoutant 0000D -> 0000A donnerait :

  valid   linear   physical
  ------  -------  ---------
  1       0000D    0000A
> 1       00007    00009
  1       00009    00001
  1       0000B    00003

CAM

L'utilisation de la TLB accélère la traduction, car la traduction initiale nécessite un seul accès. par niveau de TLB ce qui signifie 2 sur un simple schéma 32 bits, mais 3 ou 4 sur les architectures 64 bits.

La TLB est généralement implémentée comme un type de RAM coûteux appelé mémoire adressable par le contenu (CAM). La CAM implémente une carte associative sur le matériel, c'est-à-dire une structure qui, à partir d'une clé (adresse linéaire), récupère une valeur.

Les mappings peuvent également être implémentés sur des adresses RAM, mais les mappings CAM peuvent nécessiter beaucoup moins d'entrées qu'un mappage RAM.

Par exemple, une carte dans laquelle :

  • les clés et les valeurs ont toutes deux 20 bits (cas d'un système de pagination simple).
  • il faut stocker au maximum 4 valeurs à chaque instant

pourrait être stocké dans une TLB avec 4 entrées :

linear   physical
-------  ---------
00000    00001
00001    00010
00010    00011
FFFFF    00000

Cependant, pour mettre en œuvre ce système avec la RAM, il serait nécessaire d'avoir 2^20 adresses :

linear   physical
-------  ---------
00000    00001
00001    00010
00010    00011
... (from 00011 to FFFFE)
FFFFF    00000

ce qui serait encore plus coûteux que d'utiliser une TLB.

Invalidation des entrées

Lorsque cr3 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 les anciennes entrées aient une quelconque signification.

Le x86 offre également le invlpg qui invalide explicitement une seule entrée TLB. D'autres architectures offrent encore plus d'instructions pour invalider les entrées TLB, comme l'invalidation de 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 le garantit, entre la modification d'une entrée de la table des pages et son utilisation, quand elle n'était pas déjà en cache dans la TLB. . Apparemment, Windows 9x s'appuyait sur ce principe pour être correct, mais les processeurs AMD modernes ne permettent pas d'assurer une marche cohérente des pages. Les CPU Intel le font, même s'ils doivent détecter les erreurs de spéculation pour y parvenir. Tirer profit de cette situation est probablement une mauvaise idée, car il n'y a probablement pas grand-chose à gagner, et un grand risque de causer de subtils problèmes de synchronisation qui seront difficiles à déboguer.

Utilisation du noyau Linux

Le noyau Linux fait un usage intensif des fonctions de pagination de x86 pour permettre des changements de processus rapides avec une faible fragmentation des données.

Sur v4.2 regardez sous arch/x86/ :

  • include/asm/pgtable*
  • include/asm/page*
  • mm/pgtable*
  • mm/page*

Il ne semble pas y avoir de structures définies pour représenter les pages, seulement des macros : include/asm/page_types.h est particulièrement intéressante. 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, le PG position du bit :

#define X86_CR0_PG_BIT      31 /* Paging */

Bibliographie

Gratuit :

  • rutgers-pxk-416 Chapitre "Gestion de la mémoire : notes de cours".

    Bonne revue historique des techniques d'organisation de la mémoire utilisées par les anciens systèmes d'exploitation.

Non libre :

  • bovet05 chapitre "Adressage de la mémoire"

    Introduction raisonnable à l'adressage mémoire x86. Il manque quelques bons exemples simples.

26voto

Kerrek SB Points 194696

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 différents modèles d'adressage de la mémoire (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 plat.

Dans le monde moderne, seul l'adressage linéaire plat 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.

Or, le mode d'adressage mémoire donne une signification aux opérandes mémoire des instructions machine (telles que mov DWORD PTR [eax], 25 qui stocke une valeur de 32 bits (alias dword ) entier de valeur 25 dans la mémoire dont l'adresse est stockée dans le fichier eax registre de 32 bits). Dans l'adressage linéaire plat, ce nombre en eax est autorisé à s'étendre sur une seule plage contiguë, de zéro à la valeur maximale (dans notre cas, c'est 2 32  - 1).

Cependant, l'adressage linéaire plat peut être soit paginé o non paginé . Sans pagination, l'adresse fait directement référence à la mémoire physique. Avec la pagination, l'unité de gestion de la mémoire du processeur (ou MMU) alimente de manière transparente l'adresse souhaitée (désormais appelée une adresse virtuelle ) dans un mécanisme de recherche, appelé tableaux des pages et obtient une nouvelle valeur, qui est interprétée comme une adresse physique. L'opération originale fonctionne maintenant sur cette nouvelle adresse traduite dans la mémoire physique, même si l'utilisateur ne voit jamais 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 façon arbitraire, par exemple lorsqu'il "change de tâche". 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 CPU donné, il charge les tables de pages du processus dans le MMU de ce CPU (chaque CPU a son propre ensemble de tables de pages). Le résultat est que chaque processus voit son propre virtuel un espace d'adressage qui a la même apparence, quelles que soient les pages physiques qui étaient libres lorsque le système d'exploitation a dû lui allouer de la mémoire. Il ne connaît jamais la mémoire d'un autre processus, puisqu'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. Elles sont "chargées" dans la MMU en réglant un registre de contrôle spécial du CPU pour qu'il pointe sur la table de niveau supérieur. Le CPU utilise un cache appelé TLB pour mémoriser les recherches, de sorte que les accès répétés aux mêmes quelques pages sont beaucoup plus rapides que les accès dispersés, pour des raisons de TLB-miss 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 la table des pages, même si elles ne sont pas mises en cache dans la TLB.

Et au cas où vous vous inquiéteriez qu'un processus puisse simplement désactiver la pagination ou essayer de modifier les tables de pages : Ceci n'est pas autorisé, puisque x86 implémente niveaux de privilèges (appelés "anneaux"), et le code utilisateur s'exécute à un niveau de privilège trop faible pour lui permettre de modifier les tables de pages du processeur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X