uni
La memoria virtuale è un’astrazione che ci permette di ottenere:
- Isolamento tra processi
- semplicità nel collegamento dei programmi
- semplicità nel caricamento/scaricamento dei processi
- possibilità di condivisione di memoria
“Nascondiamo” la memoria fisica, effettiva, ai processi, e introduciamo la memoria virtuale in modo del tutto trasparente dal punto di vista di questi ultimi.
Paginazione
Dividiamo la memoria virtuale, in regioni naturali, dette pagine, di dimensione .
Facciamo altrettanto con la memoria fisica, la dividiamo in frame della stessa dimensione delle pagine.
Introduciamo adesso un modo per tradurre gli indirizzi virtuali in indirizzi fisici, ovvero un modo di trovare in memoria fisica, in quale frame abbiamo caricato una pagina.
Si ricorda che nei processori Intel/AMD di architettura AMD64 solo gli bit meno significativi possono effettivamente assumere un valore qualsiasi, dove vale o a seconda del modello. I bit più significativi devono essere tutti uguali al bit numero (contando da ). Noi consideriamo il caso con .
Ogni indirizzo è composto di:
- numero di pagina (o di frame se parliamo di indirizzi fisici)
- offset all’interno della pagina o del frame: i bit meno significativi
Traduzione da indirizzo virtuale ad indirizzo fisico:
MMU
Per eseguire questa traduzione introduciamo un nuovo dispositivo, tra CPU e Cache, la memory management unit (MMU). Questa intercetta gli indirizzi (virtuali) generati dalla CPU e li traduce, utilizzando la tabella di corrispondenza attiva, in indirizzi fisici.
Funzioni aggiuntive
La tabella di corrispondenza può essere usata per associare ad ogni pagina ulteriori informazioni oltre al numero di frame.
Le entrate delle tabelle che si trovano nell’architettura AMD64 sono le seguenti:
- un flag P, presence
- la
activate_p()si occupa di porre a zero tutti i bit P di tutte le pagine delle quali il programma non ha bisogno
- la
- un flag R/W
- indica se il frame è disponibile in lettura e scrittura o in sola lettura (per esempio la sezione .text, anche se l’Architettura di Von Neumann fu introdotta apposta per permettere questa operazione, viene spesso impedita per questioni di sicurezza, tipicamente viene completamente vietata per i processi utente)
- un flag U/S
- siccome i processi utente possono comunque avere bisogno di saltare a codice in memoria sistema, nelle tabelle di corrispondenza dei processi utenti devo anche rendere disponibile parte della memoria M1 (memoria sistema), devo però proteggere questi frame da accessi in modalità utente, setto quindi il relativo bit U/S.
- due flag PWT (page write trough) e PCD(page cache disable), per comandare la Cache.
- settare PWT è utile quando si ha a che fare con la memoria video: la CPU desidera che quando effettua una scrittura questa avvenga subito per permettere la visualizzazione a video, ma invece di leggere dalla memoria video è più veloce leggere dalla cache.
- due flag A e D, utili per lo Swap
- la MMU setta il bit A di una entrata quando la utilizza (ovvero accede ad un indirizzo in quella pagina)
- la MMU setta anche il bit D se l’operazione effettuata è una scrittura
- l’idea è che quando carico una pagina in memoria, resetto ogni A e D, in questo modo posso tenere di conto di quali pagine vengono utilizzate di più con A (addirittura possiamo introdurre la paginazione su domanda, sfruttando l’eccezione trap di tipo page fault), mentre con D riconosco, al momento di rifare una swap-out, quali pagine sono effettivamente state modificate e caricare solo quelle sul dispositivo di swap, siccome quelle non modificate sono già presenti, questo permette di risparmiare costosi (in termini di tempo) accessi al dispositivo di swap.
Trie-MMU
Se calcoliamo quanto spazio occupa una singola tabella di corrispondenza otteniamo:
undefined