Sei sulla pagina 1di 11

Object 6

1
2
3
4
5

Memoria Virtuale nei Sistemi Operativi


Il rapido aumento della dimensione delle applicazioni, avvenuto negli anni ’90 e proseguito fino ai giorni nostri,
ha imposto la necessità di gestire in modo più efficiente il rapporto tra programma e memoria. Servivano nuove
tecniche in grado di consentire l’esecuzione di programmi la cui dimensione superava lo spazio disponibile in
memoria. Lo swapping non era più ritenuta una soluzione accettabile, per questo motivo fu proposta
l’introduzione di una tecnica chiamata memoria virtuale.
La memoria virtuale si basa su due principi cardine delle architetture:
• Il principio di località spaziale;
• Il principio di località temporale.

Principio di località spaziale


Se un elemento di posizione viene referenziato all’istante , la probabilità che un secondo elemento di
posizione S’ con:
venga referenziato all’istante sale al tendere di a 0.

Principio di località temporale


Se un elemento viene referenziato all’instante la probabilità che esso venga referenziato nuovamente all’istante
sale al tendere di a 0.

Memoria Virtuale tramite Overlay


La strategia dell’overlay rappresenta il primo rozzo tentativo di realizzare una memoria virtuale. Questa tecnica
consentiva di dividere il programma in blocchi più piccoli, chiamati overlay, da caricare in tempi diversi in
memoria.
All’avvio di un programma veniva caricato in memoria il gestore dell’overlay, che si occupava di caricare in
memoria il primo blocco del programma, chiamato overlay 0. Successivamente, esaurito il codice del primo
overlay, il gestore passava al caricamento del blocco successivo (overlay 1), che in base alla dimensione della
memoria veniva caricato accanto o sopra al blocco precedente.
Grazie a questa tecnica risultava possibile eseguire programmi la cui dimensione superava la dimensione totale
della memoria, tuttavia la creazione dei vari overlay richiedeva molto tempo perché doveva essere svolta
manualmente dal programmatore.

Memoria Virtuale tramite Paginazione


Un sistema più efficace e semplice da realizzare dello swapping e dell’overlay fu presentato qualche anno dopo
con il nome di memoria virtuale paginata. L’idea di base della memoria virtuale paginata è la suddivisione dello
spazio degli indirizzi del programma in blocchi, chiamati pagine. Ogni pagina rappresenta quindi un blocco di
indirizzi contigui appartenenti al medesimo programma.
Le pagine di uno stesso programma non devono necessariamente essere sempre presenti all’interno della memoria,
le singole pagine vengono caricate in memoria solo quando gli indirizzi al loro interno vengono referenziati.
Come già accade per l’overlay, il caricamento di una nuova pagina può avvenire accanto ad una pagina esistente o
sopra una pagina esistente.
Questo sistema è del tutto automatico e consente di eseguire un numero elevato di programmi
contemporaneamente.

Traduzione degli indirizzi


Gli indirizzi generati dal programma sono detti indirizzi virtuali, l’insieme di tutti gli indirizzi virtuali forma lo
spazio virtuale degli indirizzi. In un sistema senza paginazione l’indirizzo virtuale viene inviato direttamente sul
bus e provoca la lettura/scrittura di una determinata locazione di memoria. Quando in un sistema è in uso la
memoria virtuale, questi indirizzi vengono inviati alla MMU (Memory Management Unit) che si occupa di
mappare gli indirizzi virtuali sugli indirizzi della memoria fisica.
Esaminiamo ad esempio un sistema con una memoria fisica di 16 KB e una memoria virtuale di 32 KB. Tale
sistema potrà eseguire programmi con dimensione totale uguale a 32 KB, ma non potrà caricarli interamente in
memoria. Supponiamo che l’utente decida di eseguire un programma la cui dimensione totale è proprio di 32 KB,
il sistema operativo si preoccuperà di dividere lo spazio degli indirizzi del programma in blocchi più piccoli, le
pagine.
Nell’esempio la dimensione delle pagine è uguale a 4 KB. Tale valore, pur non rappresentando uno standard, è in
genere compreso tra i 512 byte e i 64 KB. Le unità corrispondenti le pagine nella memoria fisica (disco) sono detti
frame o page frame, essi hanno in genere la stessa dimensione delle pagine. Durante l’esecuzione del programma
le singoli pagine verranno caricate, secondo necessità, dal disco alla memoria. Dato che la dimensione della
memoria è di soli 16 KB, solo quattro pagine per volta potranno essere caricate su di essa. Le pagine non caricate
(rappresentate in grigio) rimarranno salvate nel disco, e in caso di necessità verrano caricate in memoria.

