![]() | ![]() |
|
|
La gestione della memoria
Il secondo livello del modello a macchine virtuali simula l'esistenza di una pluralità di unità di memoria centrale, ciascuna associata ad uno dei processi generati dal nucleo.
Si consideri il seguente esempio che illustra le modalità attraverso le quali viene realizzata la convivenza dei processi all'interno della RAM. Si abbia una RAM di 2000 locazioni o indirizzi, ciascuno dei quali può contenere un'istruzione in linguaggio macchina, 500 dei quali sono occupati dal sistema operativo. Restano disponibili 1500 indirizzi per i processi degli utenti. Un modo di dividere questi indirizzi consiste nel ripartirli in blocchi di dimensioni predefinite, per esempio 10 blocchi di 150 indirizzi. Ciascun blocco può essere libero o occupato da un programma di dimensioni inferiori a 150 istruzioni. Una tabella contiene le informazioni relative a ciascun blocco: se sia libero o occupato, e da quale programma. Quando si richiede l'esecuzione di un programma, il sistema operativo consulta la tabella e lo carica in un blocco libero, mentre quando un programma termina provvede a liberare il blocco corrispondente.
Blocco |
Programma |
|
P1 |
|
P2 |
|
P3 |
|
|
Questo metodo viene detto a partizioni fisse ed è il più semplice da realizzare, ma presenta due limiti fondamentali:
- Il numero massimo di processi che possono essere allocati in memoria è prefissato;
- Un blocco contenente un processo molto breve viene considerato comunque occupato e perciò lo spazio rimanente risulta sprecato.
Questi problemi sono evitati se non si impone un insieme fisso di partizioni, ma si caricano i programmi ovunque ci sia spazio sufficiente ad accoglierli. All'inizio il caricamento avviene in sequenza, ma al procedere dell'attività, lo scaricamento dei programmi terminati genera spazi di lunghezza variabile in posizioni casuali della memoria. Ne consegue che il sistema operativo deve usare una tabella più complessa della precedente, contenente informazioni sull'allocazione dei singoli processi e sugli spazi liberi disponibili.
Indirizzo iniziale Lunghezza Contenuto Operativo |
||
P1 |
||
Inutilizzato |
||
P3 |
||
Inutilizzato |
||
P2 |
||
Libero |
|
|
P1 |
|
|
|
|
|
P3 |
|
|
|
|
|
P2 |
|
|
|
|
|
|
|
|
|
Indirizzo Lunghezza Aree libere Aree occupate
Questa seconda politica di uso della RAM è detta a partizioni variabili e sfrutta meglio della precedente la risorsa, al prezzo però di un maggior carico di lavoro svolto dal sistema operativo. La scelta della partizione dove caricare un nuovo programma può essere fatta:
- Si carica nella prima zona libera sufficientemente grande per contenere il programma, usando l'algoritmo first - fit;
- Si carica nella zona di memoria più piccola tra quelle adatte a contenere il programma con l'algoritmo best - fit.
La seconda soluzione è più lenta della prima, ma più razionale, in quanto mantiene ampi spazi liberi per quanto più tempo possibile.
In realtà, l'analisi statistica sui due algoritmi ha mostrato come il punto 1 risulti essere più efficiente del punto 2: infatti si perde meno tempo usando il primo posto libero e creandolo se non si trova, piuttosto che cercando tutte le volte il posto migliore.
Quale che sia la soluzione scelta, nel tempo si vengono a formare spazi vuoti tra un programma e l'altro, che sono troppo piccoli per ospitare un programma e che costituiscono perciò uno spreco.
Si può ovviare a questo inconveniente ricompattando il codice in memoria quando la situazione peggiora oltre un limite fissato, per esempio quando ci sono più di x spazi liberi di dimensione inferiore a y locazioni.
Finora si è supposto implicitamente che il caricamento di un programma in memoria riguardi tutto il suo codice: questo però non è sempre possibile e, se anche lo fosse, può non essere necessario.
Nel caso di
programmi molto grandi di dimensioni confrontabili con quelle della RAM, la
loro allocazione sarebbe problematica e, una volta avvenuta, monopolizzerebbe
la risorsa. Se poi la dimensione del programma fosse maggiore, la sua
allocazione non sarebbe possibile. Si adottano perciò tecniche dette di
funzionamento overlay (sovrapposizione), basata sull'osservazione che è
sufficiente avere in RAM una porzione per volta di ciascun programma, lasciando
il resto in memoria di massa, caricando altre porzioni solo quando necessario,
e liberando memoria quando non servono più. La zona del disco riservata per
questa gestione si chiama area di swap. Concretamente, questo significa usare
parti del disco (memoria lenta) per memorizzare informazioni che dovrebbero
risiedere in RAM (memoria veloce), ma che in un certo momento non sono
necessarie.
Ovviamente la memoria virtuale impone un rallentamento nell'esecuzione dovuta ai trasferimenti di codice dal disco: se possono essere eseguiti parallelamente alla normale attività del sistema l'effetto complessivo sull'efficienza è però minimo.
Comunque nel progetto del sistema operativo è qualificante l'uso di algoritmi che minimizzano il numero di tali trasferimenti in modo da evitare, per quanto possibile, le interruzioni dell'elaborazione.
La gestione della memoria virtuale si realizza attraverso due diverse tecniche di suddivisione di programmi in blocchi: la paginazione e la segmentazione.
Paginazione
Ogni programma viene considerato diviso in blocchi di diverse dimensioni detti pagine logiche; analogamente la memoria centrale viene divisa in pagine fisiche di dimensioni uguali a quelle delle pagine logiche. Una tabella riassume la situazione in cui si trovano le varie pagine di ogni programma. In particolare, per ogni pagina occorre sapere:
- Se occupa una pagina fisica
- La pagina occupata
- La posizione sul disco della pagina logica
- Altre informazioni come il flag (costituito da 1 bit) che indica se la pagina è stata modificata.
Ad ogni accesso alla memoria si verifica se la pagina richiesta è presente in RAM e, se non lo è, il sistema operativo provvede a caricarla in una pagina fisica libera. Se tutte le pagine fisiche sono occupate, occorre scaricare una delle pagine presenti in RAM (swapping). Per migliorare l'efficienza del sistema è opportuno che la pagina che si decide di scaricare non venga richiesta subito dopo: la decisione riguardo a quale pagina debba essere scaricata è un esempio di politica di gestione; una scelta ragionevole potrebbe essere quella di scaricare una pagina che da un certo tempo non viene richiesta, supponendo che non lo sarà ancora per un po'. Si può allora aggiungere, nella tabella, un flag per ogni pagina, che viene azzerato periodicamente dal sistema (per esempio ogni secondo) e, posto questo uguale ad 1 quando la pagina viene richiesta
Quando è necessario scaricare
una pagina, il sistema ne sceglie una tra quelle col segnale e il segnalatore
azzerato.
P |
IF |
ID |
M |
|
|
|
|
|
|
|
|
|
|
|
|
Per ogni pagina logica facente parte del programma, la tabella di gestione della paginazione contiene:
- Un bit P: vale 1 se la pagina logica occupa una pagina fisica, 0 se non la occupa;
- Un gruppo di bit IF: se la pagina logica non è presente il contenuto di questi bit non ha importanza;
- Un gruppo di bit ID: questo serve anche se la pagina logica è presente in memoria centrale, perché se essa contiene dati che vengono modificati durante l'elaborazione occorre ricopiarla su disco quando viene scaricata;
- Un bit M: vale 0 fintanto che nessuna parola della pagina è modificata dal momento in cui questa è stata allocata in memoria l'ultima volta; diventa 1 alla prima modifica.
Se al momento di scaricare la pagina, M vale 0 si può evitare la ricopiatura su disco risparmiando tempo. Al momento della richiesta della pagina logica, la consultazione della tabella permette di verificare se la pagina logica è allocata e, se non lo è, ne viene effettuato il caricamento a cura del sistema operativo; nella stessa tabella si legge l'indirizzo su hard disk della pagina richiesta.
Il vantaggio principale della paginazione è la semplicità dovuta al fatto che tutte le pagine, logiche o fisiche, hanno uguali dimensioni, mentre il difetto più importante è l'arbitrarietà con cui le pagine logiche vengono generate suddividendo il programma, il che generalmente aumenta il numero di chiamate tra pagine diverse e perciò diminuisce l'efficienza. È opportuno precisare due concetti lasciati finora sottintesi:
- Anche le routine del sistema operativo risiedono in memoria centrale;
- Le tabelle delle pagine occupano anch'esse memoria.
La paginazione riguarda solo la parte di memoria centrale libera, nel senso che le pagine contenenti il sistema operativo e le tabelle non devono mai essere scaricate. In realtà alcuni moduli del sistema operativo possono essere scaricati in caso di bisogno perché il loro uso non è molto frequente. Le pagine contenenti il nucleo del sistema operativo (codice e dati su cui si opera) restano fissate nella memoria e non vengono mai paginate su disco. Il meccanismo che impedisce il paging su disco di alcune parti della memoria viene indicato col termine pinning (puntare con uno spillo).
Segmentazione
Con questa seconda tecnica di gestione della memoria virtuale la suddivisione del programma viene effettuata sulla base di criteri logici e può essere controllata dal programmatore. Ciascun blocco risultante da tale suddivisione ha lunghezza arbitraria, talvolta limitata a valori inferiori ad una lunghezza massima, e viene detto segmento. Un programma non molto complesso può essere chiuso in un segmento che contiene i dati ed un segmento per il codice da eseguire mentre, al crescere della complessità, è opportuno raggruppare in segmenti le procedure che più frequentemente si chiamano fra loro, e i dati che vengono più spesso usati insieme. La ripartizione deve, cioè, seguire il criterio del minimo numero di chiamate tra segmenti diversi.
Compatibilmente con tale criterio è bene ridurre le dimensioni dei segmenti, perché ciò facilita il compito del sistema operativo. La memoria centrale non viene divisa in blocchi statici predefiniti, ma viene occupata, come visto, parlando delle partizioni variabili. Questo conduce immediatamente a due conseguenze negative: da un lato la memoria diventa soggetta a frammentazione, e dall'altro le tabelle da impiantare per gestire la memoria virtuale con i segmenti sono più complesse da usare che nel caso della paginazione. Per ogni segmento dei programmi da eseguire occorre sapere:
- Se è presente in memoria
- Indirizzo iniziale
- Dove si trova su disco
- Dimensioni
È utile disporre anche di una
tabella delle aree di memoria libere da consultare ed aggiornare ad ogni
richiesta di allocazione di nuovi segmenti e ad ogni terminazione di programma.
P |
IDI |
DS |
ID |
|
|
|
|
|
|
|
|
|
|
|
|
La tabella della segmentazione contiene le informazioni richieste per ogni segmento ossia: P, un bit; IDI, l'indirizzo dell'allocazione in memoria centrale che contiene la prima parola del segmento; DS, che serve quando si deve allocare il segmento, per trovare un'area libera adatta; ID. La segmentazione presenta, come la paginazione, vantaggi e svantaggi impliciti; tra questi, evidenziamo i seguenti:
- La memoria si frammenta rapidamente, dato che quando si scarica un segmento esso viene rimpiazzato normalmente da un segmento più piccolo, lasciando spazio inutilizzato. A questo si può ovviare con routine che provvedano a compattare tale spazio, spostando i segmenti in modo che siano adiacenti.
- Non è semplice costruire con scaricamenti aree libere di dimensioni assegnate. L'esempio della figura seguente mostra un'immagine della memoria in un istante in cui contiene diversi segmenti: si osservi che per poter caricare un nuovo segmento che richiede 100 locazioni occorre scaricare due segmenti. Un modo semplice di risolvere il problema consiste nel partire dall'inizio, e scaricare segmenti finché si raggiunge la dimensione voluta, ma in questo modo i segmenti all'inizio sono costantemente penalizzati, mentre quelli alla fine vengono scaricati raramente.
Libero (10) |
Occupato (70) |
Libero (10) |
Occupato (50) |
Libero (20) |
Occupato (40) |
|
100 locazioni
Un modo migliore può consistere nello scaricare segmenti e partire da
un punto variabile della memoria: la prima volta dalla locazione 0, la seconda
dalla locazione x, la terza dalla locazione 2x, e così via, in modo da
garantire che tutta la memoria (e tutti i segmenti) venga coinvolta. Ancora
meglio, si possono assegnare dei punteggi ad ogni segmento, e favorire lo
scaricamento dei segmenti con punteggi peggiori. È chiaro che, per ottenere
prestazioni migliori, occorre complicare notevolmente gli algoritmi di
controllo. Tra i vantaggi principali della segmentazione elenchiamo:
- La possibilità di far condividere a più processi alcuni segmenti. Questo ha un'importanza notevole in alcune applicazioni: per esempio, nei sistemi transazionali, nei quali molti utenti operano sugli stessi dati, si possono allocare segmenti di dati sui quali tutti gli utenti eseguono operazioni, risparmiando spazio in memoria centrale. Inoltre, anche le performance migliorano, dato che non occorre accedere molte volte ai dischi per reperire le informazioni richieste. Viceversa, diversi utenti che usano una stessa procedura potranno condividere il segmento contenente il codice lavorando, però, su segmenti di dati differenti.
- La mancanza di vincoli sul numero di segmenti caricabili in memoria, al contrario del numero fisso delle pagine. Questo è vantaggioso soprattutto se si hanno molti segmenti piccoli, che possono essere caricati in numero elevato.
Privacy |
Articolo informazione
Commentare questo articolo:Non sei registratoDevi essere registrato per commentare ISCRIVITI |
Copiare il codice nella pagina web del tuo sito. |
Copyright InfTub.com 2025