Memoria
Le gerarchie di memoria si suddividono in:
- Memoria cache: la più veloce e la prima a cui la CPU accede
- Memoria principale: contenente i dati volatili dei programmi in esecuzione
- Memoria secondaria: necessita la copia dei dati in memoria principale
La gestione della memoria consiste nel decidere quando copiare i dati di un programma (i.e. fetch), dove collocarli (i.e. posizionamento) e a chi rimpiazzare i dati se non c'è abbastanza spazio (i.e. sostituzione).
L'allocazione può essere:
-
Contigua:
Utilizzata se vanno memorizzati i dati in un unico blocco di indirizzi sequenziali. Se però è richiesta una grande porzione di memoria può essere impossibile trovare un blocco libero abbastanza grande.
Sui vecchi sistemi mono utente, l'intera memoria era allocata al singolo utente e la gestione era compito del programmatore, anche con sistemi di terze parti come IOCS (Input/Output Control System).
-
Non contigua:
Divide la memoria del programma in segmenti, che si possono trovare in parti diverse della memoria. Questo aumenta il numero di processi in memoria, ma aumenta anche l'overhead.
Organizzazione
Se l'ambiente è mono utente, il singolo processo utilizza tutta la memoria libera disponibile.
Nel caso di insufficiente memoria contigua disponibile esiste la tecnica di overlay, che consiste nel dividere il programma in sezioni e alternativamente caricare quelle attive nella stessa area contigua di memoria.
Questo però complica l'organizzazione delle sezioni, perchè quelle che interagiscono non possono essere sovrapposte, ma anche perchè le modifiche al programma possono renderle non sovrapponibili.
La protezione del S.O. avviene salvando gli indirizzi limite della memoria del processo in un registro protetto, così da lanciare un'eccezione nel caso vengano superati.
Se l'ambiente è a multiprogrammazione invece, la memoria può essere organizzata in:
-
Partizioni fisse
Inizialmente ogni programma veniva compilato con indirizzi assoluti, di conseguenza ad ogni processo caricato veniva assegnata una partizione di memoria con posizione e dimensione fissa.
Questo semplificava il parallelismo, ma forzava l'attesa ai processi da caricare sulla stessa partizione.
L'alternativa è stata l'introduzione di compilazione con indirizzi rilocabili, per cui ogni processo è caricato dinamicamente su partizioni diverse.
La protezione del S.O. e gli altri processi avviene associando ad ogni partizione un registro base e limite che limitano gli indirizzi accessibili in memoria dal processo.
Un lato negativo è la frammentazione interna alle partizioni, dato che la memoria non in uso dal processo nella partizione non può essere in alcun modo usata.
-
Partizioni variabili
L'alternativa alle partizioni fisse è creare nuove partizioni della stessa dimensione del processo.
Questo risolve la frammentazione interna, ma introduce frammentazione esterna causata dai buchi lasciati in memoria quando il processo termina. Tra i modi per risolvere, ci sono:
- Coalescenza: combina più blocchi liberi vicini in un singolo blocco
- Compattazione: compatta i blocchi in uso in memoria, comportando overhead significativo
Inoltre, la scelta di posizione in memoria avviene secondo le strategie di:
- Best-fit: nel più piccolo spazio in grado di contenere il processo
- First-fit: nel primo spazio libero, riducendo l'overhead
- Worst-fit: nello spazio più grande disponibile, lasciando un grande buco per il prossimo processo
Per poter gestire la memoria libera però, vanno salvate le informazioni sulle partizioni. Una tecnica consiste nel salvare una bitmap, dove ogni bit indica lo stato di un'unità di memoria, un'altra nell'usare una linked list, dove ogni elemento è una partizione di un processo o di memoria libera.
-
Swapping
Invece di avere una partizione per processo, si tiene l'intera memoria per il singolo processo in esecuzione, spostando gli atri temporaneamente nella memoria secondaria.
Questo comporta grande overhead al cambio di contesto, dato che è richiesto l'accesso in memoria secondaria. Di conseguenza, una soluzione può essere l'uso di partizioni variabili con swapping.
Memoria virtuale
La memoria virtuale permette l'uso di un indirizzamento virtuale molto più ampio di quello reale.
Blocchi virtuali contigui vengono tradotti in blocchi reali non necessariamente contigui, e possono essere:
- Pagine, con la paginazione, se ogni blocco ha dimensione fissa
- Segmenti, con la segmentazione, se ogni blocco può avere dimensione diversa