Vediamo come lavora la MMU: supponiamo che l’utente esegua la seguente istruzione:
1 MOV REG, 0

La MMU riceverà l’indirizzo virtuale 0 e calcolerà la pagina che contiene l’indirizzo. Nel nostro caso la pagina in
questione è la pagina 0 (indirizzi da 0 a 4095). Successivamente si collegherà al frame associato alla pagina, che
in questo caso è sempre il frame 0.
Infine calcolerà il nuovo indirizzo fisico: 0
1 MOV REG, 0

Tuttavia questo è solo un caso fortuito, analizziamo la seguente richiesta:


1 MOV REG, 16400

La MMU calcolerà la pagina virtuale che contiene l’indirizzo, che in questo caso è la pagina 4 (indirizzi da 16 380
a 20 475). Il frame associato alla pagina 4 è il frame 1 (indirizzi da 4095 a 8190). A questo punto la MMU
calcolerà l’offset nella pagina e lo addizionerà al frame .
L’indirizzo fisico corrispondente è il: 4115
1 MOVE REG, 4115
Implementazione della Paginazione
Per eseguire la traduzione degli indirizzi, la Memory Management Unit deve mappare le pagine sui frame
corrispondenti. Per eseguire questa operazione in modo efficiente la MMU utilizza uno strumento noto come
tabella delle pagine o page table.

Struttura della Tabella delle Pagine


La tabella delle pagine consente di mappare in modo efficiente le pagine durante il processo di traduzione. Il
numero della pagina virtuale è usato come indice della tabella, mentre il numero del frame è inserito all’interno di
una delle voci della tabella. L’indirizzo della tabella delle pagine di ogni processo è contenuto all’interno PCB del
processo in questione. La struttura della tabella delle pagine varia in base al sistema, tuttavia tutte posseggono una
struttura base comune.

Analizziamo nel dettaglio i campi più importanti:


• Numero del frame (page base address), rappresenta il numero del frame a cui la pagina virtuale fa
riferimento;
• Modificato (dirty), rappresenta lo stato della pagina virtuale collegata.
Se il bit è impostato a ‘0’ la pagina non è stata modificata da quando è stata portata in memoria, quindi
può essere sovrascritta senza problemi.
In caso contrario la pagina è stata modificata e deve essere scritta su disco prima di essere sovrascritta;
• Referenziato (accessed), questo bit viene azzerato a intervalli regolari e serve al sistema operativo per
trovare le pagine usate di recente.
• Caching (cache disable), consente di disabilitare il caching della pagina.
• Protezione (read/write), indica gli accessi che sono consentiti sulla pagina.
Può avere una dimensione compresa tra 1 bit e 2 bit e può mappare diversi stati, (lettura/scrittura, sola
lettura, sola scrittura);
• Presenza/assenza (present), segnala se la pagina virtuale a cui la voce fa riferimento si trova
effettivamente in memoria.
Richiamare una pagina il cui bit di presenza è impostato a ‘0’ genera il page fault;
Le dimensione della tabella delle pagine non sono costanti, nel caso dei sistemi con processori Intel Pentium le
tabelle possedevano 1024 voci per 1024 pagine differenti. La dimensione delle pagine in questi sistemi è uguale a
4 KB.
Dimensione di una voce:
Dimensione della tabella di 1024 voci:

Dimensione delle Pagine


La scelta della dimensione delle pagine è un problema cruciale al fine del funzionamento di un sistema operativo.
Come vedremo la dimensione delle pagine è via via cresciuta nel tempo, adattandosi alla struttura e alla capacità
dei calcolatori e dei sistemi operativi.
Come spesso succede non esiste una strategia migliore in modo assoluto sulle altre, ad esempio la scelta di creare
un sistema paginato con pagine di piccole dimensioni porta una serie di vantaggi:
1. Scarsa frammentazione interna;
2. Maggiore adattabilità alle dimensioni del programma;
3. Diminuzione dello spazio di memoria utilizzato.
Tuttavia crea anche alcuni svantaggi:
1. Necessità di creare grosse tabelle delle pagine;
2. Aumento del numero di accessi al disco.

