:: Sistemi Operativi - Lezione del 1° Aprile 2008 BIS ::
Torna alla pagina di Sistemi Operativi
E via che si riprende... stavolta si parte dalla gestione della memoria... per piacere non urlate così forte per l'entusiasmo...
Lezione 1 - Indirizzamento in memoria centrale
Il bus degli indirizzi
Il problema da cui tutto nasce è che occorre far apparire la memoria in un modo diverso rispetto a come essa veramente è.
L'indirizzo fisico è l'indirizzo nella memoria centrale: ogni parola di memoria ha un 1 indirizzo che la identifica univocamente, ed è quello a cui la CPU può accedere direttamente tramite il bus degli indirizzi.
Un processo, in generale, occupa 1 porzione di memoria che va da 1 addr. fisico ad un altro addr. fisico. Ma il processo non vede QUESTO: lui vede uno spazio logico che va da 0 a X. Al processo non interessa sapere che ci sono altri processi in giro per il sistema, che anche loro condividono la RAM etc.
Serve pertanto un modo per mappare gli indirizzi logici in indirizzi fisici. Per esempio, l'indirizzo 100 del programma (partendo da 0) potrebbe essere l'indirizzo 34AF della memoria, e cose del genere.
La formuletta è:
addr.fisico = addr.logico + offset
dove offset è lo spiazzamento tra lo 0 logico del processo e la mappatura effettiva in RAM di quello 0.
Nella fase di linking dei programmi, viene eseguito il calcolo degli address logici: da indirizzo simbolico (call funzione) si passa all'indirizzo logico.
Nella fase di binding si passa dall'indirizzo logico a quello fisico.
La fase di binding può avvenire durante la compilazione, oppure durante la fase di loading del programma, o anche durante l'esecuzione.
Se ho librerie dinamiche, effettuo il binding durante la fase di esecuzione, perché non so a priori dove le librerie dinamiche siano state caricate.
Collegamento in fase di compilazione
È statico, in posizione fissa. Infatti, devo sapere già quando compilo il programma dove esso andrà a finire una volta che sarà diventato un processo.
Una volta calcolati gli indirizzi, questi sono immutabili.
Collegamento in fase di caricamento
Avviene una rilocazione del codice durante il caricamento, cioè il SO decide dove caricare il processo in memoria, e da quella locazione partirà lo 0 relativo del processo, in base al quale calcolerò tutte le altre posizioni in base al loro offset.
In questo modo, posso caricare il programma dove voglio, che tanto uso l'offset relativamente all'indirizzo di base.
Ciononostante, il caricamento è ancora di tipo statico: posso piazzare il codice dove voglio, ma una volta che è lì, lì deve rimanere. Cmq ho il vantaggio di poter già caricare 2 istanze dello stesso programma, visto che non ho indirizzi statici a cui obbedire.
Collegamento in fase di esecuzione
Se voglio fare ciò, ho bisogno di una MMU configurata propriamente. In questo modo posso spostare pezzi di codice qua e là durante l'esecuzione, e nessuno ne soffre.
Si tratta di un caricamento statico rilocabile.
Ma...
...chi mi obbliga a caricare tutto il programma in memoria centrale? Magari ci sono parti che vengono utilizzate molto raramente etc. e quindi sarebbe uno spreco.
Il caricamento dinamico mi permette quindi di caricare cose diverse durante l'esecuzione di un programma, solo quando esse mi servono.
In questo modo posso evitare di caricare 10 volte la stessa libreria, se 10 processi diversi ne fanno uso: la carico una sola volta e dinamicamente la collego al processo che la utilizza.
Lezione 2 - Partizionamento
Lo spazio di indirizzamento della CPU è l'insieme di tutte le parole cui la CPU può accedere direttamente. Ma il più delle volte lo spazio fisico è di gran lunga inferiore. E questa memoria va ripartita tra SO e processi vari, e protetta.
Come ottengo tutto ciò, visto che è necessario per la multiprogrammazione e quindi per il multitasking?
L'idea principale è quella di creare partizioni di memoria, e poi distribuirle: tu al SO, tu a questo processo, tu a quell'altro.
Per tenere traccia di chi occupa quale spazio, posso dividere in modo statico la mia memoria in tot partizioni, e in una tabella segnarmi dove una finisce e dove inizia l'altra.
Oppure, posso gestire il tutto in modo dinamico, se ho partizioni che possono avere dimensioni variabili.
Quale che sia il metodo che uso, insorge il problema della frammentazione, perché quando levo un programma di grosse dimensioni dalla memoria, e al suo posto ne metto uno più piccolo, lo spazio che avanza va sprecato, e potrebbe essere comunque tanto.
Riassumendo
Le caratteristiche del partizionamento sono:
- multiprogrammazione con protezione
- non si aumenta lo spazio di memoria centrale complessivo
- deve essere realizzato dal SO
Lezione 3 - Overlaying
Il limite dello spazio fisico è pesante. Infatti, se ho poca memoria potrei non essere in grado di usare un processo ciccione. Se voglio evitare ciò, e permettere anche ai grassi di essere eseguiti, devo trovare un altro sistema, anche se va potenzialmente a discapito delle prestazioni.
Per ottenere ciò, nei tempi che furono si è pensato alla tecnica dell'overlaying. L'idea di fondo è che carico in memoria solo le parti di programma che mi servono. Quelle che non mi servono, le carico dopo. E tutte queste parti, le carico nello stesso spazio in memoria.
Se eseguo le funzioni di una certa parte del mio programma, caricherò quella parte di programma in memoria. Quando mi rivolgerò ad un'altra parte di programma, quell'altra parte verrà caricata al posto della prima, e così via.
Così facendo lo spazio di indirizzamento logico viene mappato in uno spazio di indirizzamento fisico di dimensioni inferiori.
I problemi di sta tecnica sono che:
- deve pensarci il programmatore a definire gli overlay;
- se ho moduli di dimensione diversa, ho comunque uno spreco quando carico un modulo piccolo, perché lo spazio riservato deve essere grande cmq per contenere il modulo più grosso.
Certo, poi sarà il compilatore a provvedere al caricatore di overlay etc. ma deve essere il programmatore che divide il suo programma in overlay diversi.
Posso anche avere degli overlay gerarchici, in cui overlay grossi contengono più overlay piccoli etc. Naturalmente, tutto a carico del programmatore, il che NON è un bene.
Lezione 4 - Swapping
L'idea di partizionare la memoria va bene, ma è utile tenere in memoria anche i processi che non stanno facendo niente? Eh no, è uno spreco.
Ecco quidi che tolgo dalla memoria i processi in stato di attesa e faccio posto a processi ready o running. Togliendoli dalla memoria, occorre salvare tutti i loro dati sensibili, tranne il codice che tanto quello lo posso sempre ricaricare.
È possibile anche decidere di cacciare dalla memoria i processi che sono pronti, e magari far posto a processi con priorità più alta.
Questo sistema
- aumenta la multiprogrammazione...
- ...ma non aumenta lo spazio fisico
- è gestito dal SO
- è potenzialmente lento, quando si copiano processi da e verso memoria secondaria
Torna alla pagina di Sistemi Operativi