Come funziona il paging x86?


92

Questa domanda ha lo scopo di riempire il vuoto di buone informazioni gratuite sull'argomento.

Credo che una buona risposta si inserirà in una grande risposta SO o almeno in alcune risposte.

L'obiettivo principale è fornire ai principianti assoluti informazioni sufficienti in modo che possano prendere il manuale da soli ed essere in grado di comprendere i concetti di base del sistema operativo relativi al paging.

Linee guida suggerite:

  • le risposte dovrebbero essere adatte ai principianti:
    • esempi concreti, ma forse semplificati, sono molto importanti
    • le applicazioni dei concetti mostrati sono benvenute
  • citare risorse utili è positivo
  • sono benvenute piccole divagazioni su come i sistemi operativi utilizzano le funzionalità di paging
  • Le spiegazioni PAE e PSE sono benvenute
  • sono benvenute piccole digressioni in x86_64

Domande correlate e perché penso che non siano stupidi:


1
Dovrebbe essere etichettato "faq" e contrassegnato come "community-wiki".
Kerrek SB

@KerrekSB Non so davvero come gestire questo tipo di domande. Le risposte dovrebbero essere wiki della comunità, è così? Non sono riuscito a trovare un faqtag.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

3
Direi che la risposta breve è "leggi Vol 3, Capitolo 4: Paging nel manuale Intel". È abbastanza chiaro, conciso e ben scritto, e non diventa più autorevole.
Kerrek SB

4
@KerrekSB Sono d'accordo sul fatto che il manuale sia chiaro e autorevole, ma è stato un po 'troppo duro come prima lettura per me, avevo bisogno di alcuni esempi semplici e concreti + motivazioni per capire meglio le cose.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Risposte:


144

Versione di questa risposta con un bel sommario e più contenuti .

Correggerò qualsiasi errore segnalato. Se vuoi apportare modifiche di grandi dimensioni o aggiungere un aspetto mancante, fallo in base alle tue risposte per ottenere una meritata reputazione. Le modifiche minori possono essere unite direttamente in.

Codice d'esempio

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

Come ogni altra cosa nella programmazione, l'unico modo per capirlo veramente è giocare con esempi minimi.

Ciò che rende questo argomento "difficile" è che l'esempio minimo è grande perché è necessario creare il proprio piccolo sistema operativo.

Manuale Intel

Sebbene sia impossibile capire senza esempi in mente, cerca di familiarizzare con i manuali il prima possibile.

Intel descrive il paging nel manuale Intel Volume 3 System Programming Guide - 325384-056US Settembre 2015 Capitolo 4 "Paging".

Particolarmente interessante è la Figura 4-4 "Formati di CR3 e voci della struttura di paging con paging a 32 bit", che fornisce le strutture dati chiave.

MMU

Il paging viene eseguito dalla parte MMU ( Memory Management Unit ) della CPU. Come molti altri (ad es. Co-processore x87 , APIC ), all'inizio questo era un chip separato, che è stato successivamente integrato nella CPU. Ma il termine è ancora usato.

Fatti generali

Gli indirizzi logici sono gli indirizzi di memoria usati nel "normale" codice user-land (ad esempio il contenuto di rsiin mov eax, [rsi]).

La prima segmentazione li traduce in indirizzi lineari, quindi il paging traduce gli indirizzi lineari in indirizzi fisici.

(logical) ------------------> (linear) ------------> (physical)
             segmentation                 paging

Il più delle volte, possiamo pensare agli indirizzi fisici come indicizzare le celle di memoria hardware RAM effettive, ma questo non è vero al 100% a causa di:

Il paging è disponibile solo in modalità protetta. L'utilizzo del paging in modalità protetta è facoltativo. Il paging è attivo se e solo se il PGbit del cr0registro è impostato.

Paging vs segmentazione

Una delle principali differenze tra il paging e la segmentazione è che:

  • il paging divide la RAM in blocchi di uguale dimensione chiamati pagine
  • la segmentazione divide la memoria in blocchi di dimensioni arbitrarie