Page Fault
Riprendiamo l’esempio fatto in precedenza, consideriamo il seguente schema di page e frame page. Come si vede
dall’immagine seguente, le pagine 1, 3, 5, 6 referenziano frame dello spazio degli indirizzi presenti sul disco, e
non ancora caricati in memoria. Al contrario le pagine 0, 2, 4, 7 sono collegate a frame presenti in memoria e sono
per questo motivo accessibili.

Immaginiamo all’interno del sistema d’esempio l’utente faccia la seguente richiesta:


Doppio click per copiare il codice
1 MOV REG, 4096

Come dovrebbe comportarsi il sistema, e in particolare la MMU in questi casi? L’indirizzo 4096 rappresenta il
primo indirizzo della page 1, che referenzia un frame che al momento è presente solo sul disco. Per questo motivo
il bit di presenza/assenza associato alla entry in questione nella tabella delle pagine è in questo caso impostato a 0.
La MMU leggerà questo valore è scatenerà un trap verso il sistema operativo, questo trap è chiamato “page fault”
o “errore di pagina”.
Il sistema operativo dovrebbe avere una tabella delle pagine simile alla seguente:

Indice Indirizzo frame Bit presenza/assenza


7 11 1
6 00 0
5 10 0
4 01 1
3 11 0
2 10 1
1 01 0
0 00 1
Gli indirizzi frame delle pagine non caricate in memoria sono casuali.
Per gestire correttamente il page fault, il sistema operativo dovrà sceglie una pagina usando un apposito algoritmo
di sostituzione delle pagine e lo sovrascriverà con la nuova pagina letta da disco. Svolto questo passaggio
aggiornerà la tabella delle pagine e riavvierà l’istruzione che ha generato il trap. Nella pagina successiva vediamo
l’aspetto dello schema è della tabella delle pagine dopo l’aggiornamento.

Indice Indirizzo frame Bit presenza/assenza


7 11 1
6 00 0
5 10 0
4 01 1
3 11 0
2 10 0
1 10 1
0 00 1
Gli indirizzi frame delle pagine non caricate in memoria sono casuali.
Il page fault è dannoso per le prestazioni generali del sistema, in quando obbliga la MMU a perdere del tempo
leggendo dei dati dal disco, invece che da un supporto più veloce come è la RAM. Per questo motivo all’interno di
un sistema operativo è cruciale la scelta di un buon algoritmo di sostituzione delle pagine.
Tempo di accesso a un dato/istruzione in un sistema paginato:
Sintesi del Page Fault Handling:
1. La MMU solleva una trap di page fault;
2. L’hardware salva la sessione di lavoro e successivamente richiama la procedura di gestione
dell’eccezione;
3. Il S.O. individua il frame sul disco da caricare in memoria;
4. Il S.O. usa un particolare algoritmo per decidere quale frame presente nella memoria sovrascrivere;
5. La nuova pagina viene caricata in memoria e la tabella delle pagine viene aggiornata;
6. Viene ripristinata la sessione di lavoro;
7. Il processo torna in esecuzione.

Algoritmi per la sostituzione delle pagine


Come per la gestione dello scheduling esistono diversi algoritmi che si occupano della sostituzione delle pagine in
memoria. Alcuni di questi algoritmi sono:
• Not Recently Used
• First In – First Out
• Second Chance
• Clock
• Least Recently used
• Ottimale

Not Recently Used


È l’algoritmo più semplice da scrivere ma non il più efficiente. Esso utilizza i bit “referenziato” e “modificato” per
dividere le pagine in classi:
• Classe 0, non referenziato e non modificato;
• Classe 1, non referenziato e modificato;
• Classe 2, referenziato e non modificato;
• classe 3, referenziato e modificato.
A questo punto l’algoritmo sovrascrive una pagina a caso appartenente alla classe inferiore.
Nota: È più sensato sovrascrivere una pagina che è stata modificata ma che non è stata usata recentemente
piuttosto che sovrascrivere una pagina non modificata ma che è appena stata usata. Questo per il principio di
località temporale.

