Organizzazione della memoria dinamica
I programmi, per essere eseguiti, devono essere caricati in memoria principale (RAM+registri). I registri si trovano direttamente sulla CPU, garantendo un accesso diretto in un colpo di clock, mentre l'accesso alla RAM risulta essere più oneroso in termini di tempo. Questo gap è parzialmente colmato mediante le cache messe tra la memoria RAM e i registri.
Per garantire buone prestazione, la RAM deve contenere più processi nello stesso momento. E' quindi necessario garantire la loro corretta esecuzione, evitando la possibilità che i processi scrivano in aree di memoria già occupate.
Per questo motivo nella CPU sono presenti due registri che indicano l'indirizzo logico di inizio e l'offset della porzione di memoria utilizzabile da un determinato processo. Le parti di memoria al di fuori di questa porzione non sono accessibili dal processo considerato. Questi due registri sono accessibili soltanto dal sistema operativo, che risulta quindi essere l'unica entità ad avere accesso indiscriminato ad ogni parte della memoria.
Allocazione della memoria
L'associazione di un processo ad una porzione specifica di memoria dinamica (quindi l'attribuzione dei due indirizzi di base e offset), determina anche gli indirizzi fisici del processo caricato. La maggior parte dei sistemi utilizza indirizzi simbolici per definire parti di memoria o determinate variabili, che però devono essere sostituiti con indirizzi reali prima di essere passati alla CPU.
L'allocazione della memoria e di conseguenza la traduzione degli indirizzi fisici in indirizzi simbolici può avvenire in tempi diversi:
-durante la compilazione (allocazione assoluta), viene allocata nel momento della compilazione del programma. Questa tecnica è utilizzabile nel caso in cui si conosca la quantità di memoria necessaria ad un processo e la sua posizione in memoria nel momento dell'esecuzione, prima dell'esecuzione stessa. E' necessario ricompilare il codice, nel caso in cui si voglia modificare la porzione di memoria dedicata.
-durante il caricamento e per questo è necessario scrivere codice riallocabile, in quanto lo spazio di indirizzamento non è noto a priori.
-durante l'esecuzione. Un processo può essere spostato da un segmento di memoria ad un altro, utilizzando un supporto hardware per la memorizzazione delle mappe di indirizzamento.
Indirizzamento
Per consentire la riallocabilità del codice in porzioni diverse della memoria, bisogna considerare separatamente gli indirizzi logici e gli indirizzi fisici.
L'indirizzamento logico è quello con il quale lavora la CPU e si riferisce ad indirizzi virtuali. L'indirizzamento fisico, invece, è quello usato dalla memoria per indirizzare singolarmente tutte le celle della memoria.
I due indirizzamenti coincidono per gli schemi di memoria a caricamento e a compilazione, risultano invece separati per gli indirizzamenti che variano con l'esecuzione.
L'apparato hardware che si occupa di trasformare gli indirizzi logici in indirizzi fisici si chiama Memory-Management Unit (MMU). In uno schema di memoria basato su MMU viene attribuito un offset (che rappresenta la differenza tra l'indirizzo logico e l'indirizzo fisico) ad ogni processo in esecuzione. In questo modo, nel momento in cui viene richiesto un nuovo indirizzo logico, esso viene convertito in indirizzo fisico prima di essere utilizzato. Grazie a questa tecnica, il programmatore, non vede mai la memoria reale, ma solo quella virtuale.
Caricamento dinamico
Questa tecnica è utile per migliorare l'utilizzo della memoria. Il caricamento dinamico prevede che un processo non venga caricato totalmente in memoria principale al momento dell'attivazione del processo, ma venga caricata solo la parte corrispondente al programma principale. In questo modo è ottimizzato lo spazio occupato di memoria e il tempo di caricamento iniziale risulta molto basso.
Nel caso però di continue chiamate a funzioni esterne, sono necessari lunghi tempi di caricamento anche durante l'esecuzione del programma. Questa soluzione è, quindi, utilizzabile solo quando sono presenti nel codice lunghe funzioni poco utilizzate.
Questo tipo di caricamento è supportato da molti sistemi operativi, i quali mettono a disposizione del programmatore alcune chiamate di sistema specifiche per gestire questa funzionalità.
Indirizzamento dinamico
In questo caso la fase di indirizzamento è rimandata fino al momento dell'esecuzione. Piccole parti di codice, dette stub, sono utilizzate per segnalare le locazioni di memoria. Gli stub vengono sostituiti con l'indirizzo corretto, nel momento in cui il processo è chiamato.
In questo caso è necessario che il sistema operativo controlli se la routine specificata dal processo sia presente in memoria. L'indirizzamento dinamico è molto usato per le librerie di sistema, in modo tale che le librerie possano essere condivise fra più processi, senza dover essere ogni volta ricaricate in memoria. Inoltre è possibile anche conservare in memoria più versioni della stessa libreria, caricando più copie e mettendo negli stub anche le informazioni relative alla versione da utilizzare.
Se per il caricamento dinamico è sufficiente per il sistema operativo mettere a disposizione del programmatore alcune chiamate di sistema, la gestione dell'indirizzamento dinamico risulta essere più complessa: le aree di memoria condivise devono poter essere accedute da processi diversi con spazi di indirizzamento diversi.
Swapping
A volte è utile trasferire temporaneamente un processo che si trova in memoria RAM fuori da essa, per poi recuperarlo in modo rapido (tecnica dello swapping).
Esistono dei dischi molto veloci (backing store), abbastanza grossi da poter contenere la copia di tutta la memoria secondaria. E' possibile accedere direttamente a queste "immagini" della memoria, a fronte di un tempo di accesso maggiore.
Esistono diverse strategie per la gestione efficiente dello swapping.
La strategia più semplice prevede di ciclare a turno i processi attivi (round robin). Altre strategie si basano sulla priorità definita dallo scheduler dei processi: in questo modo è possibile swappare i processi a priorità più bassa, in modo da eseguire in breve tempo i processi a priorità maggiore.
Normalmente i sistemi mantengono una coda di processi pronti ad essere eseguiti, cioè quelli che hanno un'immagine nella backing store.
Lo swapping è implementato in molti OS in modo differente.
Nei sistemi UNIX, esso si attiva solo nel caso in cui la porzione di memoria principale utilizzata da tutti i processi risulta essere maggiore di una certa soglia.
Nei primi sistemi Windows (3.1), invece, lo swapping si avviava nel momento in cui si tentava di eseguire un processo che non stava totalmente in memoria principale.
Allocazione contigua della memoria
La memoria principale è spesso divisa in due partizioni:
-una parte destinata al sistema operativo e alla interrupt vector table
-una parte destinata ai processi utente.
La rilocazione dei registri usata è fatta in modo da proteggere ogni processo da tutti gli altri e da distinguere codice e dati. Le tecniche utilizzate per ottenere questo risultato sono:
-un registro base nel processore che contiene l'indirizzo fisico più piccolo a cui si può accedere
-un registro limite che contiene l'indirizzo massimo a cui si può accedere.
-la MMU mappa gli indirizzi logici dinamicamente.
Nel momento della traduzione da indirizzo logico a indirizzo fisico si verifica che il valore a cui si vuole accedere sia minore del registro limite. A questo punto si somma il valore contenuto nel registro di rilocazione e si produce l'indirizzo fisico. Nel caso in cui una delle condizioni precedenti non venga rispettata si origina una trap (addressing error).
Per allocare la memoria disponibile esistono diverse tecniche.
La più semplice consiste nel partizionare la memoria principale in blocchi di ugual grandezza atti a contenere un intero processo. Il grado di concorrenza dei processi è quindi fissato a priori. Questa tecnica era utilizzata nei primi OS, come IBM OS/360.
Un metodo di allocazione più usato rispetto al precedente prevede che la lunghezza delle partizioni non sia fissa a priori. Nel momento in cui viene caricato un processo, viene occupato in memoria lo spazio necessario al processo e si assegnano i registri base e limite in modo dinamico. In questo modo, quando i processi terminano si vengono a creare dei buchi (detti hole), di varie dimensione. Gli hole contigui vengono uniti in un solo hole. Nel momento in cui nuovi processi debbano essere caricati in memoria viene occupato un hole abbastanza grande da contenerlo.
Per implementare questo metodo è necessario che il sistema operativo deve tenga traccia di tutti gli hole disponibili.
Nel caso in cui ci siano più hole abbastanza grandi da contenere un determinato processo, esistono diverse tecniche per sceglierlo:
-first fit, viene utilizzato il primo hole abbastanza grande da contenere il processo.
-best fit, viene utilizzato il più piccolo hole abbastanza grande da contenere il processo.
-worst fit, viene utilizzato il più grande hole disponibile.
Frammentazione
Esistono due tipi di frammentazione:
-frammentazione esterna. Lo spazio disponibile della memoria consentirebbe il caricamento di un processo, ma poiché lo spazio non è contiguo, non è possibile procedere con il caricamento.
-frammentazione interna. La memoria allocata potrebbe essere maggiore rispetto a quella realmente utilizzata da un processo. Questa parte di memoria non può essere utilizzata da altri processi.
Per ridurre la frammentazione esterna esistono il metodo della compattazione. Esso prevede che la memoria usata venga compattata in un unico blocco contiguo. La compattazione è possibile solo se l'allocazione della memoria è dinamica, poiché lo spazio di indirizzamento di ciascun processo viene modificato durante questa operazione. La compattazione crea alcuni problemi di I/O e risulta essere molto onerosa in termini di tempo.
Altre soluzioni alla frammentazione esterna sono la paginazione e la segmentazione.
| Succ. > |
|---|