Questo è il vantaggio principale del paging, poiché blocchi di dimensioni uguali rendono le cose più gestibili.

Il paging è diventato molto più popolare che il supporto per la segmentazione è stato abbandonato in x86-64 in modalità 64 bit, la modalità operativa principale per il nuovo software, dove esiste solo in modalità compatibilità, che emula IA32.

Applicazione

Il paging viene utilizzato per implementare processi spazi di indirizzi virtuali su sistemi operativi moderni. Con gli indirizzi virtuali il sistema operativo può adattare due o più processi simultanei su una singola RAM in modo che:

  • entrambi i programmi non devono sapere nulla dell'altro
  • la memoria di entrambi i programmi può crescere e ridursi secondo necessità
  • il passaggio tra i programmi è molto veloce
  • un programma non può mai accedere alla memoria di un altro processo

Il paging storicamente è venuto dopo la segmentazione e in gran parte lo ha sostituito per l'implementazione della memoria virtuale nei sistemi operativi moderni come Linux poiché è più facile gestire i blocchi di memoria di dimensioni fisse delle pagine anziché i segmenti di lunghezza variabile.

Implementazione hardware

Come la segmentazione in modalità protetta (dove la modifica di un registro di segmento innesca un carico da GDT o LDT), l'hardware di paging utilizza strutture di dati in memoria per svolgere il proprio lavoro (tabelle di pagine, directory di pagine, ecc.).

Il formato di queste strutture dati è fissato dall'hardware , ma spetta al sistema operativo impostare e gestire correttamente quelle strutture dati sulla RAM e dire all'hardware dove trovarle (tramite cr3).

Alcune altre architetture lasciano il paging quasi completamente nelle mani del software, quindi un TLB mancato esegue una funzione fornita dal sistema operativo per esplorare le tabelle delle pagine e inserire la nuova mappatura nel TLB. Ciò lascia che i formati della tabella delle pagine siano scelti dal sistema operativo, ma rende improbabile che l'hardware sia in grado di sovrapporre le passeggiate di pagina con l'esecuzione fuori ordine di altre istruzioni, come può fare x86 .

Esempio: schema di paging a un livello semplificato

Questo è un esempio di come il paging opera su una versione semplificata dell'architettura x86 per implementare uno spazio di memoria virtuale.

Tabelle delle pagine

Il sistema operativo potrebbe fornire loro le seguenti tabelle di pagina:

Tabella delle pagine fornita al processo 1 dal sistema operativo:

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

Tabella delle pagine fornita al processo 2 dal sistema operativo:

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

Dove:

  • PT1e PT2: posizione iniziale della tabella 1 e 2 sulla RAM.

    Valori campione: 0x00000000, 0x12345678, etc.

    È il sistema operativo che decide quei valori.

  • L: lunghezza di una voce della tabella delle pagine.

  • present: indica che la pagina è presente in memoria.

Le tabelle delle pagine si trovano nella RAM. Potrebbero ad esempio essere localizzati come:

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


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


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

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

Le posizioni iniziali sulla RAM per entrambe le tabelle delle pagine sono arbitrarie e controllate dal sistema operativo. Spetta al sistema operativo assicurarsi che non si sovrappongano!

Ogni processo non può toccare direttamente le tabelle delle pagine, sebbene possa effettuare richieste al sistema operativo che causano la modifica delle tabelle delle pagine, ad esempio chiedendo segmenti di stack o heap più grandi.

Una pagina è un blocco di 4KB (12 bit) e poiché gli indirizzi hanno 32 bit, sono necessari solo 20 bit (20 + 12 = 32, quindi 5 caratteri in notazione esadecimale) per identificare ogni pagina. Questo valore è fissato dall'hardware.

Voci della tabella delle pagine

Una tabella delle pagine è ... una tabella delle voci della tabella delle pagine!

Il formato esatto delle voci della tabella è fissato dall'hardware .

In questo esempio semplificato, le voci della tabella delle pagine contengono solo due campi:

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

quindi in questo esempio i progettisti dell'hardware avrebbero potuto scegliere L = 21.

La maggior parte delle voci della tabella delle pagine reali ha altri campi.