First In – First Out (FIFO)


Questo algoritmo traccia l’ordine con cui le pagine sono state inserite nella tabella delle pagine, e in caso di
necessità sovrascrivere la pagina più vecchia. Questo algoritmo non è molto efficiente, è probabile che tra le prime
pagine ci sia ad esempio la dichiarazione delle variabili globali, che verrà sicuramente riutilizzate dal processo.
L’algoritmo First In – First Out è soggetto all’anomalia di Belady, si è infatti appurato che nei sistemi FIFO
all’aumentare del numero di pagine segue un aumento del numero di page fault.

Second Chance
Per risolvere i problemi presentati nell’algoritmo precedente, si esegue un controllo incrociato sul tempo di
ingresso della pagina e sul suo bit “referenziato”. In questo modo si possono trovare le pagine più vecchie, che
non vengono referenziate da più tempo. Se il bit “referenziato” della pagina è impostato a ‘1’ allora quest’ultima è
stata usata di recente. In questi casi il bit viene azzerato e la pagina messa in coda. Successivamente si procede
con l’analisi della pagina successiva, fino a raggiungere la cima della lista. Se il bit è impostato a ‘0’ si utilizza la
pagina in esame come spazio di rimpiazzo in caso di fault.
Clock
Molto simile al precedente. Si implementa una lista circolare con una “lancetta” che indica sempre la pagina più
vecchia. In caso di fault si controlla il bit “referenziato” e si decide se sostituire o no la pagina.

Least Recently Used


Questo algoritmo implementa una lista linkata per tenere traccia delle pagine usate più frequentemente e più
recentemente. Si suppone che le pagine più recenti, come quelle usate più frequentemente saranno riutilizzate in
futuro, questo per il principio di località temporale. Purtroppo questo algoritmo non è facile da implementabile. È
però possibile associare a ogni pagine un contatore che viene azzerato a intervalli di tempo regolare. In questo
modo è possibile trovare le pagine usate più frequentemente in un certo arco di tempo.

Ottimale
Un algoritmo ottimale necessiterebbe di un sistema in grado di stabile se e quando una pagina sarà utilizzata
nuovamente in futuro. In questo modo si potrebbe procedere con la sostituzione delle pagine non più referenziate,
o referenziate più in la nel tempo.

Politiche globali o locali?


Chiudiamo il paragrafo dedicato agli algoritmi di sostituzione delle pagine con una considerazione, fino a ora
abbiamo ragionato in termini di singolo processo, tuttavia questo raramente è vero. Normalmente esistono più
processi in esecuzione simultanea che hanno diverse pagine caricate in memoria. In questi casi dobbiamo
chiederci che pagine considerare in caso di page fault, dobbiamo applicare un algoritmo (ad esempio l’LRU) a
tutte le pagine o solo alle pagine del processo in questione? Si parla di algoritmo di sostituzione locale quando un
algoritmo considera solo le pagine del singolo processo, al contrario si parla di algoritmo di sostituzione globale
quando un algoritmo considera tutte le pagine presenti in memoria.
Nella maggior parte dei casi l’uso di un algoritmo globale sarebbe da preferisti, tuttavia esistono dei casi limiti,
non è infatti accettabile che un processo con tanti page fault monopolizzi lo spazio della memoria inserendo
soltanto le sue pagine.
Bisogna cercare di mantenere un equilibro tra il numero di pagine dei vari processi.

