Gestione della memoria

Materie:Appunti
Categoria:Sistemi
Download:156
Data:07.02.2007
Numero di pagine:9
Formato di file:.doc (Microsoft Word)
Download   Anteprima
gestione-memoria_3.zip (Dimensione: 13.32 Kb)
readme.txt     59 Bytes
trucheck.it_gestione-della-memoria.doc     72.5 Kb


Testo

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
1
P1
2
P2
3
P3
4

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.
Sistema
Operativo
P1
Inutilizzato
P3
Inutilizzato
P2
Libero
500
100
P1
620
184
P3
1008
224
P2
600
200
804
204
1232
768
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. La RAM virtualmente disponibile diviene allora più grande. I processi hanno a disposizione la RAM che serve, anche se solo virtuale, in quanto non corrisponde alla memoria fisica. In questo contesto, il caricamento di un programma viene realizzato con l’allocazione della prima porzione e delle informazioni relative alla localizzazione, sui supporti di memoria di massa, delle porzioni restanti. I programma che realizzano questi meccanismi (sistema operativo) si collocano al secondo livello del modello onion skin e generano una macchina virtuale che assegna una memoria centrale di dimensioni quasi arbitrariamente grandi ad ogni processo: il limite superiore di tali dimensioni è costituito dalla capacità dei dischi, molto superiore a quella della RAM fisica.
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.

Esempio