Sarebbe poco pratico allineare le cose a 21 bit poiché la memoria è indirizzabile a byte e non a bit. Pertanto, anche in questo caso sono necessari solo 21 bit, i progettisti di hardware sceglierebbero probabilmente L = 32di rendere l'accesso più veloce e riserverebbero solo i bit rimanenti per un utilizzo successivo. Il valore effettivo per Lx86 è di 32 bit.

Indirizzi la traduzione in uno schema a livello singolo

Una volta che le tabelle delle pagine sono state impostate dal sistema operativo, la traduzione degli indirizzi tra indirizzi lineari e fisici viene eseguita dall'hardware .

Quando il sistema operativo vuole attivare il processo 1, imposta il cr3a PT1, l'inizio della tabella per il processo uno.

Se il Processo 1 desidera accedere all'indirizzo lineare 0x00000001, il circuito hardware di paging esegue automaticamente le seguenti operazioni per il sistema operativo:

  • dividere l'indirizzo lineare in due parti:

    | page (20 bits) | offset (12 bits) |
    

    Quindi in questo caso avremmo:

    • pagina = 0x00000
    • offset = 0x001
  • esaminare la tabella di pagina 1 perché cr3punta ad essa.

  • guarda la voce 0x00000perché quella è la parte della pagina.

    L'hardware sa che questa voce si trova all'indirizzo RAM PT1 + 0 * L = PT1.

  • essendo presente, l'accesso è valido

  • dalla tabella delle pagine, la posizione del numero di pagina 0x00000è in 0x00001 * 4K = 0x00001000.

  • per trovare l'indirizzo fisico finale dobbiamo solo aggiungere l'offset:

      00001 000
    + 00000 001
      -----------
      00001 001
    

    perché 00001è l'indirizzo fisico della pagina cercata sulla tabella ed 001è l'offset.

    Come indica il nome, l'offset viene sempre aggiunto semplicemente all'indirizzo fisico della pagina.

  • l'hardware quindi ottiene la memoria in quella posizione fisica.

Allo stesso modo, le seguenti traduzioni avverrebbero per il processo 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

Ad esempio, quando si accede all'indirizzo 00001000, la parte della pagina è che 00001l'hardware sa che la sua voce nella tabella delle pagine si trova all'indirizzo RAM: PT1 + 1 * L(a 1causa della parte della pagina), ed è lì che la cercherà.

Quando il sistema operativo desidera passare al processo 2, tutto ciò che deve fare è cr3indicare la pagina 2. È così semplice!

Ora le seguenti traduzioni avverrebbero per il processo 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

Lo stesso indirizzo lineare si traduce in indirizzi fisici diversi per processi diversi , a seconda solo del valore all'interno cr3.

In questo modo ogni programma può aspettarsi che i suoi dati inizino a 0 e finiscano FFFFFFFF, senza preoccuparsi degli indirizzi fisici esatti.

Errore di pagina

Cosa succede se il processo 1 tenta di accedere a un indirizzo all'interno di una pagina che non è presente?

L'hardware notifica il software tramite un'eccezione di errore di pagina.

Di solito spetta al sistema operativo registrare un gestore di eccezioni per decidere cosa fare.

È possibile che l'accesso a una pagina non presente nella tabella sia un errore di programmazione:

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

ma ci possono essere casi in cui è accettabile, ad esempio in Linux quando:

  • il programma vuole aumentare il suo stack.

    Cerca solo di accedere a un determinato byte in un dato intervallo possibile e, se il sistema operativo è soddisfatto, aggiunge quella pagina allo spazio degli indirizzi del processo.

  • la pagina è stata scambiata su disco.

    Il sistema operativo dovrà eseguire del lavoro dietro i processi per riportare la pagina nella RAM.

    Il sistema operativo può scoprire che questo è il caso in base al contenuto del resto della voce della tabella delle pagine, poiché se il flag presente è chiaro, le altre voci della voce della tabella delle pagine sono completamente lasciate per il sistema operativo a ciò che desidera.

    Su Linux, ad esempio, se presente = 0:

    • se tutti i campi della voce della tabella delle pagine sono 0, indirizzo non valido.

    • altrimenti, la pagina è stata spostata su disco ei valori effettivi di quei campi codificano la posizione della pagina sul disco.