La linea A corrisponde a una frequenza di errori troppo alta, mentre la linea B corrisponde a una frequenza di
errori molto bassa. Questa funzione aiuta a distribuire equamente la memoria tra più processi, se un processo si
trova spesso sopra la riga A ha probabilmente troppa poca memoria, al contrario se si trova spesso sotto la riga B
ha sicuramente troppa memoria a disposizione.
Memoria Secondaria
Nelle sezioni precedenti abbiamo condotto un’analisi abbastanza approfondita sul sistema del page fault,
descrivendo il funzionamento dei vari algoritmi che determinano il passaggio di una pagina dal disco alla
memoria, tuttavia non abbiamo detto niente sulla zona del disco adibita al contenimento delle pagine.
Il sistema più semplice per allocare sul disco lo spazio delle pagine è avere una partizione speciale destinata allo
swapping (chiamata area di swap). In alcune circostanze questa partizione può essere rappresentata da un disco a
se stante. Questa partizione è usata specialmente nei sistemi UNIX, ed è composta da uno speciale file system in
grado di accelerare le procedure di lettura delle singole pagine.
La partizione di swap è trattata come una lista contente le pagine del processo, e nel modello più semplice è di
dimensione uguale o superiore a quest’ultimo. Ogni processo possiede un particolare indirizzo che rappresenta
l’inizio della sua memoria di swapping, a esso sommerà l’offset della pagina per determinare il punto in cui
prelevare/salvare la pagina.

Translation Lookaside Buffer


I progettisti hanno osservato che la maggior parte dei programmi tende a referenziare spesso un numero ridotto di
pagine. Il Translation Lookaside Buffer è un componente della MMU creato per ottimizzare le prestazioni del
sistema. All’interno del TLB vengono conservati i riferimenti alle ultime pagine referenziate. Ovviamente il TLB
non punta a sostituire la tabella delle pagine, per questo motivo presenta un numero di elementi notevolmente
inferiore (ad esempio minore di 64).
Il TLB è costituito dalla stessa struttura della tabella della pagina, ma contiene in aggiunta la colonna contenente il
numero della pagina virtuale. Quando un indirizzo virtuale viene passato alla MMU, quest’ultima controlla la
presenza della pagina nel TLB e ne verifica il bit di presenza/assenza. Se la pagina è presente e il bit di
presenza/assenza è impostato a ‘1’ la MMU genera un HIT TLB e procede con la conversione.In caso contrario
genera un TLB MISS, e procedere con il recupero della pagina dalla tabella delle pagine o dal disco.
Nelle sue versioni più recenti il TLB può essere considerato una cache del processore. Tempo di accesso a un
dato/istruzione in un sistema paginato con TLB:

Tabelle delle Pagine Multilivello


Le tabelle delle pagine multilivello sono utilizzate in alcuni sistemi che utilizzano tabelle delle pagine di grosse
dimensioni per risparmiare spazio in memoria. Questi sistemi dividono la tabella delle pagine in più tabelle di
dimensioni inferiori, in modo da caricare in memoria solo le tabelle in uso in un determinato momento.
Consideriamo un sistema a 32 bit che utilizza tabelle delle pagine a due livelli. L’indirizzo virtuale viene suddiviso
dalla MMU in tre blocchi di dimensione diversa:
1. Il primo blocco da ‘n’ bit indica l’offset all’interno della tabella di livello 1;
2. Il secondo blocco da ‘m’ bit indica l’offset all’interno della tabella di livello 2;
3. Il terzo valore da ‘k’ bit indica l’offset del frame.
Usando il primo offset, la MMU localizza la riga all’interno della prima tabella ed estrapola l’indirizzo delle
tabelle di livello inferiore. Se la tabella di livello 2 scelta è presente in memoria, la MMU utilizza il secondo offset
per ottenere l’indirizzo della pagina. In caso contrario si preoccupa prima di caricare la tabella in memoria. Infine,
utilizza il terzo offset per localizzare l’indirizzo all’interno della pagina.

Tabelle Multilivello (Intel Pentium)