In ogni caso, il sistema operativo deve sapere quale indirizzo ha generato l'errore di pagina per poter affrontare il problema. Questo è il motivo per cui i simpatici sviluppatori IA32 impostano il valore di cr2a quell'indirizzo ogni volta che si verifica un errore di pagina. Il gestore delle eccezioni può quindi esaminare cr2per ottenere l'indirizzo.

Semplificazioni

Semplificazioni alla realtà che rendono questo esempio più facile da capire:

  • tutti i circuiti di paging reali usano paging multi-livello per risparmiare spazio, ma questo mostrava un semplice schema a livello singolo.

  • le tabelle delle pagine contenevano solo due campi: un indirizzo a 20 bit e un flag di presenza di 1 bit.

    Le tabelle della pagina reale contengono un totale di 12 campi e quindi altre caratteristiche che sono state omesse.

Esempio: schema di paginazione multilivello

Il problema con uno schema di paging a livello singolo è che occuperebbe troppa RAM: 4G / 4K = 1 M di voci per processo. Se ogni voce è lunga 4 byte, ciò renderebbe 4 M per processo , che è troppo anche per un computer desktop: ps -A | wc -ldice che sto eseguendo 244 processi in questo momento, quindi ci vorrebbe circa 1 GB della mia RAM!

Per questo motivo, gli sviluppatori x86 hanno deciso di utilizzare uno schema multilivello che riduce l'utilizzo della RAM.

Lo svantaggio di questo sistema è che ha un tempo di accesso leggermente superiore.

Nel semplice schema di paginazione a 3 livelli utilizzato per processori a 32 bit senza PAE, i 32 bit di indirizzo sono suddivisi come segue:

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

Ogni processo deve avere una e solo una directory di pagine associata, quindi conterrà almeno 2^10 = 1Kvoci di directory di pagine, molto meglio del minimo 1M richiesto su uno schema a livello singolo.

Le tabelle delle pagine vengono allocate solo in base alle esigenze del sistema operativo. Ogni tabella di 2^10 = 1Kpagina ha voci di directory di pagina

Le directory delle pagine contengono ... voci della directory delle pagine! Le voci della directory delle pagine sono le stesse delle voci della tabella delle pagine tranne per il fatto che puntano agli indirizzi RAM delle tabelle delle pagine invece che agli indirizzi fisici delle tabelle . Poiché questi indirizzi sono larghi solo 20 bit, le tabelle delle pagine devono trovarsi all'inizio delle pagine da 4 KB.

cr3 ora punta alla posizione sulla RAM della directory delle pagine del processo corrente invece delle tabelle delle pagine.

Le voci delle tabelle delle pagine non cambiano affatto da uno schema a livello singolo.

Le tabelle delle pagine cambiano da uno schema a livello singolo perché:

  • ogni processo può avere fino a 1 KB di tabelle di pagina, una per voce di directory di pagina.
  • ogni tabella di pagina contiene esattamente 1000 voci invece di 1 milione di voci.

Il motivo per utilizzare 10 bit sui primi due livelli (e non, diciamo, 12 | 8 | 12) è che ogni voce della tabella pagine è lunga 4 byte. Quindi le 2 ^ 10 voci delle directory delle pagine e delle tabelle delle pagine si adatteranno perfettamente alle pagine 4Kb. Ciò significa che è più semplice e veloce allocare e rilasciare le pagine a tale scopo.

Traduzione degli indirizzi in uno schema a più livelli

Directory della pagina assegnata al processo 1 dal sistema operativo:

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

Tabelle delle pagine fornite all'elaborazione 1 dal sistema operativo in 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

Tabelle delle pagine fornite all'elaborazione 1 dal sistema operativo in 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

dove:

  • PD1: posizione iniziale della directory di pagina del processo 1 sulla RAM.
  • PT1e PT2: posizione iniziale della tabella di pagina 1 e della tabella di pagina 2 per il processo 1 sulla RAM.

Quindi in questo esempio la directory della pagina e la tabella delle pagine potrebbero essere memorizzate nella RAM in modo simile:

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


----------------> PT2 + 0x3FF * L
Page Table 1
----------------> PT2

----------------> PD1 + 0x3FF * L
Page Directory 1
----------------> PD1


----------------> PT1 + 0x3FF * L
Page Table 2
----------------> PT1

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

Traduciamo 0x00801004passo dopo passo l' indirizzo lineare .

Supponiamo che cr3 = PD1, cioè, punti alla directory della pagina appena descritta.

In binario l'indirizzo lineare è:

0    0    8    0    1    0    0    4
0000 0000 1000 0000 0001 0000 0000 0100

Raggruppamento come 10 | 10 | 12da:

0000000010 0000000001 000000000100
0x2        0x1        0x4

che dà:

  • voce della directory della pagina = 0x2
  • voce della tabella di pagina = 0x1
  • offset = 0x4

Quindi l'hardware cerca la voce 2 della directory della pagina.

La tabella delle directory delle pagine dice che la tabella delle pagine si trova in 0x80000 * 4K = 0x80000000. Questo è il primo accesso alla RAM del processo.

Poiché la voce della tabella delle pagine è 0x1, l'hardware esamina la voce 1 della tabella delle pagine in 0x80000000, che indica che la pagina fisica si trova all'indirizzo 0x0000C * 4K = 0x0000C000. Questo è il secondo accesso alla RAM del processo.

Infine, l'hardware di paging aggiunge l'offset e l'indirizzo finale è 0x0000C004 .

Altri esempi di indirizzi tradotti sono:

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

Gli errori di pagina si verificano se non è presente una voce di directory di pagine o una voce di tabella di pagine.

Se il sistema operativo desidera eseguire un altro processo contemporaneamente, fornirà al secondo processo una directory di pagina separata e collegherà tale directory a tabelle di pagine separate.

Architetture a 64 bit

64 bit è ancora troppo indirizzo per le dimensioni attuali della RAM, quindi la maggior parte delle architetture utilizzerà meno bit.

x86_64 utilizza 48 bit (256 TiB) e il PAE della modalità legacy consente già indirizzi a 52 bit (4 PiB).

12 di quei 48 bit sono già riservati per l'offset, che lascia 36 bit.

Se viene adottato un approccio a 2 livelli, la divisione migliore sarebbe due livelli a 18 bit.

Ma ciò significherebbe che la directory della pagina avrebbe 2^18 = 256K voci, il che richiederebbe troppa RAM: quasi una paginazione a livello singolo per architetture a 32 bit!

Pertanto, le architetture a 64 bit creano ancora ulteriori livelli di pagina, comunemente 3 o 4.

x86_64 utilizza 4 livelli in uno 9 | 9 | 9 | 12schema, in modo che il livello superiore occupi solo 2^9voci di livello superiore.

PAE

Estensione dell'indirizzo fisico.

Con 32 bit, è possibile indirizzare solo 4 GB di RAM.

Questo ha iniziato a diventare un limite per i server di grandi dimensioni, quindi Intel ha introdotto il meccanismo PAE in Pentium Pro.

Per alleviare il problema, Intel ha aggiunto 4 nuove linee di indirizzo, in modo che 64 GB possano essere indirizzati.

Anche la struttura della tabella delle pagine viene modificata se PAE è attivo. Il modo esatto in cui viene modificato dipende dal fatto che PSE sia attivato o disattivato.

PAE viene attivato e disattivato tramite il PAEbit di cr4.

Anche se la memoria indirizzabile totale è di 64 GB, i singoli processi possono comunque utilizzare solo fino a 4 GB. Il sistema operativo può tuttavia mettere diversi processi su diversi blocchi da 4 GB.

PSE

Estensione delle dimensioni della pagina.

Consente alle pagine di avere una lunghezza di 4 M (o 2 M se PAE è attivo) invece di 4K.