Le architetture costruite con il processore Intel Pentium posseggono una particolare struttura di tabelle delle
pagine a due livelli. Ogni processo in esecuzione su questi sistemi possiede una particolare tabella chiamata page
directory (directory delle pagine) composta da 1024 voci da 32 bit. L’indirizzo della page directory viene salvata
all’interno del registro di controllo CR3 del processore. Il contenuto di tale registro viene aggiornato a ogni
context switch.
Le voci della page directory contengono i puntatori per le diverse page table a disposizione del processo. Le varie
page table non si trovano sempre in RAM, infatti in RAM vengono mantenute solo le tabelle delle pagine in uso
in quel momento. Di seguito descriviamo il processo di conversione degli indirizzi. L’indirizzo a 32 bit in arrivo
alla MMU viene suddiviso in tre blocchi:
1. Il primo blocco di 10 bit identifica un record della page directory, possiamo considerare questo valore
come un offset all’interno della page directory;
2. Il secondo blocco di 10 bit identifica un record della page table, anche in questo caso consideriamo il
valore al pari di un offset all’interno della page table;
3. Il terzo blocco di 12 bit rappresenta l’offset all’interno della pagina.
Grazie al registro di controllo CR3 ricaviamo l’indirizzo iniziale della page directory, successivamente grazie ai
primi 10 bit dell’indirizzo troviamo una riga all’interno della page directory. Gli elementi all’interno della page
directory sono quindi , ovvero 1024 elementi. L’indirizzo ricavato in precedenza rappresenta l’indirizzo della
page table che contiene la pagina che ci interessa. Utilizziamo i 10 bit successivi dell’indirizzo per localizzare
l’indirizzo della pagina. Anche per la page table gli elementi sono , ovvero 1024 elementi. Infine, gli ultimi 12
bit dell’indirizzo rappresentano l’offset all’interno della pagina.

La dimensione delle pagine nei sistemi Pentium è di , ovvero 4 KB.

Struttura della Page Directory


Come per la page table i vari sistemi posseggono una struttura per page directory differente, in seguito ne vediamo
una possibile implementazione.
Tabelle delle Pagine Invertite
Le tabelle delle pagine multi-livello funzionano egregiamente nei sistemi a 32 bit, tuttavia sono del tutto
inefficienti nei sistemi a 64 bit. In queste situazioni per memorizzare il medesimo spazio di memoria
occorrerebbero tabelle delle pagine di enormi dimensioni.

Sistemi a 32 bit:
Sistemi a 64 bit:
I sistemi a 64 bit possiedono unicamente una struttura nota come tabelle delle pagine invertite, le cui voci invece
che rappresentare pagine nella memoria virtuale rappresentano frame nella memoria fisica.  Per fare un esempio,
un sistema a 64 bit con 1 GB di RAM e pagine/frame di 4 KB utilizzerebbe una tabella delle pagine invertite con
voci ( ).
L’indice dell’entry nella tabella delle pagine invertita rappresenta l’indirizzo del frame. Le voci nella tabelle delle
pagine contengono il numero della pagina virtuale e l’identificativo del processo a cui il frame è assegnato. Le
tabelle delle pagine invertite hanno l’indiscusso vantaggio di portare a un notevole risparmio di spazio a parità di
memoria, se confrontate con le tabelle delle pagine multi-livello, tuttavia esse hanno un grande svantaggio:
portano a una traduzione degli indirizzi molto più complessa.

Traduzione degli indirizzi


L’indirizzo a 64 bit in arrivo viene scomposto in tre parti:
1. PID, ID del processo;
2. P, numero della pagina;
3. Offset.
La MMU non può più cercare la pagina all’interno della tabella delle pagine, questa volta dovrà cercare la tupla
<PID, P>, con PID che rappresenta l’ID del processo a cui è associato lo spazio degli indirizzi e P che rappresenta
la pagina virtuale. Per prima cosa la MMU cercherà nella tabella delle pagine invertite una voce che possiede i
campi <PID, P> uguali a quelli dell’indirizzo, successivamente utilizzerà il numero della voce come indirizzo per
recuperare il frame corrispondente, e infine utilizzerà l’offset per selezionare l’indirizzo corretto nel frame.
Questa ricerca non dovrà essere eseguita in caso di page fault per recuperare la singola pagina, bensì dovrà essere
eseguita per ogni riferimento alla memoria. Questo porta ovviamente a una drastica riduzione delle prestazioni.
Per questo motivo è fondamentale l’uso del TLB per ridurre i tempi di caricamento delle pagine usate più
frequentemente.

Vantaggi
La tabella delle pagine invertite permette un sostanziale risparmio di spazio nei sistemi a 64 bit.

Svantaggi
La tabella delle pagine invertite presenta due svantaggi:
• Il tempo di ricerca nella tabella è notevolmente più lungo rispetto a quello della controparte a 32 bit, per
questo motivo è essenziale l’introduzione del TLB nei sistemi a 64 bit;
• È molto complesso condividere le pagine tra due processi.