PSE viene attivato e disattivato tramite il PAEbit di cr4.

Schemi delle tabelle delle pagine PAE e PSE

Se sono attivi PAE e PSE, vengono utilizzati schemi di livelli di paging differenti:

  • no PAE e no PSE: 10 | 10 | 12

  • non PAE e PSE: 10 | 22.

    22 è l'offset all'interno della pagina 4Mb, poiché 22 bit indirizzano 4Mb.

  • PAE e no PSE: 2 | 9 | 9 | 12

    Il motivo di progettazione per cui 9 viene utilizzato due volte invece di 10 è che ora le voci non possono più essere contenute in 32 bit, che sono stati tutti riempiti da 20 bit di indirizzo e 12 bit di flag significativi o riservati.

    Il motivo è che 20 bit non sono più sufficienti per rappresentare l'indirizzo delle tabelle di pagina: ora sono necessari 24 bit a causa dei 4 cavi aggiuntivi aggiunti al processore.

    Pertanto, i progettisti hanno deciso di aumentare la dimensione della voce a 64 bit e per farli stare in una tabella di una singola pagina è necessario ridurre il numero di voci a 2 ^ 9 invece di 2 ^ 10.

    Il 2 iniziale è un nuovo livello di pagina chiamato Page Directory Pointer Table (PDPT), poiché punta alle directory delle pagine e compila l'indirizzo lineare a 32 bit. Anche i PDPT sono larghi 64 bit.

    cr3ora punta ai PDPT che devono essere sui primi quattro 4 GB di memoria e allineati su multipli di 32 bit per indirizzare l'efficienza. Ciò significa che ora cr3ha 27 bit significativi invece di 20: 2 ^ 5 per i 32 multipli * 2 ^ 27 per completare i 2 ^ 32 dei primi 4 GB.

  • PAE e PSE: 2 | 9 | 21

    I designer hanno deciso di mantenere un campo largo 9 bit per adattarlo a una singola pagina.

    Questo lascia 23 bit. Lasciando 2 per il PDPT per mantenere le cose uniformi con il caso PAE senza PSE lascia 21 per l'offset, il che significa che le pagine sono larghe 2M invece di 4M.

TLB

Il Translation Lookahead Buffer (TLB) è una cache per gli indirizzi di paging.

Poiché è una cache, condivide molti dei problemi di progettazione della cache della CPU, come il livello di associatività.

Questa sezione descrive un TLB completamente associativo semplificato con 4 voci di indirizzo singolo. Si noti che, come altre cache, i TLB reali di solito non sono completamente associativi.

Operazione base

Dopo che si è verificata una traduzione tra indirizzo lineare e fisico, viene memorizzata nel TLB. Ad esempio, un TLB a 4 voci inizia nel seguente stato:

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

Il >indica la voce corrente da sostituire.

e dopo che un indirizzo lineare di pagina 00003è stato tradotto in un indirizzo fisico 00005, il TLB diventa:

  valid   linear   physical
  ------  -------  ---------
  1       00003    00005
> 0       00000    00000
  0       00000    00000
  0       00000    00000

e dopo una seconda traduzione di 00007to 00009diventa:

  valid   linear   physical
  ------  -------  ---------
  1       00003    00005
  1       00007    00009
> 0       00000    00000
  0       00000    00000

Ora, se 00003deve essere nuovamente tradotto, l'hardware prima cerca il TLB e trova il suo indirizzo con un singolo accesso alla RAM 00003 --> 00005.

Ovviamente 00000non è nel TLB poiché nessuna voce valida contiene 00000come chiave.

Politica di sostituzione

Quando TLB è pieno, gli indirizzi meno recenti vengono sovrascritti. Proprio come per la cache della CPU, la politica di sostituzione è un'operazione potenzialmente complessa, ma un'euristica semplice e ragionevole consiste nel rimuovere la voce utilizzata meno di recente (LRU).

Con LRU, a partire dallo stato:

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

l'aggiunta 0000D -> 0000Adarebbe:

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

CAMERA

L'uso del TLB rende la traduzione più veloce, perché la traduzione iniziale richiede un accesso per livello TLB , il che significa 2 su un semplice schema a 32 bit, ma 3 o 4 su architetture a 64 bit.

Il TLB viene solitamente implementato come un tipo costoso di RAM chiamato CAM (Content-Addressable Memory). CAM implementa una mappa associativa su hardware, ovvero una struttura che data una chiave (indirizzo lineare), recupera un valore.

Le mappature potrebbero anche essere implementate su indirizzi RAM, ma le mappature CAM potrebbero richiedere molte meno voci di una mappatura RAM.

Ad esempio, una mappa in cui:

  • sia le chiavi che i valori hanno 20 bit (il caso di un semplice schema di paginazione)
  • è necessario memorizzare al massimo 4 valori per volta

potrebbe essere memorizzato in un TLB con 4 voci:

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

Tuttavia, per implementarlo con la RAM, sarebbe necessario avere 2 ^ 20 indirizzi :

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

che sarebbe ancora più costoso rispetto all'utilizzo di un TLB.

Invalidazione delle voci

In caso di cr3modifiche, tutte le voci TLB vengono invalidate, poiché verrà utilizzata una nuova tabella delle pagine per un nuovo processo, quindi è improbabile che le voci precedenti abbiano un significato.

L'x86 offre anche l' invlpgistruzione che invalida esplicitamente una singola voce TLB. Altre architetture offrono ancora più istruzioni per le voci TLB invalidate, come l'invalidazione di tutte le voci su un determinato intervallo.

Alcune CPU x86 vanno oltre i requisiti della specifica x86 e forniscono più coerenza di quanto garantisca, tra la modifica di una voce della tabella di pagina e il suo utilizzo, quando non era già memorizzata nella cache del TLB . Apparentemente Windows 9x faceva affidamento su questo per la correttezza, ma le moderne CPU AMD non forniscono page-walk coerenti. Le CPU Intel lo fanno, anche se devono rilevare speculazioni errate per farlo. Approfittarne è probabilmente una cattiva idea, poiché probabilmente non c'è molto da guadagnare e c'è un grosso rischio di causare sottili problemi sensibili al tempo che saranno difficili da eseguire il debug.

Utilizzo del kernel Linux

Il kernel Linux fa un uso estensivo delle funzionalità di paging di x86 per consentire rapidi cambi di processo con una piccola frammentazione dei dati.

In v4.2, guarda sotto arch/x86/:

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

Sembra che non ci siano strutture definite per rappresentare le pagine, solo macro: include/asm/page_types.hè particolarmente interessante. Estratto:

#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.hdefinisce CR0, ed in particolare la PGposizione del bit:

#define X86_CR0_PG_BIT      31 /* Paging */

Bibliografia

Gratuito:

  • rutgers-pxk-416 capitolo "Gestione della memoria: dispense"

    Buona revisione storica delle tecniche di organizzazione della memoria utilizzate dai sistemi operativi precedenti.

Non libero:

  • bovet05 capitolo "Indirizzamento della memoria"

    Ragionevole introduzione all'indirizzamento della memoria x86. Mancano alcuni esempi buoni e semplici.


Ottima risposta, ma non sono ancora chiaro come venga deciso LRU. Invocare il sistema operativo ogni volta che si accede a una pagina diversa da MRU sembra costoso. In alternativa, ho potuto vedere l'hardware che riordina la tabella delle pagine per LRU, che può essere pericoloso per i programmi concorrenti. Uno di questi è corretto? Come fa il sistema operativo a sapere quale pagina è l'LRU quando si verifica un errore di pagina?
Keynan

@Keynan Penso che sia l'hardware a farlo, quindi il tempo impiegato non è un problema. Per quanto riguarda la concorrenza non so come sia gestita. Penso che ci sia un CR3 e una cache per processore e il sistema operativo deve solo assicurarsi che le pagine di memoria non si sovrappongano.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
real TLBs are not usually fully associativeThe TLB is usually implemented as … CAMQueste due affermazioni non sono contraddittorie?
A3f

>>> x86_64 utilizza 4 livelli in un 9 | 9 | 9 | 12 schema dovrebbe essere 9 | 9 | 9 | 9 | 12?
monklof

@monklof Penso che questo sia corretto: 9 9 9 12 consente già 512 GB di RAM. Lo schema a 5 livelli è uno sviluppo più recente rivolto solo ai server, questo è menzionato nella risposta nel mio sito web che è più aggiornato.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

22

Ecco una risposta molto breve e di alto livello:

Un processore x86 funziona in una delle diverse modalità possibili (più o meno: reale, protetto, a 64 bit). Ciascuna modalità può utilizzare uno dei diversi possibili modelli di indirizzamento della memoria (ma non tutte le modalità possono utilizzare tutti i modelli), vale a dire: indirizzamento in modalità reale, indirizzamento segmentato e indirizzamento lineare lineare.

Nel mondo moderno, è rilevante solo l'indirizzamento lineare lineare in modalità protetta oa 64 bit e le due modalità sono essenzialmente le stesse, con la differenza principale che è la dimensione della parola macchina e quindi la quantità di memoria indirizzabile.

Ora, la modalità di indirizzamento della memoria dà significato agli operandi di memoria delle istruzioni della macchina (come mov DWORD PTR [eax], 25, che memorizza un dwordintero a 32 bit (aka ) di valore 25 nella memoria il cui indirizzo è memorizzato nel eaxregistro a 32 bit). Nell'indirizzamento lineare-piatto, questo numero in eaxpuò essere eseguito su un unico intervallo contiguo, da zero fino al valore massimo (nel nostro caso è 2 32  - 1).

Tuttavia, l'indirizzamento lineare lineare può essere impaginato o non impaginato . Senza paging, l'indirizzo si riferisce direttamente alla memoria fisica. Con il paging, l'unità di gestione della memoria del processore (o MMU) alimenta in modo trasparente l'indirizzo desiderato (ora chiamato indirizzo virtuale ) in un meccanismo di ricerca, le cosiddette tabelle di pagina , e ottiene un nuovo valore, che viene interpretato come un indirizzo fisico. L'operazione originale ora opera su questo nuovo indirizzo tradotto nella memoria fisica, anche se l'utente vede solo l'indirizzo virtuale.

Il vantaggio principale del paging è che le tabelle delle pagine sono gestite dal sistema operativo. In questo modo il sistema operativo può modificare e sostituire arbitrariamente le tabelle delle pagine, come quando si "cambia attività". Può conservare un'intera raccolta di tabelle di pagine, una per ogni "processo", e ogni volta che decide che un particolare processo verrà eseguito su una data CPU, carica le tabelle di pagina del processo nella MMU di quella CPU (ogni CPU ha la propria insieme di tabelle di pagina). Il risultato è che ogni processo vede il proprio spazio di indirizzi virtuali che sembra lo stesso indipendentemente da quali pagine fisiche erano libere quando il sistema operativo doveva allocare memoria per esso. Non conosce mai la memoria di nessun altro processo, poiché non può accedere direttamente alla memoria fisica.

Le tabelle delle pagine sono strutture di dati ad albero annidate archiviate nella memoria normale, scritte dal sistema operativo ma lette direttamente dall'hardware, quindi il formato è fisso. Vengono "caricati" nella MMU impostando uno speciale registro di controllo della CPU in modo che punti alla tabella di primo livello. La CPU utilizza una cache chiamata TLB per ricordare le ricerche, quindi gli accessi ripetuti alle stesse poche pagine sono molto più veloci degli accessi sparsi, per motivi di mancata TLB e per i soliti motivi della cache dei dati. È comune vedere il termine "voce TLB" utilizzato per fare riferimento alle voci della tabella di pagina anche quando non sono memorizzate nella cache del TLB.

E nel caso ti preoccupi che un processo possa semplicemente disabilitare il paging o provare a modificare le tabelle delle pagine: questo non è consentito, poiché x86 implementa i livelli di privilegio (chiamati "anelli") e il codice utente viene eseguito a un livello di privilegio troppo basso per consentire per modificare le tabelle delle pagine della CPU.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.