S I S T E MI OPERATIVI Sistemi operativi Processi 2 Componenti di un sistema operativo 3 Tipologie di un sistema operativo 4 Nucleo di un sistema operativo Caratteristiche dei processi 7 Stati dei processi 8 Scheduler e Dispatcher 10 Competizione e cooperazione 14 Gestione dell’I/O Classificazione dei device 22 Gestione dei dischi 24 Gestione della memoria centrale Allocazione contigua 31 Allocazione non contigua 33 Memoria virtuale 34 Gestione del File System Directory 43 Allocazione 48 FAT 52 Glossario A.C. Neve – Sistemi operativi 3.0 1 Sistemi Operativi Un sistema operativo è un complesso insieme di programmi che consente il buon funzionamento di un elaboratore. Il sistema operativo agisce da intermediario fra l’utente e la struttura fisica del calcolatore. Esso ha il compito di gestire e controllare le risorse del sistema (l’hardware ed il software) fornendo un ambiente di lavoro di semplice uso, stabile ed indipendente dall’hardware. Il sistema operativo deve quindi realizzare politiche di gestione delle risorse hardware al fine di: 1. regolamentare l’impiego delle risorse evitando conflitti di accesso, 2. scegliere i criteri con cui assegnare una risorsa a fronte di più richieste contemporanee, a causa della limitata disponibilità di risorse, 3. nascondere all’utente i dettagli hardware legati al particolare dispositivo. Processi Un processo rappresenta un programma in esecuzione. L’esecuzione di un programma richiede un flusso di varie operazioni compiute dalla CPU nelle quali, l’esecuzione delle specifiche istruzioni del programma sono solo una parte. Un processo comprende anche tutte le risorse necessarie per la sua esecuzione (programm counter, stack, dati, device, ecc.) Molto spesso, l’esecuzione di un programma richiede l’utilizzo di più risorse che vengono attivate per mezzo di altri processi. L’attivazione di questi altri processi viene gestita dal sistema operativo. Dato che la CPU può eseguire un solo processo per volta, sarà necessario sospendere il processo in corso per attivare il nuovo. Questa commutazione, che è detta cambio di contesto, viene eseguita dal sistema operativo. Il tempo di CPU è quindi in parte dedicato ai processi utente ed in parte ai processi di sistema operativo. Il tempo necessario al cambio di contesto è detto overhead ed essendo tempo sprecato, dovrà essere ridotto al minimo. Programma (entità statica) e processo (entità dinamica) non sono quindi la stessa cosa. Tutto il software attivo su un computer è organizzato in un insieme di processi sequenziali attivabili uno per volta. Per poter sospendere e poi riattivare un processo è necessario poter ricordare le condizioni in cui si trovava all’atto della sospensione. Questo insieme di informazioni è chiamato stato del processo ed è costituito da tutte le informazioni necessarie per poter riprendere l’esecuzione dopo una sospensione. Il PCB (Process Control Block) memorizza queste informazioni Thread Ci sono operazioni che vengono svolte sequenzialmente e che potrebbero essere svolte concorrentemente riducendo così i tempi con la conseguente ottimizzazione delle prestazioni del sistema. L'ideale sarebbe creare processi eseguibili in parallelo e che condividano risorse in comune ma che siano strutturati in modo da rendere i tempi di cambiamento di contesto più brevi. Si introduce quindi il concetto di thread. Il thread rappresenta l’unità di base di utilizzo della CPU. Un thread è una parte di un processo che può essere eseguita contemporaneamente ad altri thread riuscendo così ad aumentare lo pseudoparallelismo senza aumentare eccessivamente l’overhead. Un processo è costituito da almeno un thread che viene creato automaticamente all’avvio del processo. Un thread può creare altri thread e così via. I thread generalmente condividono tutto il codice del processo e lo spazio dei dati delle variabili, ciò implica che la commutazione tra i thread risulterà molto veloce. Un processo può creare dei thread ma non viceversa. A.C. Neve – Sistemi operativi 3.0 2 Componenti di un SO Gestore dei processi Gestore degli I/O Gestore della memoria Gestore del file system Gestore del networking Il gestore dei processi si occupa di tutte le operazioni che coinvolgono i processi. Un processo è un programma in esecuzione che per svolgere i suoi compiti necessita di alcune risorse (CPU, memoria, file, dispositivi di I/O). Il sistema operativo si occupa di mandare un programma in esecuzione ed allocare le risorse utilizzate. Il sistema operativo quindi: - crea e cancella i processi utente e di sistema, - sospende e ripristina i processi, - fornisce meccanismi per la sincronizzazione dei processi, - fornisce meccanismi per la comunicazione tra i processi, - fornisce meccanismi per la gestione dei deadlock. Il sistema operativo deve nascondere all’utente le caratteristiche degli specifici dispositivi hardware. Solo il driver dello specifico dispositivo fisico conosce le caratteristiche dello stesso. Il sistema operativo realizza un’astrazione dei dispositivi di I/O definendo un’interfaccia comune che sfrutta i servizi dei driver (sottosistema I/O), che è composto da: - componente per la gestione della memoria di un buffering, latching e spooling, - un’interfaccia generale per i driver dei dispositivi, - i driver per gli specifici dispositivi. Prima di eseguire un programma deve essergli allocata la memoria: il programma deve essere associato a indirizzi assoluti e caricato in memoria; tali indirizzi vengono generati dalla CPU. Se ci sono più programmi che richiedono l’elaborazione occorre scegliere quale mandare in esecuzione tenendo conto della memoria totale disponibile e di quella richiesta. Quando il programma termina il sistema operativo gli revoca la memoria rendendola disponibile per l’esecuzione di altri programmi. In particolare deve: - tener traccia della memoria disponibile, - selezionare i processi da caricare in memoria, - assegnare e revocare la memoria ai processi, - memorizzare per ogni processo la memoria allocata I dispositivi di memorizzazione secondaria sono molteplici ed ognuno ha proprie caratteristiche ed una organizzazione fisica diversa. Il sistema operativo si occupa di semplificare i compiti dell’utente e traduce le sue richieste per i dispositivi fisici. Riguardo alla gestione dei file è responsabile di: - creare e cancellare file e directory, - fornire all’utente le funzioni fondamentali per la manipolazione di file e directory, - creare una associazione tra i file e i corrispondenti dispositivi di memoria secondaria, - mantenere un back up dei file su dispositivi di memorizzazione stabili, non volatili. In un sistema distribuito le unità di elaborazione sono collegate da una rete di comunicazione. I calcolatori che ne fanno parte condividono le risorse. Quindi il sistema operativo deve gestire l’accesso a queste ultime e gestire la comunicazione con processi remoti. Per far questo è dotato di A.C. Neve – Sistemi operativi 3.0 3 Gestore della sicurezza Interfaccia utenti un dispositivo di interfaccia con la rete (dispositivi di I/O) e sviluppa la parte dei protocolli di rete nel sistema operativo stesso. Se un sistema di calcolo ha più utenti e consente che più processi siano eseguiti in modo concorrente, i diversi processi devono essere protetti dall’attività di altri processi e gli utenti possono accedere solo alle risorse di cui dispongono i permessi. Per questo motivo esistono meccanismi che assicurano che i file, i segmenti della memoria, la CPU e altre risorse possano essere controllate solo dai processi che hanno ricevuto l’autorizzazione dal sistema operativo. E’ compito del sistema operativo amministrare la sicurezza del sistema nel modo più opportuno. La UI oppure GUI è l’interfaccia tra il sistema operativo e l’utente trasformando le indicazioni dell’utente in comandi per il sistema operativo senza doverne gestire tutti i dettagli. Tipologie dei sistemi operativi batch single user single task single user multi tasking multi user I sistemi di tipo batch prevedono l’esecuzione di un programma alla volta sempre residente nella memoria: il calcolatore legge un programma, lo carica in memoria e lo esegue fino alla fine o fino ad un errore, poi stampa il risultato. Quindi passa ad un nuovo programma: lettura, memorizzazione, esecuzione, stampa. E così via per tutti i programmi. Il sistema operativo utilizzato è molto semplice: non c’è interazione tra programma e utente, non si devono prendere decisioni su come allocare le risorse, né scegliere il successivo programma da eseguire. Questi sistemi sono poco efficienti e la CPU è spesso inattiva. Prevede un unico utente ed una sola attività in esecuzione. Prevede un unico utente ma la possibilità di eseguire più attività “contemporaneamente”, sono anche detti multiprogrammati. La multiprogrammazione consente di aumentare l’utilizzo della CPU organizzando i lavori in modo tale da mantenerla in continua attività. In questi sistemi sono presenti contemporaneamente in memoria centrale diversi programmi, uno dei quali viene selezionato e mandato in esecuzione. Il processo creato mantiene il controllo della CPU finché non si arresta perché termina o perché in attesa di qualche evento come il completamento di una operazione di I/O. A questo punto la CPU invece di rimanere inattiva viene assegnata ad un altro processo presente in memoria. Se il primo processo non era terminato, quando torna pronto riprende il controllo della CPU continuando la sua esecuzione. La multiprogrammazione ottimizza l’utilizzo delle risorse, in particolare della CPU che viene commutata tra i processi in esecuzione per ridurre i tempi morti Una variante dei sistemi multiprogrammati è rappresentata dai sistemi interattivi (multi user) in cui la CPU viene commutata più velocemente per permettere all’utente di interagire con il programma. Per fare questo (e per dare all’utente l’impressione che l’esecuzione dei programmi sia parallela) si introduce il meccanismo del timesharing (ripartizione del tempo) secondo il quale si commuta la CPU tra i diversi programmi per un quanto di tempo T. Ogni programma viene quindi A.C. Neve – Sistemi operativi 3.0 4 paralleli reti di calcolatori cluster real time eseguito per un tempo minore o uguale a T. Se non ci sono errori che sospendono l’esecuzione prima che sia trascorso il tempo T, il programma viene bloccato ed il controllo della CPU passa ad un altro programma, mentre il precedente viene accodato in una apposita lista. Il tempo impiegato dal sistema operativo per trasferire il controllo da un programma ad un altro è detto overhead e deve essere limitato il più possibile. La scelta dell’intervallo di tempo T dipende dal numero di programmi che devono essere eseguiti: maggiore è il numero di programmi, minore è T. Se T è sufficientemente piccolo l’utente non si accorge del cambiamento di esecuzione. I sistemi paralleli sono dotati di più unità di elaborazione, per questo sono chiamati anche sistemi multiprocesso. I processori che compongono questo tipo di sistemi sono strettamente accoppiati: condividono risorse hardware e tipicamente hanno lo spazio di memoria in comune. Le operazioni sono eseguite in parallelo su più processi; quindi se una solita operazione deve essere eseguita su dati diversi, sfruttando la presenza di più CPU, vengono mandati in esecuzione contemporaneamente processi diversi. Offrono maggiore efficienze e maggiore affidabilità. In una rete di calcolatori il sistema è costituito da più processori come accade nei sistemi paralleli, ma a differenza di questi ultimi, i processori sono dislocati a distanze elevate uno dall’altro. Inoltre nei sistemi paralleli lo spazio di memoria è generalmente in comune (in realtà dipende dalla struttura del sistema) , quindi i diversi processori non sono completamente autonomi. Nelle reti invece i nodi sono indipendenti e possono lavorare da soli. SISTEMI DI RETE: il sistema operativo è supportato da moduli per il networking e da programmi per la comunicazione in rete con altri calcolatori simili. L’utente ha visione di tutte le risorse disponibili, può quindi accedere a macchine remote e copiare file da una macchina all’altra. Ogni macchina ha il proprio sistema operativo locale. SISTEMI DISTRIBUITI: il sistema operativo così organizzato fa apparire la rete come un unico sistema monoprocessore: l’utente manda in esecuzione un programma su una macchina; sarà poi il sistema operativo a decidere su quale macchina verrà realmente eseguito (di solito sceglie il processore meno occupato), quindi l’utente non sa quali risorse vengono utilizzate. Risultano più complessi. I sistemi cluster hanno alcune caratteristiche simili ai sistemi paralleli e altre simili alle reti: sono composti da più nodi autonomi che condividono le risorse (per esempio condividono dispositivi di memoria secondaria – dischi). Questi sistemi sono utilizzati per garantire affidabilità e disponibilità: devono produrre grosse prestazione o essere sempre disponibili (server web). Le richieste vengono gestite da un controllore e smistate ai nodi. Le richieste di uno stesso utente possono essere gestite tutte da uno stesso nodo o da nodi diversi a seconda della politica scelta dal controllore. I sistemi di elaborazione in tempo reale si usano quando è necessario fissare rigidi vincoli temporali per le operazioni della CPU o per il flusso di dati. In questi sistemi tutte le elaborazioni devono essere eseguite entro un limite massimo temporale, altrimenti i risultati non risultano più attendibili. A.C. Neve – Sistemi operativi 3.0 5 embedded mobile transaction processing HARD REAL-TIME: i compiti critici devono essere completati in un dato intervallo di tempo; il mancato rispetto del vincolo temporale produce effetti catastrofici (la definizione di effetti catastrofici è relativa). SOFT REAL-TIME: il mancato rispetto del vincolo temporale non ha conseguenze disastrose (per es. sistemi multimediali, realtà virtuale). I sistemi embedded vengono eseguiti su calcolatori che controllano dispositivi che non sono generalmente veri e propri sistemi di elaborazione (per esempio televisori, lavatrici, forni a microonde, telefoni cellulari). Il sistema in questo caso è ottimizzato per la specifica applicazione, per questo è in contraddizione con i general purpuse i quali sono progettati per svolgere funzioni diverse. Il sistema operativo risulta quindi semplice: si limita ad interpretare i comandi, gestire la memorizzazione dei dati sui file e attivare i programmi applicativi. I sistemi embedded spesso hanno esigenze di real-time, ma hanno anche problemi di limitazioni di memoria (non c’è la virtualizzazione delle risorse) e di alimentazione I sistemi mobili comprendono palmari, notebook, cellulari che possono connettersi alle reti. La dimensione ridotta di questi dispositivi rende il sistema che li realizza particolare: Per ridurre al minimo le dimensioni dei portatili si utilizzano risorse hardware inferiori ai normali pc. La memoria disponibile è limitata ed a volte non è realizzata la virtualizzazione della memoria; il sistema operativo deve quindi gestire la memoria in modo efficiente. L’unità di elaborazione è lenta, perché una maggiore frequenza della CPU comporta un maggior consumo. Quindi il sistema risulta avere una minor potenza di calcolo. Perché questi dispositivi siano portatili le batterie devono essere leggere e non alimentate da rete. Tutte le risorse sono progettate in modo da consumare il meno possibile e utilizzate nel modo più efficiente, compatibilmente al minor consumo. Per transazione si intende una operazione le cui varie fasi devono avvenire tutte con successo altrimenti l’operazione stessa deve essere annullata in tutte le sue fasi. Un esempio sono le transazioni bancarie. Si evidenzia il fatto che, la classificazione ora proposta non definisce le tipologie in modo mutuamente esclusivo in quanto possono esistere sistemi operativi le cui caratteristiche soddisfano contemporaneamente più di una delle tipologie esposte. A.C. Neve – Sistemi operativi 3.0 6 Caratteristiche dei processi Locality Uno degli aspetti più apprezzati di un processo è la velocità di accesso al codice eseguibile e la relativa disponibilità dello stesso nei vari livelli di memoria. Il concetto di locality si riferisce alla possibilità di prevedere il futuro punto di accesso in memoria. Spatial Locality: se un programma accede ad una particolare locazione di memoria, la probabilità che acceda, in un prossimo futuro, a locazioni vicine è molto elevata. Ciò è giustificato dall’esecuzione sequenziale e dall’utilizzo di strutture dati di tipo vettoriale. Temporal Locality: se un programma accede ad una particolare locazione di memoria, è probabile che vi acceda nuovamente in un prossimo futuro. Ciò è giustificato dall’uso frequente nei programmi di tecniche iterative Working set: è la generalizzazione del concetto di locality in ambito di gestione della memoria. Il working set è l’insieme delle pagine di memoria utilizzate nelle ultime unità temporali di lavoro. Boundless Un processo che fa uso frequente di I/O si dice I/O bound. Un processo che fa molta computazione e poco I/O si dice CPU bound. La fase di utilizzo della CPU si dice CPU burst e quella di I/O invece I/O burst. Un processo si dice che è in fase di I/O bound se esegue una operazione di I/O in un tempo uguale o inferiore a 10 unità di tempo. Un processo si dice in fase di CPU bound se esegue 100 o più istruzioni senza operazioni di I/O. Un processo è in fase neutrale se non rientra nelle due precedenti. La conoscenza della tipologia di bound è importante nei sistemi multiprocesso per poter assegnare la CPU o l’I/O ai diversi processi in modo da migliorare l’impiego delle risorse e l’efficienza. Registri e memoria L’accesso ai dati per l’esecuzione delle istruzioni può essere effettuato dai registri o dalla memoria RAM. Nel caso dei registri la velocità di elaborazione è massima. Un processo si dice che è in fase di register intensive se esegue 100 o più istruzioni senza accedere alla RAM mentre è in fase RAM intensive se accede intensivamente alla RAM Foreground/background I processi foreground sono gestiti e quindi interagiscono con gli utenti mentre i processi background sono in stato di sospensione pronti però ad attivarsi per fornire servizi. A.C. Neve – Sistemi operativi 3.0 7 Stati dei processi Process Control Block In un sistema operativo, un processo è rappresentato da una struttura dati detta Process Control Block (PCB) o descrittore del processo. stack del processo puntatore numero del processo contatore di programma registri limiti di memoria elenco dei file aperti ... Il PCB contiene le seguenti informazioni: program counter area di stack per il salvataggio dei registri di stato e general purpuse stato di avanzamento del processo identificatore del processo puntatori ai processi dipendenti livello di priorità informazioni per il memory management informazioni per lo scheduling del processo informazioni sull’accounting del processo informazioni sull’I/O del processo Il PCB è quindi la struttura dati mediante la quale il sistema operativo gestisce un processo e ne rappresenta lo stato globale. Stati di un processo Lo stato di un processo consiste in tutte le informazioni necessarie per riprendere l’esecuzione dopo una temporanea sospensione. Un processo si evolve attraverso una serie di stati di avanzamento discreti: • Stato iniziale: NEW. Quando si manda in esecuzione un programma si crea un processo ed il relativo descrittore. Il programma creato si trova nello stato new. I campi del PCB vengono inizializzati con dei valori iniziali appropriati e il processo viene inserito in una coda. Si crea anche un identificatore (PID) che serve al sistema operativo per identificare quel processo. Il processo creato non ha ancora la memoria a sua disposizione, quindi non è ancora in esecuzione. Esistono più processi in stato new raggruppati in una lista da cui verranno scelti in base alla politica dello schedulatore. Il programma verrà poi selezionato per l’assegnazione della memoria. • Stato READY. Quando un processo che si trova nello stato iniziale viene scelto dallo schedulatore, gli viene associata la memoria e passa nello stato ready, in attesa di essere assegnato ad una unità di elaborazione (il processo viene inserito nella lista dei processi pronti). Oltre a modificare opportunamente il campo stato, operazione che avviene ad ogni cambiamento di stato, nel descrittore si inseriscono anche le informazioni sulla memoria allocata. Il processo è pronto per essere mandato in esecuzione e possiede tutte le risorse necessarie tranne la CPU. • Stato RUNNING. Il passaggio dallo stato ready allo stato running avviene quando l’unità di elaborazione è libera. Un processo passa dallo stato ready allo stato running quando un’unità di elaborazione esegue le istruzioni del relativo programma. Poiché esistono più di un processo nello stato ready, la selezione del processo da mandare in esecuzione avviene in base alla politica adottata dal sistema. Una componente del sistema operativo (dispatcher) commuta il contesto: copia i valori attuali della CPU nel descrittore del processo uscente e carica i registri della CPU con i nuovi valori prelevati dal descrittore del processo entrante. Il processo uscente viene inserito nella lista dei processi pronti. Da questo stato si può tornare allo stato ready per effetto di una interruzione (per esempio nei sistema timesharing trascorso il quanto temporale a disposizione di un processo si ha un cambiamento di contesto), oppure se il processo richiede operazioni di I/O si passa allo stato di waiting, infine se il processo è terminato passa nello stato terminate. A.C. Neve – Sistemi operativi 3.0 8 • Stato WAITING. Quando i processi richiedono operazioni che non coinvolgono l’utilizzo della CPU (per esempio una operazione di I/O) vengono messi nello stato waiting in attesa che l’operazione termini (intanto la CPU passa ad eseguire un altro processo); quindi torna in READY in attesa che sia di nuovo il suo turno. Un processo può andare in stato WAITING anche per problemi diversi dalle chiamate di I/O, per esempio per attese di particolari eventi. • Stato TERMINATE. Il processo può terminare in modo anomalo, mentre è in stato RUNNING, o normale. Il processore aggiunge alla fine del programma una chiamata di sistema che lo fa terminare. Tutte le risorse vengono liberate, eventuali file aperti vengono chiusi dal sistema operativo, la memoria allocata torna libera e tutte le risorse revocate. Il descrittore di processo viene liberato e messo in una coda in attesa di essere allocato per un altro processo. ammesso uscita new terminate interruzione ready running dispatch completamento di un I/O o verificarsi di un evento attesa di un I/O o di un evento waiting Operazioni sui processi CREATE I principali eventi che possono determinare la creazione di un processo sono: avviamento ed inizializzazione del sistema un processo in esecuzione invoca una system call creation un utente richiede la creazione di un nuovo processo FORK Un processo può creare un nuovo processo per mezzo della procedura fork. Il processo creatore viene detto parent mentre quello creato è detto child. In questa gerarchia, un parent può avere più child ma un child può avere un solo parent. JOIN La join è usata per unificare due sequenze di codice sdoppiate da una fork. E’ utile al processo parent per potersi sincronizzare con il processo child. ABORT Determina la terminazione forzata di un processo ed è usata per rimuovere un processo malfunzionante. Crea anche delle informazioni sui motivi del malfunzionamento. SUSPEND Un processo può sospendere se stesso o un altro processo secondo le priorità. RESUME Riattiva un processo sospeso DELAY Gestisce i time out cioè il tempo di attesa di un certo evento, scaduto il quale, il processo riprende il controllo. TERMINATE Rappresenta la conclusione di un processo. Questo può avvenire in più modi: Normal exit: quando il processo si conclude regolarmente Error exit: quando si verifica un errore parametrico Fatal error: quando si verifica un grave errore che impedisce la prosecuzione Killed: quando un altro processo ne chiede la conclusione A.C. Neve – Sistemi operativi 3.0 9 Thread Si è già detto che un thread rappresenta l’unità di base di utilizzo della CPU che viene individuato e schedulato per l’esecuzione da parte della CPU. Un thread è caratterizzato dal Program Counter, un insieme di registri ed una area di stack. Il thread è quindi più leggero da gestire rispetto ai normali processi, in particolare riduce l’overhead. Nella programmazione multithread, ogni processo è composto da singoli thread in esecuzione per ogni attività concorrente e che condividono codice, dati e file. Ogni singolo thread contiene i registri e lo stack che definiscono l'attività caratteristica di quel thread. Con l’utilizzo della programmazione a multithread i tempi di commutazione di contesto si riducono notevolmente. Quando si commuta il contesto, il salvataggio dello stato del vecchio thread ed il caricamento dello stato del nuovo thread (all’interno di uno stesso processo) riguarda solo i registri e lo stack dato gli altri dati sono in comune. I vantaggi della programmazione multithread sono: • • • • Tempi di risposta più brevi. Un programma può continuare la sua elaborazione anche quando un thread che lo compone è bloccato o sta eseguendo una operazione particolarmente lunga. Ottimizzazione della condivisione delle risorse. Un’applicazione può avere molti thread di attività diverse, tutti nello stesso spazio di indirizzamento, grazie alla condivisione del codice. Aumento del grado di parallelismo nei sistemi con più unità di elaborazione. Nelle architetture a singola CPU i thread si scambiano il controllo della CPU molto velocemente, creando l’illusione di una esecuzione parallela, in realtà si esegue un thread alla volta. Tempi più brevi derivanti dalla condivisione delle risorse dei thread. Nei thread, i tempi di cambio di contesto sono inferiori rispetto alla creazione ed allocazione della memoria ai processi. Quindi è conveniente creare un solo processo composto da tanti thread invece di creare tanti processi indipendenti. Scheduler e Dispatcher Si intende per scheduling il criterio di individuazione, di uno tra più richiedenti, al quale assegnare l’esecuzione di un sevizio. Il concetto di scheduling risulta essere alquanto ampio e pertanto ne vengono definiti tre livelli: Long Term Scheduling (LTS): Regola l’accesso dei programmi al sistema per la loro esecuzione e quindi controlla il grado di multiprogrammazione per cercare di massimizzarlo. Suddivide i processi in gruppi bilanciati nell’uso di CPU ed I/O per poi sottoporli all’STS. Medium Term Scheduling (MTS): Riguarda solo una parte dei processi che vengono mantenuti in memoria mentre i rimanenti sono trasferiti su disco. Vengono rimossi i processi che sono rimasti in memoria un tempo sufficientemente lungo per caricare un nuovo insieme di processi dal disco. Si tratta di un livello connesso alla gestione della memoria virtuale. Short Term Scheduling (STS): è il normale scheduler di CPU dei processi residenti in memoria ed è quello che sarà in seguito preso in considerazione. Scheduler Il compito dello scheduler è quello di stabilire quale sarà il successivo processo al quale assegnare la CPU per l’esecuzione. La scelta avviene per mezzo di opportuni algoritmi di schedulazione. L’effettiva assegnazione della CPU al processo prescelto è effettuata dal dispatcher . Scheduler e dispatcher operano sulle code di attesa dei processi. A.C. Neve – Sistemi operativi 3.0 10 Dispatcher Il dispatcher è un modulo del sistema operativo che passa effettivamente il controllo della CPU ai processi scelti dallo scheduler. Il dispatcher effettua il cambio di contesto (context switch) assegnando la CPU ad un processo di un utente precedentemente sospeso e riprendendone l’esecuzione dal punto dove era stato sospeso. Il tempo necessario per sospendere un processo ed avviare il successivo è detto tempo di latenza. Context switch Per assicurare ad ogni processo un equo utilizzo della CPU, il sistema operativo usa un Real Time Clock hardware che genera periodicamente un interrupt consentendo una regolare schedulazione dei processi presenti in memoria. Quando la CPU viene assegnata ad un nuovo processo si ha un context switching. I valori dei registri sono salvati nel PCB del processo che viene sospeso ed i registri sono caricati con i valori del nuovo processo. Queste operazioni richiedono un tempo non trascurabile mentre per i thread questa operazione è molto più veloce in quanto è relativa ad una minore quantità di informazioni da commutare. Processo P0 in esecuzione Sistema Operativo Processo P1 interruzione o chiamata del sistema Salva stato in PCB0 scheduler inattivo Ripristina stato da PCB1 inattivo in esecuzione interruzione o chiamata del sistema Salva stato in PCB1 scheduler Ripristina stato da PCB0 inattivo in esecuzione Criteri di scheduling L’obbiettivo dello scheduling è quello di ottimizzare le prestazioni dell’intero sistema ed in modo particolare quello della CPU. Si definiscono quindi alcuni indici di prestazione in base ai quali si determina il comportamento di un sistema. • • Fairness: lo scheduler deve essere imparziale. Ogni processo deve ricevere la sua equa parte di tempo di CPU e nessun processo deve attendere indefinitamente. Equità però non vuol dire uguaglianza ma assegnazione di tempo proporzionale all’importanza ed alla priorità. Policy Enforcement: lo scheduler deve assicurare che le politiche del sistema siano sempre garantite e cioè che i processi ad alta priorità siano sempre eseguiti. A.C. Neve – Sistemi operativi 3.0 11 • • • • • Efficiency: lo scheduler deve sempre mantenere le risorse del sistema impegnate al 100%. Response time: è il tempo che intercorre tra l’istante in cui viene generata una richiesta e l’istante di inizio dell’asservimento. Il suo valore deve essere il più piccolo possibile. Tempo di attesa: rappresenta la somma dei tempi che i processi passano in coda. Throughput: numero dei processi elaborati per unità di tempo. Overhead: percentuale di tempo di CPU usata dallo scheduler e dal dispatcher. Scheduling preemptive e non preemptive Gli algoritmi di schedulazione possono essere divisi in due categorie: preemptive e non preemptive (con prerilascio e senza prerilascio oppure interrompente e non interrompente). Lo scheduling si dice non preemptive se, una volta assegnata la CPU ad processo, questa non può essere tolta al processo in base ad eventi esterni al processo stesso. In questo caso, il context switch si potrà attivate solo se il processo termina o si blocca. Lo scheduling si dice preemptive quando è possibile togliere la CPU ad un processo in relazione ad eventi esterni al processo stesso e cioè può essere sospeso anche se dispone di tutte la risorse necessarie, per es. l’arrivo di un processo con priorità più elevata. Algoritmi di schedulazione First Come First Served (FCFS) Viene anche detto FIFO (First In First Out) ed utilizza la politica secondo la quale il primo arrivato è il primo servito per cui la coda dei processi viene riempita secondo il tempo di arrivo. Questo algoritmo è il più utilizzato, soprattutto per la sua semplicità realizzativa. E’ un algoritmo non premptive per cui un processo al quale viene assegnata la CPU sarà eseguito fino al suo completamento. Si tratta di un algoritmo solo teoricamente equo in quanto un processo lungo e poco importante potrebbe far attendere molto un processo breve ma importante. I cambiamenti di contesto risultano molto veloci, ma i tempi medi di attesa in coda sono elevati. Si consideri questo esempio: Processo P1 P2 P3 Burst Time 24 3 7 I tempi di attesa in coda risultano: TP1 = 0; TP2 = 24; TP3 = 27; Quindi il tempo medio di attesa sarà: (TP1 + TP2 + TP3 )/3 = 17 Il valore ottenuto è elevato: l’algoritmo FCFS non fornisce grandi prestazioni dal punto di vista dell’attesa dei processi in coda. Risulta quindi evidente che questo algoritmo non può essere utilizzato nei sistemi real-time nei quali i processi richiedono risposte in tempi brevi. Shortest Process Next First (SPNF) Viene anche detto SJF (Shortest Job First) ed utilizza la politica secondo la quale il primo processo ad essere eseguito è quello con minor tempo di esecuzione, cioè quello con CPU burst più breve. Se nella coda dei processi pronti ci sono più processi con lo stesso CPU burst, tra questi si sceglie secondo il criterio FCFS. E’ un algoritmo non premptive per cui un processo al quale viene assegnata la CPU sarà eseguito fino al suo completamento. A.C. Neve – Sistemi operativi 3.0 12 La coda dei processi pronti è più complicata rispetto al caso precedente: non è organizzata più in modalità FIFO, ma ordinata in base alla durata della successiva sequenza di operazioni di CPU. L’estrazione risulta quindi immediata (se la coda è ordinata in modo decrescente si estrae il primo), mentre l’inserimento è più complesso. Con questo algoritmo si riducono i tempi medi di attesa dei processi in coda. Considerando l’esempio precedente si ha che: I tempi di attesa in coda risultano: TP1 = 10; TP2 = 0; TP3 = 3; Quindi il tempo medio di attesa sarà: (TP1 + TP2 + TP3 )/3 = 4.5 Il risultato ottenuto è decisamente migliore di quello trovato con l’algoritmo FCFS. Questo algoritmo è ottimale nel senso che rende minimo il tempo di attesa medio per un dato insieme di processi in coda, ma non è realizzabile poiché non è nota a priori la durata della successiva richiesta della CPU da parte di ogni singolo processo. Questo algoritmo non è adeguato per ambienti time sharing al contrario per quelli batch. Round Robin (RR) I processi vengono eseguiti per un intervallo di tempo definito dal sistema (time slice). Dopo tale intervallo il processo viene inserito in fondo alla coda dei processi pronti e la CPU viene assegnata al prossimo processo prelevato dalla testa della coda. Se l’esecuzione del processo termina prima dell’intervallo che gli è stato concesso, il processo stesso rilascia volontariamente la CPU e lo scheduler seleziona il primo processo nella coda dei processi pronti. Quindi l’esecuzione di ogni processo ha una durata massima definita dal quanto di tempo. Questo algoritmo è di tipo preemptive e quindi adatto ai sistemi time sharing. Questo tipo di scheduling è implementato tramite una coda circolare gestita in modalità FIFO e garantisce che tutti i processi vadano in esecuzione, eliminando il problema della starvation (blocco all’infinito). Ogni volta che lo scheduler seleziona un processo dalla coda dei processi pronti (il primo) oltre ad attivare il dispatcher per l’effettiva esecuzione del processo, imposta un timer inizializzato al quanto di tempo specificato dal sistema operativo. Allo scadere del timer il sistema invia un segnare di interruzione che blocca il processo attualmente in esecuzione, per assegnare la CPU ad un altro processo. La scelta del quanto di tempo da dedicare ad ogni processo è fondamentale: se il sistema sceglie un intervallo troppo grande, l’algoritmo degenera nell’FCFS; se l’intervallo è troppo piccolo (al limite 0) degenera nel processor sharing, algoritmo a condivisione di processore, non realizzabile, nel quale i diversi processi hanno l’impressione che ci siano n processori reali ognuno dei quali esegue un processo diverso. Il sistema operativo deve quindi scegliere un intervallo di tempo abbastanza piccolo da garantire l’interattività, cioè deve fare in modo che i processi brevi vengano eseguiti velocemente, ma abbastanza grande in modo da limitare l’overhead. Infatti se il quanto temporale è troppo piccolo il sistema è rallentato dai numerosi cambi di contesto. Quindi l’intervallo deve essere molto maggiore della latenza di dispatcher. Shortest Remaining Time (SRT) Questo algoritmo è la versione preemptive di SPNF ed è quindi idoneo al time sharing. A.C. Neve – Sistemi operativi 3.0 13 L’algoritmo prevede che il successivo processo sia quello con minor tempo stimato per il completamento. Con questo algoritmo si ha un overhead maggiore di SPNF in quanto è necessario determinare ed aggiornare il tempo restante e gestire il preemptive. Priority Scheduling Questo algoritmo assegna ad ogni processo una priorità e viene mandato in esecuzione il processo a priorità maggiore. Nel caso di processi con uguale priorità si opera con FCFS. La priorità può essere assegnata dal sistema operativo o dal programmatore. Nel primo caso si devono individuare delle proprietà misurabili mentre nel secondo caso la priorità del processo è generalmente funzione dell’importanza del processo. Il priority scheduling può essere sia preemptive che non preemptive. Il problema fondamentale di questo algoritmo è la starvation (causato dalla priorità statica): l’attesa di un processo in coda è indefinita poiché dipende dalla priorità di ogni singolo processo. In particolare, l’esecuzione dei processi con bassa priorità può essere impedita da un flusso costante di processi con priorità maggiore. Poiché non c’è garanzia che un processo venga eseguito, si introduce il concetto di aging (si introduce la priorità dinamica): periodicamente il sistema aumenta le priorità dei processi che sono da molto tempo nella coda. In questo modo i processi con CPU burst brevi vengono eseguiti molto più velocemente. Questo concetto è utilizzato in particolare nei sistemi interattivi. Multilevel Queue Scheduling L’algoritmo multilevel queue suddivide la coda in diverse code ognuna delle quali ha uno specifico livello di priorità. I processi vengono assegnati staticamente ad una certa coda in base ad una specifica tipologia di priorità. Ogni coda potrà poi avere un proprio algoritmo di schedulazione. Naturalmente sarà poi necessario effettuare lo scheduling tra le varie code. Competizione e cooperazione Interferenza e sincronizzazione tra processi I processi gestiti dal sistema operativo possono essere indipendenti o cooperanti. I processi indipendenti non scambiano informazioni e l’esecuzione di tali processi non è condiziona dall’esecuzione degli altri. Al contrario, due processi cooperanti si scambiano dati e le esecuzioni sono influenzate l’una dall’altra. La cooperazione è una caratteristica utile per diverse ragioni, in particolare: • Consente la condivisione delle informazioni tra i processi cooperanti: per esempio se due utenti devono condividere codice o dati, i processi devono cooperare. • Aumenta il grado di parallelismo: esistono attività non sequenziali ma costituite da sottoattività che possono essere svolte concorrentemente (per esempio scrittura, stampa e compilazione) e per questo devono cooperare e sincronizzarsi. • Modularizza il sistema: l’organizzazione a moduli semplifica il sistema. Ogni modulo può corrispondere ad un processo o un thread, l’insieme dei quali costituisce un unico programma. A.C. Neve – Sistemi operativi 3.0 14 Per permettere ai processi di cooperare il sistema operativo deve fornire dei meccanismi che implementino la comunicazione e la sincronizzazione delle attività dei processi. In un sistema in cui è ammessa la cooperazione, un processo può influenzare un altro processo in esecuzione causando situazioni incoerenti o dati in uscita non attendibili. I processi cooperanti possono condividere direttamente uno spazio logico di indirizzi, quindi il sistema operativo deve implementare dei meccanismi che assicurino una ordinata esecuzione dei processi stessi. In ambiente multiprocesso i processi possono accedere a risorse condivise, se questi accessi non sono opportunamente controllati si può determinare la contaminazione di dati o conflitti sulle periferiche. E’ quindi necessario impedire che più di un processo possa, simultaneamente, leggere o scrivere stessi dati o usare stesse periferiche. Si parla di race condition quando più processi accedono e manipolano gli stessi dati concorrentemente e l’esito varia a seconda dell’ordine con il quale sono avvenuti gli accessi. Per evitare questa situazione occorre assicurare che un solo processo alla volta possa modificare la variabile condivisa, condizione realizzabile tramite la sincronizzazione dei processi. Per evitare il problema del race condition, i dati e le risorse condivise da più processi devono essere usate e manipolate da un solo processo alla volta. Ogni processo può quindi avere nel suo codice una sezione critica, cioè una parte di programma con la quale può accedere a risorse comuni o modificare dati comuni ad altri processi. Ad un solo processo per volta è consentito eseguire la propria sezione critica: l’esecuzione delle sezioni critiche da parte dei processi deve essere mutuamente esclusiva nel tempo, cioè, quando un processo sta eseguendo una parte di codice a sezione critica, nessun altro processo può fare altrettanto. L’esecuzione delle sezioni critiche deve essere soggetta ad alcune regole: Mutua esclusione: Se un processo sta eseguendo la sua sezione critica, nessun altro processo può eseguire la sua sezione critica. Progresso: Se un processo è nella sua sezione critica ed esiste un processo che vuole entrare nella sua sezione critica, l’esecuzione di quest’ultimo processo non può essere rimandata all’infinito. Attesa limitata: Se un processo ha chiesto di entrare nella sua sezione critica, il numero di volte che si può rinviare questo accesso per concederlo ad altri processi deve essere limitato. E’ quindi evidente che, il primo processo che entra in sezione critica obbligherà tutti gli altri a mettersi in coda di attesa. Esistono diversi metodi per attuare la mutua esclusione. • Un primo metodo prevede la disabilitazione delle interruzioni quando un processo entra in sezione critica e la riabilitazione quando ne esce. Può però essere pericoloso consentire ad un processo utente la gestione delle interruzioni. • Alcuni sistemi operativi hanno la possibilità di commutare dal modo multiprocesso al modo single processing eliminando così la condizione di race condition. Anche in questo caso può essere pericoloso consentire ad un processo utente la gestione di questa commutazione. • Un altro metodo è il test and set che consiste nell’utilizzo di una variabile condivisa detta lock la quale assume valore 1 quando c’è un processo in sezione critica altrimenti vale 0. Se un processo vuole entrare in sezione critica testa la variabile lock: se vale 0 entra in sezione critica e la pone ad 1 altrimenti continua a fare il test finché non la trova a 0. L’inefficienza di questo metodo risiede nel fatto che il processo in coda spenderà molto tempo in un loop di test della variabile lock. A.C. Neve – Sistemi operativi 3.0 15 • Per risolvere il problema della sezione critica si usa uno strumento di sincronizzazione chiamato semaforo. Un semaforo è una variabile intera il cui accesso può avvenire solo tramite due operazioni atomiche standard, wait e signal, (atomico si riferisce al fatto che il flusso di esecuzione delle due operazioni è indivisibile). Inizialmente ogni semaforo viene inizializzato a 1. Quando un processo P1 vuole entrare nella propria sezione critica, esegue la wait sul semaforo. Se nessun altro processo si trova nella propria sezione critica, P1 continua la sua esecuzione dopo aver decrementato il valore del semaforo. Se invece un altro processo P2 sta già eseguendo la sezione critica, P1 viene sospeso. Quando il processo P2 esce dalla sua sezione critica esegue la signal che sblocca il processo P1. Tutte queste operazioni devono essere fatte in un’unica indivisibile azione atomica in modo da garantire che, una volta che un’operazione su un semaforo è cominciata, nessun altro processo possa accedere al semaforo fino a che l’operazione non è completata. L’atomicità è assolutamente essenziale per risolvere i problemi di sincronizzazione. Il problema dello stallo Si intende per sincronizzazione, quell’insieme di meccanismi che consentono ad un programma di sospendersi in attesa di un segnale da parte di un altro processo che gli consenta di riprendere l’avanzamento. Questo problema può essere risolto con l’uso dei semafori. Un insieme di processi si trova in stato di stallo (deadlock) quando ognuno di essi è in attesa di un evento generato da un altro processo che a sua volta si trova nella stessa condizione. Nessuno dei processi può avanzare, nessuno può rilasciare le risorse e nessuno può essere posto nello stato di ready. In sintesi, il sistema è in deadlock poiché ogni processo dispone di una risorsa richiesta da un altro e che non può rilasciare. Anche le risorse possono essere preemptive e non preemptive: una risorsa preemptive può essere sottratta al processo senza conseguenze una risorsa non preemptive non può essere tolta al processo senza conseguenze negative, solo il processo che la detiene può rilasciarla. La riallocazione delle risorse potrebbe evitare il deadlock se le risorse fossero premptive. Si definiscono quattro condizioni il cui simultaneo verificarsi determina un deadlock: • Mutua esclusione: le risorse coinvolte non sono condivisibili, cioè solo un processo può usare la risorsa e quindi si accede per mutua esclusione. • Hold and wait: un processo richiede risorse ed è in attesa di altre risorse. • Non preemptive: le risorse già allocate non sono preemptive (sia dal kernel che dal processo). • Condizione di attesa circolare. I processi del sistema formano quindi una lista circolare nella quale ogni processo è in attesa di una risorsa posseduta dal successivo processo in lista. Naturalmente il deadlock si può verificare solo in presenza di almeno due processi. Esistono diversi modi per gestire il deadlock: Rilevare e recuperare il deadlock: si usa un monitor che rileva l’esistenza di un deadlock ed identifica i processi e le risorse coinvolti. Si disallocano temporaneamente le risorse dei processi A.C. Neve – Sistemi operativi 3.0 16 in deadlock, si riportano questi processi in uno stato precedente al deadlock e si concludono alcuni processi fino a rimuovere il deadlock. Si tratta però di una tecnica molto onerosa. Evitare il deadlock mediante una oculata schedulazione delle risorse. L’algoritmo più noto è quello del Banchiere così chiamato perché simile alla procedura che utilizzano i banchieri per decidere se un prestito può essere o meno concesso senza rischi. L’algoritmo si evolve quindi valutando se il soddisfacimento di una richiesta può condurre in una stato sicuro o in deadlock ed in quest’ultimo caso ritarda la concessione della richiesta. Prevenzione del deadlock: consiste nello schedulare le risorse in modo da evitare almeno una delle quattro condizioni citate in precedenza. Infine si può cercare di evitare la condizione di coda circolare forzando i processi a richiedere le risorse in base ad un ordine crescente o decrescente. In questo modo il grafo di allocazione delle risorse non potrà mai essere circolare. La comunicazione Spesso i processi hanno bisogno di comunicare tra loro. Nasce quindi la necessità che l’interazione tra i processi avvenga in modo ben strutturato, mantenendo alta l’efficienza del sistema. Per realizzare la comunicazione il sistema operativo deve implementare dei meccanismi che consentano a un processo di passare informazioni ad un altro processo in modo sequenzialmente corretto. I processi concorrenti in esecuzione sotto il controllo del sistema operativo, possono essere indipendenti o cooperanti. I processi indipendenti non scambiano informazioni e la loro esecuzione non è condiziona dall’esecuzione degli altri. Due processi cooperanti al contrario si scambiano dati e le esecuzioni sono influenzate l’una dall’altra. Come già detto, la cooperazione è una caratteristica utile per diverse ragioni, in particolare: • Consente la condivisione delle informazioni tra i processi cooperanti; per esempio se due utenti devono condividere codice o dati i processi devono cooperare. • Aumenta il grado di parallelismo; esistono attività non sequenziali ma costituite da sottoattività che possono essere svolte concorrentemente e per questo devono cooperare e sincronizzarsi. • Modularizza il sistema; l’organizzazione a moduli semplifica il sistema. Per permettere ai processi di cooperare il sistema operativo deve fornire dei meccanismi che implementino la comunicazione e la sincronizzazione delle attività dei processi. Esistono due tecniche diverse per lo scambio di dati tra processi: Modello a memoria comune: i due processi condividono una area di memoria comune tramite la quale cooperano. Un processo scrive i dati nell’area comune, l’altro li legge. Modello a scambio di messaggi: modello basato sullo scambio di messaggi tra processi. Il sistema operativo provvede a recapitare i messaggi al destinatario implementando funzioni apposite a questo scopo. Per illustrare il modello a memoria comune si consideri il problema del produttore e del consumatore che è un classico paradigma per processi cooperanti. Un processo produttore produce informazioni che sono consumate da un processo consumatore. A.C. Neve – Sistemi operativi 3.0 17 Per permettere un’esecuzione concorrente dei processi, occorre disporre di un vettore (buffer) in cui il produttore può inserire un elemento mentre il consumatore ne sta prelevando un altro, facendo attenzione alla capacità del vettore. Nascono quindi due problemi: Sincronizzazione tra i processi consumatore e produttore: il prelievo di un dato da parte del consumatore deve avvenire dopo che il produttore lo ha inserito nel buffer. Grandezza dell’area di memoria comune: se la memoria è illimitata, non ci sono limiti alla grandezza del buffer e il produttore può sempre produrre dati; se la memoria è limitata il buffer avrà una lunghezza massima e il produttore prima di inserire un dato nel buffer deve controllare che non sia pieno. In entrambe le situazioni il consumatore, prima di prelevare un dato, deve sempre controllare che il buffer non sia vuoto. Un metodo alternativo con cui il sistema operativo può ottenere gli stessi risultati ottenuti con il modello a memoria comune consiste nel fornire gli strumenti per la comunicazione tra processi, realizzando ciò che comunemente prende il nome di sistema di comunicazione tra processi (IPC: Inter Processing Communication). Il modello IPC è particolarmente utile in un ambiente distribuito dove i processi comunicanti possono risiedere in diversi calcolatori connessi da una rete. Le due operazioni fondamentali che costituiscono il modello IPC e che permettono ai processi di comunicare e sincronizzarsi senza condividere dati sono due: - operazione send(message), - operazione receive(message). I processi che vogliono comunicare con il modello a scambio di messaggi possono farlo grazie alla presenza di un canale di comunicazione, realizzabile in molti modi sia logicamente che fisicamente. Per comunicare i processi devono disporre di un modo con cui riferirsi agli altri processi. A secondo di come si implementa questa caratteristica si distinguono due differenti sistemi: 1. Comunicazione diretta. Con la comunicazione diretta, ogni processo che intenda comunicare deve nominare esplicitamente il ricevente o il trasmittente della comunicazione. Nella comunicazione diretta il canale ha le seguenti caratteristiche: • i canali sono creati automaticamente; • tra ogni coppia di processi esiste un canale • i processi per comunicare devono conoscere solo la reciproca identità; • un canale è associato esattamente a due processi; • il canale può essere unidirezionale o, più spesso, bidirezionale. 2. Comunicazione indiretta. I messaggi vengono inviati a delle mailbox (chiamati anche porte) e da essi ricevuti. La mailbox è un oggetto in cui i processi possono introdurre e da cui possono prelevare i messaggi. Nella comunicazione indiretta due processi possono comunicare solo se hanno una mailbox in comune. A.C. Neve – Sistemi operativi 3.0 18 In questo schema le primitive send e receive hanno il seguente formato: send(A, message); invia il messaggio alla mailbox A; receive(A, message); riceve un messaggio dalla mailbox A. Il canale di comunicazione ha le seguenti caratteristiche: • • • tra una coppia di processi si stabilisce un canale solo se entrambi i processi della coppia condividono una mailbox; un canale può essere associato a più di due processi; tra ogni coppia di processi comunicanti possono esserci più canali diversi, ciascuno corrispondente a una porta. A.C. Neve – Sistemi operativi 3.0 19 Gestione dell’I/O Il sistema operativo si deve occupare della gestione dell’I/O eseguendo i comandi destinati ai dispositivi fisici, rimuovendo le condizioni di errore e risolvendo i problemi dovuti alla diversità delle caratteristiche dei vari dispositivi. A tale scopo fornisce una interfaccia comune a tutti i dispositivi, diposta tra i dispositivi fisici e il resto del sistema. Con la sigla I/O si identificano delle unità fisiche che possono essere: • • • Dispositivi fisici di ingresso uscita (device) Unità di controllo dei device in grado di gestire polling, interrupt o DMA con registri di I/O e buffer (I/O controller) Processori di I/O in grado di alleggerire il lavoro della CPU I compiti che un sistema operativo deve eseguire nella gestione dell’I/O sono: • • • • Stabilire a quale processo assegnare un device e per quanto tempo Mantenere traccia dello stato del device Allocare fisicamente il device al processo Deallocare il device dal processo Sono infine necessari dei componenti software detti device driver i quali sono specifici per ogni device ed operano da interfaccia software tra il sistema operativo ed il device vero e proprio gestendo le operazioni di I/O, le interruzioni e gli errori. I device di I/O sono generalmente costituiti da parti elettromeccaniche con un funzionamento spesso asincrono rispetto alla CPU rendendo così più complessa la loro gestione. L’hardware di I/O è costituito dai dispositivi di I/O e dai relativi controllori. I dispositivi possono differire sotto molti aspetti. Per quanto riguarda il trasferimento dei dati si distinguono: • Trasferimenti a blocchi di dati o a stringhe di caratteri. I dispositivi a blocchi memorizzano le informazioni in blocchi di lunghezza fissa, ognuno dei quali ha un proprio indirizzo ed ogni blocco può essere indirizzato in maniera indipendente dagli altri. I dispositivi a caratteri operano invece su flussi di caratteri. • Trasferimenti seriali o paralleli. I dispositivi seriali inviano o ricevono un bit alla volta, i dispositivi paralleli trasferiscono più bit contemporaneamente. • Trasferimenti sincroni, che hanno tempi di risposta prevedibili, o asincroni, in cui i tempi di risposta non sono prevedibili. • Dispositivi che consentono solo la lettura, solo la scrittura o entrambe le operazioni. Le operazioni di lettura e scrittura vengono eseguite su un blocco o su un carattere a seconda del tipo di dispositivo. La modalità di accesso al dispositivo può essere sequenziale se il trasferimento avviene secondo un ordine fisso, oppure casuale, se si accede direttamente ad una qualunque locazione di memoria. Inoltre si distinguono i dispositivi condivisi, utilizzati da più utenti simultaneamente, da quelli dedicati ai quali può accedere un utente alla volta. La velocità di funzionamento è generalmente diversa per ogni dispositivo. A.C. Neve – Sistemi operativi 3.0 20 Il software deve garantire buone prestazioni su sequenze di dati di diversi ordini di grandezza. La gestione dell’I/O viene suddivisa in tre livelli: • Il primo livello consiste nella definizione delle operazioni di sincronizzazione e trasferimento delle informazioni tra device e processi. Le funzioni di questo livello rendono trasparenti all’utente i meccanismi hardware e software usati. Il trasferimento delle informazioni può avvenire per mezzo dei registri del processore o per mezzo del gestore del DMA. • Il secondo livello consente l’interfacciamento tra i processi ed i device in modo indipendente dalle caratteristiche del device stesso. Tutto ciò avviene per mezzo dello specifico driver del device il quale traduce i comandi provenienti dal software di sistema in comandi e segnali interpretabili dall’hardware del device. • Il terzo livello (detto Virtual File System) realizza una interfaccia uniforme per i processi utenti che potranno così utilizzare un unico sistema di nomi per i device con uno schema di accesso simile a quello dei file. Questo livello comprende anche le politiche mediante le quali il sistema operativo ottimizza le prestazioni dell’utilizzo dei device. Il sistema operativo permette l’aggiunta di nuovi dispositivi e la modifica delle caratteristiche di quelli preesistenti. Poiché ogni dispositivo ha caratteristiche diverse, il sistema astrae dai dettagli dei dispositivi individuando alcuni tipi generali per ognuno dei quali definisce una interfaccia. Le effettive differenze tra i vari dispositivi saranno poi gestite dai driver specifici. Inoltre la parte del sistema operativo che si occupa dell’I/O deve: • Garantire l’indipendenza del sistema dai dispositivi. Il codice del programma dal quale si accede ad un dispositivo deve essere indipendente dal tipo di dispositivo, ovvero il codice per essere eseguito su dispositivi diversi non deve essere modificato. • Gestire una denominazione uniforme dei dispositivi. Un dispositivo deve avere il solito nome (file system o cdrom, o dischetti…). Tutti i file e i dispositivi sono indirizzati nello stesso modo, utilizzando uno specifico path name. • Gestire il trattamento degli errori. La maggior parte degli errori sono temporanei e non si ripresentano se l’operazioni viene ripetuta, quindi possono essere gestiti dal driver di dispositivo, se non riesce il controllore, semplicemente rieseguendo il comando. In ogni caso è preferibile che l’utente finale non si accorga degli errori, quindi devono essere gestiti al livello più basso, più vicino all’hardware. Solo se i livelli più bassi non sono in grado di gestire l’errore vengono avvertiti i livelli alti. • Gestire il trasferimento delle informazioni. Si distingue in trasferimento sincrono (a controllo di programma) e trasferimento asincrono (a controllo di interruzioni o DMA). La maggior parte dell’I/O è asincrono, ma i programmi utenti sono più semplici se le operazioni di I/O sono sincrone. Il sistema deve mascherare all’utente il vero aspetto delle operazioni facendole apparire sincrone. Un’altra funzione del sottosistema di I/O è il buffering: esiste una area della memoria in cui vengono memorizzati i dati trasferiti tra due dispositivi, o tra un’applicazione e un dispositivo. Questa zona è chiamata buffer. Senza la bufferizzazione delle informazioni, un processo viene bloccato in attesa che venga portata a termine una operazione di I/O. Viene sbloccato ogni volta che arriva un dato, quindi torna nello stato bloccato dopo il suo trasferimento. La bufferizzazione viene fatta dal sistema operativo per conto del processo: i dati da trasferire al processo vengono mantenuti in un buffer e solo quando l’operazione di I/O è terminata il processo viene sbloccato e i dati trasferiti tutti in una volta dal buffer al processo stesso. A.C. Neve – Sistemi operativi 3.0 21 La bufferizzazione può essere utile anche per gestire la differenza di velocità di trasferimento dei dati tra processi (per esempio nel caso di processo produttore e processo consumatore). Infine il sottosistema di I/O deve rendere possibile la condivisione delle risorse. Le risorse condivisibili, per esempio il disco, non causano problemi. Esistono però risorse non condivisibili a cui pervengono contemporaneamente più richieste. In questa situazione non è conveniente che la gestione del dispositivo venga svolta dal processo, ma è il sistema operativo che, tramite lo scheduling di I/O, stabilisce un ordine di esecuzione efficace delle richieste di I/O. Per ogni dispositivo esiste una coda delle richieste (device queue); quando una applicazione esegue una system call di I/O bloccante, la richiesta è aggiunta nella coda relativa al dispositivo appropriato. Lo scheduler di I/O riorganizza la coda per migliorare le prestazioni, offrendo una migliore efficienza globale del sistema e una diminuzione del tempo di attesa delle applicazioni. Classificazione dei device I device possono essere classificati per tipo di funzione, tempo di accesso, tipo di accesso, velocità di trasferimento, codifica dei dati, tipologia dei comandi e condizione di errore. In base alla funzione si ha: • Human readable: usati per comunicare con gli utenti (terminali, tastiere, mouse, stampanti, ecc.) • Machine readable: usati per la comunicazione tra altri sistemi elettronici (sensori, attuatori, ecc.) • Communication: usati per comunicare con dispositivi remoti • Memory device: usati per la memorizzazione delle informazioni Il tempo di accesso è definito come l’intervallo di tempo che intercorre tra l’istante di richiesta dell’informazione e l’istante in cui questa è disponibile. In base al tipo di accesso si ha: • Accesso sequenziale: sono device per i quali il tempo di accesso dipende dalla posizione del dato e presenta quindi grandi oscillazioni del suo valore. • Accesso casuale: sono device per i quali il tempo di accesso è costante. • Accesso quasi casuale: sono device per i quali il tempo di accesso presenta una lieve dipendenza dalla posizione del dato. Un’altra classificazione riguarda l’impacchettamento dei dati trasferiti che possono essere a blocchi (hard disk, CD ROM, stampanti) o a carattere (terminali, modem). Per quel che riguarda le velocità di trasferimento, queste possono avere grandi differenze. Struttura del software di I/O Il software di I/O è organizzato in quattro livelli: Livello utente SW di I/O Livello kernel HW di I/O A.C. Neve – Sistemi operativi 3.0 SW I/O LIVELLO UTENTE ⇓ SW I/O INDIPENDENTE DAI DISPOSITIVI ⇓ DRIVER DEI DISPOSITIVI ⇓ DRIVER DELLE INTERRUZIONI HARDWARE 22 • La gestione delle interruzioni creata nell’hardware di I/O è completata dal driver delle interruzioni. La gestione delle interruzioni non è semplice perché comporta l’esecuzione di una serie di operazioni eseguite via software dal driver delle interruzioni, dopo che l’interruzione hardware è stata avviata (nel livello hardware di I/O). • Il driver di un dispositivo comanda l’operazione richiesta (read o write) poi si blocca perché l’operazione di I/O richiede del tempo. Il driver viene bloccato da una interruzione provocata dal driver delle interruzioni. Indipendentemente dall’algoritmo di scheduling scelto si può verificare che, durante l’intervallo di tempo in cui il driver è bloccato, un processo si sblocchi. Al termine dell’operazione di I/O il processo che va in esecuzione è quello con priorità maggiore, mentre l’altro processo viene inserito in una coda di attesa. • Il livello del driver dei dispositivi gestisce la diversità delle caratteristiche dei dispositivi fisici e rende indipendente il livello superiore dai particolari dispositivi. E’ costituito da un insieme di driver, uno per ogni dispositivo presente o uno per una classe di dispositivi con caratteristiche simili. In fase di installazione del dispositivo fisico il sistema operativo carica anche il corrispondente driver. Se il sistema operativo implementasse un unico driver per rappresentare tutti i dispositivi, tale driver dovrebbe raggruppare tutte le diverse caratteristiche che può assumere un dispositivo ed avrebbe dimensioni enormi. Per rendere trasparenti le caratteristiche dei dispositivi, il sistema operativo definisce una interfaccia tra il livello del driver di dispositivo e il livello del software di I/O indipendente dai dispositivi. Tale interfaccia permette ai diversi driver dei dispositivi di fornire al livello superiore un numero limitato di funzioni, del tipo: - “write / read un carattere”, se il dispositivo trasferisce caratteri; - “write / read un blocco”, se il dispositivo trasferisce blocchi. Queste procedure possono essere chiamate dal resto del sistema per interagire con il driver del dispositivo. • L’interfaccia definita a livello utente è generale, composta da funzioni limitate e tutte dello stesso tipo per tutti i dispositivi: il livello del software di I/O indipendente dai dispositivi invia le funzioni al livello del driver di dispositivo che ha il compito gestirle opportunamente. Il driver accetta e traduce il comando di carattere generale in una sequenza di comandi relativi ad un dispositivo, in un linguaggio di più basso livello, adatti per il controllore che il driver gestisce. A questo punto il controllore insieme al driver cominciano ad eseguire l’operazione richiesta: in alcuni casi questa fase risulta veloce, altre volte il completamento delle operazioni comporta una perdita di tempo non trascurabile per il driver di dispositivo che è in attesa attiva. In questa situazione il driver di dispositivo controlla lo stato del dispositivo: • Dispositivo occupato: inserisce la richiesta in attesa in una coda (coda delle richieste pendenti); quando il dispositivo si libera, la scelta della richiesta da servire viene fatta secondo un algoritmo di scheduling. • Dispositivo libero: il driver traduce l’istruzione e invia al dispositivo di I/O il comando, poi si blocca in attesa dell’istruzione di ritorno eseguita dal dispositivo di I/O. A.C. Neve – Sistemi operativi 3.0 23 Gestione dei dischi Gli hard disk rappresentano la più importante tra le unità di I/O di un sistema di elaborazione in primo luogo per la grande quantità di dati che possono conservare e per l’importanza che questi dati possono avere. La parte principale è rappresentata dai file del sistema operativo che vengono movimentati per la gestione di tutti i vari servizi offerti dal sistema operativo stesso. Traccia Testina Braccio Cilindro Traccia Disco Gap Motore Settore Gli hard disk sono costituiti da un insieme di dischi rotanti sullo stesso asse (7200 giri/min) e ricoperti di materiale magnetico. Ogni faccia del disco contiene un insieme di tracce circolari concentriche. Le tracce corrispondenti delle diverse facce costituiscono un cilindro. I dischi vengono letti o scritti da un insieme di testine magnetiche che volano ad una microscopica distanza dalla superfice del disco e sono movimentate da un braccio ad altissima precisione. Le tracce sono suddivise in settori di capacità fissa (da 512 a 4096 byte), i settori consecutivi sono separati da piccoli intervalli non magnetizzati detti gap. I settori del disco rappresentano la minima unità di trasferimento hardware. Via software è possibile definire una nuova unità minima di trasferimento multipla intera del settore che viene denominata cluster. Ogni settore è diviso in tre parti: • Preambolo: contiene le informazioni di controllo (numero della traccia, numero del settore…). • Dati: contiene i dati. • ECC (Error Correcting Code): contiene il codice a correzione di errore. Contiene sufficienti informazioni affinché il controllore possa identificare i bit danneggiati e ricalcolare il loro corretto valore. Preambolo A.C. Neve – Sistemi operativi 3.0 Dati ECC 24 La formattazione del disco prevede cinque fasi: • Inizialmente il disco è vuoto e sarà fisicamente collegato e riconosciuto dal sistema. • Formattazione a basso livello: in questa fase vengono scritti i settori all’interno delle tracce del disco. La formattazione a basso livello dà un formato al disco (da qui deriva il termine “formattazione”) in modo che il controllore possa leggere o scrivere sul disco. • Creazioni delle partizioni del disco. Il sistema operativo suddivide il disco in gruppi di cilindri (le partizioni) e tratta ogni gruppo come una unità a disco autonoma. • Formattazione ad alto livello: in questa fase vengono inseriti i file di sistema all’interno delle partizioni del disco. • Installazione del sistema operativo. La velocità operativa di un disco è caratterizzata dai seguenti parametri: Seek time: tempo impiegato per individuare la traccia che contiene il blocco fisico; questo tempo è proporzionale alla distanza che la testina deve percorre ad ogni spostamento. Latency time: tempo che occorre per trovare il settore all’interno della traccia. In pratica è il tempo di rotazione necessario per portare il settore sotto la testina. Transfer time: tempo di lettura vero e proprio e di trasferimento del singolo blocco. Il tempo necessario per una operazione di trasferimento è dato dalla somma dei tre tempi. Indirizzamento: Il file system vede i vari settori del disco allocati in uno spazio lineare di indirizzi LBA (Linear Base Address), per contro l’accesso fisico al disco deve far uso di un indirizzamento tridimensionale CHS (Cylinder – Head – Sector ). Il controller del disco dovrà quindi effettuare una trasformazione degli indirizzi da coordinate LBA in coordinate CHS. Interleaving: Questo problema è dovuto al fatto che, durante il trasferimento, il disco continua a ruotare ed il successivo settore passa sotto la testina mentre è in corso il trasferimento. In un giro è possibile quindi leggere un solo settore. E’ pertanto opportuno numerare i settori in modo che il successivo settore sia fisicamente distanziato di un adeguato numero di settori (ordine sequenziale a salti). Algoritmi di scheduling L’unica componente sulla quale si può agire per cercare di diminuire i tempi complessivi è il seek time. Se arriva una richiesta quando il disco è libero, il sistema la serve normalmente. In questa situazione non esiste alcun rimedio alla riduzione del tempo di esecuzione. Se il disco è occupato il sistema può agire sul seek time sfruttando particolari algoritmi di scheduling. Per ridurre i tempi, il sistema operativo sfrutta il principio di località per diminuire gli accessi al disco. Quando riceve una richiesta il sistema legge non solo il settore che contiene la richiesta, ma anche alcuni settori successivi. Il driver riceve le informazioni utili a soddisfare la richiesta, mentre tutto ciò che è stato letto negli altri settori viene memorizzato in una cache. Quando arriva una richiesta che coinvolge settori presenti nella cache il sistema non accede al disco, ma i dati da passare al driver vengono prelevati dalla cache. A.C. Neve – Sistemi operativi 3.0 25 Algoritmo FCFS L’algoritmo FCFS (First-Come First-Served) organizza la lista delle richieste pendenti secondo l’ordine di arrivo. L’inserimento e l’estrazione delle richieste risulta semplice, ma questo algoritmo non permette di migliorare le prestazioni del sistema dal punto di vista del seek time. Supponiamo di avere un disco con 200 tracce e di avere le seguenti richieste nella coda: traccia richiesta ordine di arrivo 98 1 183 2 37 3 122 4 14 5 124 6 65 7 67 8 Le richieste di lettura o di scrittura indicano la traccia su cui si trova il blocco che coinvolge l’operazione. Supponiamo inoltre che la testina sia posizionata inizialmente sulla traccia 53. Con l’algoritmo FCFS il sistema serve le richieste nell’ordine di arrivo, quindi la testina si sposta dalla traccia 53 alla traccia 98, poi sulla traccia 183, poi sulla traccia 37 e così via. Dopo aver servito l’ultima richiesta presente nella coda, la testina si è spostata complessivamente di 640 cilindri. Questo algoritmo offre basse prestazioni perché il numero di spostamenti della testina è elevato: la testina ogni volta si deve spostare casualmente da una parte all’altra del disco. Algoritmo SSTF L’algoritmo SSTF (Shortest Seek Time First) minimizza i tempi di ricerca. Il sistema serve le richieste in base alla posizione della testina: seleziona dalla coda delle richieste pendenti quella più vicina alla testina in quell’istante. L’inserimento delle richieste nella coda risulta ancora semplice, mentre nell’estrazione il sistema deve scorrere tutta la lista per individuare la richiesta. Utilizzando la situazione ipotizzata nel descrivere l’algoritmo FCFS, l’ordine di esecuzione delle richieste con l’algoritmo SSTF diventa (si specificano le tracce su cui la testina si sposta): 65 → 67 → 37 → 14 → 98 → 122 → 124 → 183. Il risultato finale calcolato in numero di cilindri visitati, è di 236 cilindri. Questo algoritmo introduce starvation: se una richiesta è troppo lontana rispetto alle altre richieste che continuano ad arrivare in coda, non verrà mai servita. Al contrario le richieste favorite sono quelle che richiedono blocchi posizionati nelle tracce centrali al disco. Algoritmo SCAN L’algoritmo SCAN (noto come algoritmo dell’ascensore) effettua una scansione di tutte le tracce. Il braccio del disco parte da un estremo, si sposta nella sola direzione possibile servendo tutte le richieste che incontra sui cilindri, fino a raggiungere l’estremo opposto del disco. A questo punto viene invertita la direzione (verso l’estremo da cui era partita) e riparte a servire le altre richieste. Le testine attraversano continuamente il disco nelle due direzioni. Questa soluzione elimina la starvation, perché sicuramente tutte le richieste vengono servite. Il tempo massimo di attesa di una richiesta risulta, nella peggiore delle ipotesi, uguale al tempo necessario alla testina ad attraversare per due volte il disco. Se ripetiamo l’esempio precedente applicando questo algoritmo otteniamo 208 cilindri, risultato migliore del caso precedente, ma in generale lo SCAN offre prestazioni peggiori del SSTF. Al fine di migliorare la politica di questo algoritmo, è consigliabile far uso di due code relative alle due direzioni di spostamento della testina. A.C. Neve – Sistemi operativi 3.0 26 Algoritmo C-SCAN Con l’algoritmo SCAN quando il braccio del disco torna indietro la zona vicino all’estremo è stata visitata da poco, quindi probabilmente ci saranno poche richieste, mentre la maggior densità di richieste sarà concentrata all’estremo opposto. Una variante di questo algoritmo (ottimizzazione) prevede una scansione circolare del disco. Si parla quindi di C-SCAN: la testina si sposta da un estremo all’altro come con lo SCAN, ma quando raggiunge un estremo riparte dall’inizio, senza servire richieste nel viaggio di ritorno. Il C-SCAN tratta il disco come una lista circolare. Algoritmo LOOK Esistono altri due algoritmi che derivano dall’algoritmo a scansione SCAN e C-SCAN che si basano sull’idea che risulta inutile arrivare fino in fondo al disco se non ci sono più richieste oltre una determinata traccia: con il LOOK (per lo SCAN) e il C-LOOK (per il C-SCAN) la testina si sposta finché ci sono richieste da servire, quindi torna all’inizio. RAID (Redundant Array of Indipendent Disks) RAID è un insieme di dischi fisici visti dal sistema operativo come un solo disco logico con migliori caratteristiche di affidabilità e prestazioni. Il principio di funzionamento si basa sulle stripe (strisce): invece di registrare un file su un unico disco, si suddivide il file in più parti e la registrazione viene fatta su più dischi in modo da parallelizzare le operazioni ed incrementare la velocità. L’affidabilità viene migliorata facendo uso di tecniche di ridondanza del tipo Hamming che consentono di tollerare guasti su uno dei dischi dell’array. Esistono diversi livelli di RAID da RAID 0 fino a RAID 6. Tecniche di gestione Un device di I/O può essere gestito nei seguenti modi: • • • Dedicato: Un device si dice dedicato quando viene assegnato ad uno specifico processo per l’intera durata del processo stesso. In alcuni casi, questa tecnica può risultare inefficiente. Condiviso: Un device si dice condiviso quando viene utilizzato da più processi durante la loro evoluzione. Virtuale: un device si dice virtuale quando viene trasformato da dedicato in condiviso per mezzo di un opportuno software di controllo. Spooling Lo spooling (SPOOL Simultaneous Peripheral Operation On Line) è una tecnica per gestire periferiche dedicate, come le stampanti, in un ambiente multiprogrammato. L’I/O viene effettuato su una stampante virtuale implementata per mezzo di un file residente su disco che viene gestito da un processo detto spooler che preleva l’output di stampa da questo file ed effettua la stampa fisica. Lo spooling utilizza il disco come un buffer di memoria acquisendo velocemente tutti i vari input e provvedendo all’output secondo la velocità di acquisizione del device di stampa. I programmi di spooling fanno parte del sistema operativo ed interagiscono con i gestori di memoria, processori e device. A.C. Neve – Sistemi operativi 3.0 27 Buffering Il livello del software di I/O realizza il meccanismo di bufferizzazione, rende cioè disponibili dei buffer per la memorizzazione temporanea delle informazioni per conto dei processi utente. Queste aree di memoria sono utilizzate, come già specificato precedentemente, per evitare di bloccare il processo ogni volta che arriva un dato. Il sottosistema di I/O alloca e gestisce delle aree di memoria centrale destinate al transito di dati in I/O in modo da rendere indipendenti le differenze di velocità tra il sistema e l’I/O. L’utilizzo del buffer riduce l’overhed poiché consente ad un processo di leggere o scrivere su un device senza doversi frequentemente bloccare. I buffer possono essere implementati anche nell’interfaccia o nel controller di I/O. Generalmente si usa un singolo buffer, ma risulta più efficiente l’utilizzo del doppio buffer (double buffering): mentre il produttore riempie il primo, il consumatore svuota il secondo evitando così i tempi di attesa. Cache Mediante questa tecnica si mantiene in memoria centrale una copia delle informazioni più frequentemente usate che si trovano nella memoria secondaria (disco), in questo modo il processore non preleva i dati dal disco che è lento ma li preleva dalla copia presente in memoria centrale con maggiore velocità. Nasce però il problema della scelta delle informazioni da tenere in cache e della sincronizzazione con la relativa copia esistente sul disco. A tal proposito esistono degli algoritmi di gestione della cache: • FIFO (First In First Out): viene sostituito il blocco dati che da più tempo risiede in cache • LRU (Last Recently Used): viene sostituito il blocco che da più tempo non è utilizzato • LFU (Last Frequently Used): viene sostituito il blocco usato con minor frequenza. Schedulatore dell’I/O Lo schedulatore dell’I/O definisce le strategie di assegnazione dei device ai processi attraverso l’utilizzo di vari tipi di algoritmi. Una volta che lo schedulatore dell’I/O ha definito l’ordinamento delle richieste di I/O, il controllore del traffico determina quali richieste possono essere soddisfatte in funzione dello stato del sistema di I/O. Quando una di queste richieste viene attivata, generalmente viene condotta a termine. Controllore del traffico di I/O Il controllore del traffico mantiene traccia dello stato dei device, delle interfacce e dei canali e verifica la possibilità di definizione di un percorso per asservire una richiesta di I/O. Il controllore del traffico gestisce un archivio dati costituito da un blocco di controllo contenente: Identificatore del device Per ogni device Per ogni interfaccia Per ogni canale Stato Lista delle interfacce collegate Lista dei processi in attesa del device Identificatore dell’interfaccia Stato Lista dei device collegati Lista dei canali collegati Lista dei processi in attesa della unità di controllo Identificatore del canale Stato Lista delle interfacce collegate Lista dei processi i attesa dei canali A.C. Neve – Sistemi operativi 3.0 28 Gestori dei device Esiste un gestore (device driver) per ciascun device di I/O. Questo avvia e gestisce l’I/O fisico, gestisce le interruzioni ed ottimizza le prestazioni con l’obiettivo di ridurre il tempo richiesto per le operazioni di I/O. I device driver sono dei programmi che fanno da interfaccia software tra il sistema operativo ed i programmi applicativi. I device driver possono essere generali (device indipendent) o dedicati ad un particole device. I device driver sono corredati di tutte le informazioni necessarie per l’utilizzo hardware software della periferica in oggetto. A.C. Neve – Sistemi operativi 3.0 29 Gestione della memoria centrale La parte del sistema operativo che gestisce la memoria è il gestore di memoria il quale si occupa di tutti i problemi legati all’assegnazione della memoria ai diversi processi che ne fanno richiesta per portare a termine la loro esecuzione. Il gestore della memoria deve quindi tenere traccia di quali parti di memoria sono in uso e quali sono libere e deve allocare o deallocare memoria ai processi in modo opportuno. Infine, quando la memoria principale non è sufficiente a contenere tutti i processi, il gestore deve provvedere a fornire una memoria di supporto tramite un meccanismo di virtualizzazione della memoria. Un programma per essere eseguito deve avere un’area di memoria a disposizione. Il sistema operativo deve implementare dei meccanismi che allochino spazi di memoria ai processi pronti ad andare in esecuzione e che revochino lo spazio ai processi che terminano. Poiché la memoria principale non è sempre sufficiente a mantenere tutti i processi correntemente attivi, il sistema operativo utilizza una memoria di supporto, generalmente i dischi, sulla quale risiedono i processi in eccesso e che verranno introdotti successivamente in memoria principale. Il sistema operativo deve realizzare una corrispondenza tra istruzioni del programma e indirizzi di memoria. Tale associazione può essere stabilita in momenti diversi. Se in fase di compilazione, il sistema operativo sa già dove risiederà il programma, esso genera un indirizzamento assoluto del programma. Il codice così generato non è rilocabile, cioè se ad un dato momento gli indirizzi delle locazioni di memoria che ospitano il programma dovessero cambiare, sarebbe necessario ricompilare il programma. Quando l’associazione è eseguita in fase di caricamento, il codice generato dal compilatore è invece rilocabile. Quindi se l’indirizzo iniziale cambia si ricarica il programma in memoria senza dover ricalcolare gli indirizzi. Le opportune traduzioni sono effettuate dal loader. Se l’associazione finale degli indirizzi è ritardata alla fase di esecuzione, il programma può essere spostato da una zona di memoria all’altra durante l'esecuzione stessa. La maggior parte dei sistemi operativi impiega questo metodo. Gli indirizzi generati da un programma sono detti indirizzi logici, mentre gli indirizzi su cui opera l’unità di memoria sono indirizzi fisici. L’insieme degli indirizzi logici generati da un programma formano lo spazio degli indirizzi logici; l’insieme degli indirizzi fisici corrispondenti a tali indirizzi logici formano lo spazio degli indirizzi fisici. La definizione degli indirizzi virtuali sugli indirizzi fisici viene fatto dalla Memory Management Unit (MMU). L’unità di MMU deve svolgere le seguenti funzioni: • • • Allocazione: cioè assegnazione della memoria alle entità che ne fanno richiesta Deallocazione: rendere disponibile la memoria delle entità che non ne hanno più bisogno Memorizzazione dello stato: tenere traccia sull’utilizzo delle varie parti di memoria. L’unità di MMU svolge le funzioni elencate riferendosi a tre aspetti: • • • Hardware: che è la parte di circuiteria elettronica che gestisce tutti i chip di memoria fisica Sistema Operativo: che gestisce, in modo trasparente, l’allocazione di memoria agli applicativi Applicativi: i quali richiedono memoria al sistema operativo per mezzo funzioni specifiche del linguaggio di programmazione utilizzato. A.C. Neve – Sistemi operativi 3.0 30 Allocazione contigua Partizioni statiche Partizione singola Nel caso di un sistema operativo monoutente monoprogrammato, una parte della memoria centrale è allocata al sistema operativo mentre la restante parte è disponibile per il programma in esecuzione. Non è necessario hardware di MMU se non per un registro di limite il cui compito è quello di controllare che l’applicativo in esecuzione non acceda alla memoria allocata al sistema operativo. L’algoritmo di allocazione ha il solo compito di asservire la richiesta di memoria dell’applicativo e di controllare che le dimensioni del programma siano minori o uguali alla memoria disponibile. Uno svantaggio di questa tecnica si nota quando il programma in esecuzione è molto piccolo rispetto alle dimensioni della memoria centrale non sfruttando così le condizioni per l’esecuzione contemporanea di un altro programma. Partizioni multiple statiche Nei sistemi multiprogrammati il sistema operativo consente a diversi processi di girare contemporaneamente, quindi la memoria deve essere suddivisa in un certo numero di partizioni in modo da soddisfare le esigenze dei processi. Il modo più semplice prevede di dividere la memoria in partizioni fisse dette statiche, ognuna dedicata ad un processo; terminato un processo la sua partizione torna ad essere libera per un altro processo. Il grado di multiprogrammazione è limitato dal numero di partizioni. Più grande è la memoria centrale e maggiore è il numero di partizioni o la loro dimensione. Le partizioni sono definite dal sistema operativo ed ogni programma deve comunicare la quantità massima di memoria richiesta. Le partizioni statiche pongono il vincolo di dover caricare tutto il programma in memoria centrale per cui se non esiste una partizione libera in grado di ospitare il programma, questo non sarà eseguito. Un altro vincolo è relativo all’impossibilità di caricare il programma in più partizioni libere non contigue. Anche in questo caso si fa uso del registro limite il quale ha il compito di controllare che l’applicativo resti sempre all’interno della partizione assegnata. Spesso si utilizza una chiave di protezione definita per ogni partizione la quale consente l’accesso alla partizione solo al programma al quale è stata assegnata quella specifica chiave. I valori del registro limite o della chiave di protezione fanno parte dello stato del processo e sono quindi memorizzati nel PCB (Process Control Block). Uno degli svantaggi di questa tecnica è la frammentazione interna che si verifica quando un processo utilizza solo parzialmente la partizione assegnata per cui lo spazio restante è formalmente allocato ma di fatto non usato mentre potrebbe essere utilizzato da altri processi. 00000H SISTEMA OPERATIVO Partizione 1 AaaaaaaaaaProcesso 1aaaaaa aaa a Aaaaaaaaaaaaaaaaaaaaaaaaa aaaaa ← frammentazione Partizione 2 AaaaaaaaaaProcesso 2 aaaa aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa aaaaaaa Partizione 3 AaaaaaaaaaProcesso 3 aa aaaaaaaa aaaaaaaaaaaaaaaaaaaaaa aaaaaaaaa ←frammentazione ←frammentazione Partizione 4 aaaaaaaaaaaProcesso 4 A.C. Neve – Sistemi operativi 3.0 aaaaaaaa FFFFFH 31 Supponendo di avere partizioni statiche ma di dimensioni differenti, sarà necessario selezionare la partizione da assegnare al processo. A tal proposito esistono alcuni algoritmi: FIRST FIT: effettua una ricerca tra le partizioni assegnando il processo alla prima partizione in grado di contenerlo BEST FIT: effettua una ricerca tra tutte le partizioni assegnando il processo alla partizione di dimensioni più prossime alle dimensione del processo richiedente. Per aumentare il grado di multiprogrammazione, il sistema operativo può, in base a dei criteri di priorità, effettuare lo swapping del processo cioè salvare il processo su disco ed assegnare la partizione ad un altro processo. Tutto ciò è possibile solo se il codice del processo è rilocabile cioè risulta eseguibile indipendentemente dall’area di memoria centrale nella quale è caricato. Quando viene caricato un processo, questo viene inserito in una coda di ingresso. Esistono due diverse implementazioni: - partizioni di memoria fisse con code di ingresso separate per ciascuna partizione - partizioni di memoria fisse con coda di ingresso unica Con il primo approccio, il processo caricato viene inserito nella coda d’ingresso della partizione più piccola che lo possa contenere. Poiché le partizioni sono fisse tutto lo spazio di una partizione non utilizzato dal processo è sprecato. Il caso peggiore si ha quando vi sono molti processi in attesa di una partizione piccola mentre la coda di ingresso di una partizione grande è vuota: i processi piccoli sono in attesa anche se gran parte della memoria risulta libera. L’organizzazione dei processi in un’unica coda risulta più efficiente in termini di tempi di attesa: appena una partizione di memoria è libera, viene occupata dal primo processo in coda che può entrare nella partizione. Una ottimizzazione di questa implementazione consiste nel selezionare non il primo processo in coda, ma cercare nella coda il processo più grande che può essere ospitato. In questo modo si evita di sprecare partizioni grandi per far girare processi piccoli. Partizioni dinamiche Per eliminare il problema della frammentazione interna si ricorre alle partizioni dinamiche. In questo caso, le dimensioni delle partizioni sono create dal sistema operativo in base alle richieste dei processi che dovranno quindi essere in grado di comunicare le loro dimensioni. Il sistema operativo dovrà quindi tenere traccia di due tabelle: Tabella delle partizioni PDT (Partition Description Table) che registra l’indirizzo iniziale, le dimensioni e lo stato di allocazione. Tabella delle aree libere FDT (Free Description Table). L’allocazione richiede l’analisi delle aree libere per scegliere quella da assegnare. Se quella scelta è più grande del necessario sarà suddivisa in due parti, una da assegnare al processo ed una che resterà libera per altri processi e che sarà registrata nella FDT. Quando un processo viene deallocato sarà cancellato dalla PDT e registrato nella FDT. Esistono tre diversi algoritmi di allocazione della memoria: FIRST FIT: la tabella delle aree di memoria libere è ordinata per indirizzi crescenti delle aree stesse. La ricerca inizia dagli indirizzi più bassi e prosegue fino a trovare la prima area libera in grado di ospitare la partizione. La ricerca di aree libere adiacenti per l’unione è semplice. Questa tecnica favorisce la formazione di grandi aree libere verso gli indirizzi alti BEST FIT: in questo caso, la tabella delle aree di memoria libera è ordinata per dimensioni crescenti delle aree libere. La prima area libera che risulta in grado di contenere la partizione è quella che A.C. Neve – Sistemi operativi 3.0 32 garantisce il migliore adattamento e quindi il minimo spreco. Questa ricerca richiede mediamente l’esplorazione di metà tabella. Se la partizione allocata risulta leggermente più grande, la sua frammentazione genera partizioni libere molto piccole e difficilmente utilizzabili (frammentazione esterna). WORST FIT: come nel caso precedente, anche ora la tabella delle aree di memoria libera è ordinata per dimensioni crescenti delle aree libere. In questo caso si alloca l’area più grande in grado di contenere la partizione. Con questo criterio si tende ad evitare la creazione di partizioni libere troppo piccole che sarebbero inutilizzabili. I migliori algoritmi in termini di tempo e di utilizzo della memoria risultano i primi due. Deframmentazione e riallocazione La frammentazione della memoria può essere interna oppure esterna. La frammentazione interna riguarda spazi di memoria allocati ai processi ma non utilizzati. La frammentazione esterna è causata dal meccanismo di swapping: caricando e scaricando dalla memoria centrale processi di dimensioni diverse, si creano molti buchi liberi. La soluzione a questo problema è data dalla deframmentazione con la quale si riordina il contenuto della memoria libera in un unico grande blocco. Questo procedimento richiede un tempo non trascurabile. Dall’analisi condotta è evidente come si possano determinare delle aree libere non contigue la cui somma potrebbe consentire l’allocazione di ulteriori processi qualora potessero essere rese contigue. Si correda così l’MMU di una nuova funzione detta di deframmentazione che ha il compito di ricompattare le aree occupate creando una e grande partizione libera. La deframmentazione può essere effettuata: • Periodicamente • Quando si determina un elevato livello di frammentazione • Quando non si trova una area libera. E’ evidente che la deframmentazione potrà essere effettuata solo se il codice del programma risulta rilocabile dinamicamente cioè modificabile all’atto dell’allocazione. Allocazione non contigua La non contiguità del processo di allocazione è relativa alla possibilità di poter allocare un processo in aree di memoria separate e quindi fisicamente non adiacenti. E’ evidente che durante l’esecuzione del processo sarà necessario che la traduzione degli indirizzi logici in quelli fisici tenga conto di questa separazione dei vari pezzi di programma. Paginazione La paginazione consente di assegnare ad un processo, in modo trasparente all’utente, degli spazi di memoria non contigui ovunque essi siano disponibili, risolvendo quindi il problema della frammentazione esterna. Lo spazio degli indirizzi logici di un processo viene diviso in parti uguali dette pagine, anche la memoria viene suddivisa in parti delle stesse dimensioni dette blocchi (frames). Una pagina può essere inserita in un blocco qualsiasi. Le pagine sono logicamente contigue, i blocchi non necessariamente. Le dimensioni delle pagine sono scelte come potenza di 2 (1, 2, 4, 8 KBy). A.C. Neve – Sistemi operativi 3.0 33 Il gestore della memoria ha il compito di: • • • • • Tenere traccia dello stato di allocazione delle pagine e dei blocchi. Per ogni processo è necessaria una tabella delle corrispondenze tra pagine e blocchi. E’ poi necessaria una tabella che specifichi, per ogni blocco, se è allocato o libero. Una volta individuato il processo da allocare, sarà necessario verificare se sono disponibili tanti blocchi per quante sono le pagine da allocare. Al termine del processo sarà necessario deallocare i blocchi ed aggiornare le tabelle. Il caricamento di un processo di dimensioni Dpro richiederà un numero N di pagine uguale a INTERO(Dpro/Dpag) +1 con Dpag pari alla dimensione della pagina. Se Dpro non è multiplo intero di Dpag si crea frammentazione interna. Per limitare la frammentazione dovremmo avere pagine più piccole, ma ciò comporterebbe l’aumento della dimensione della tabella delle pagine (che potrebbe occupare anche molta memoria) e dell’overhead associato. L’organizzazione della tabella dei blocchi liberi è alquanto critica relativamente alla velocità di ricerca dei blocchi liberi in quanto questi sono distribuiti in modo casuale. Memoria virtuale Il problema fondamentale della multiprogrammazione consiste nella limitata disponibilità della memoria: la dimensione combinata di programmi, dati e stack può eccedere la dimensione della memoria fisica per essi disponibile. Si introduce quindi il meccanismo di virtualizzazione della memoria che permette di eseguire processi non contenuti interamente nella memoria, basandosi sul fatto che l’intero programma non è necessario caricarlo tutto in una volta. Il sistema operativo mantiene quindi in memoria principale solo le parti del programma in uso e il resto su una memoria ausiliaria. Spesso ci sono parti di programmi che non vengono quasi mai eseguite (per esempio il codice relativo alla gestione degli errori) e lo spazio occupato può essere utilizzato per mantenere contemporaneamente in memoria più programmi. Con la virtualizzazione della memoria il sistema alloca ad un certo istante non tutto il programma, ma solo la parte necessaria ad eseguire il set di istruzioni richieste. Il termine virtuale deriva dal fatto che i processi vengono eseguiti come se avessero a loro disposizione tutta la memoria centrale. I vantaggi offerti dalla virtualizzazione sono molteplici: • • • la programmazione non è più vincolata dalla quantità di memoria fisica disponibile; aumento del parallelismo, con una conseguente crescita della produttività della CPU; diminuzione dei tempi di caricamento dei programmi in memoria. Il sistema operativo ha il compito di caricare dal disco in memoria la pagine necessaria al processo per proseguirne l’esecuzione. Nel caso in cui non ci fosse spazio libero in memoria si dovranno eliminare delle pagine dalla memoria ricopiandole sul disco. Questa operazione è detta swapping. Una limitata quantità di memoria determinerà una eccessiva movimentazione di pagine (thrashing) che degraderà le prestazioni del sistema. Anche la ricerca della pagina sul disco determinerà una riduzione della velocità di esecuzione. A.C. Neve – Sistemi operativi 3.0 34 Paginazione a richiesta Per realizzare la virtualizzazione è necessaria la presenza di una zona di memoria dove le pagine risiedono in attesa di essere allocate per i diversi processi: esiste quindi un’area di swap, che contiene le pagine temporaneamente non in memoria. L’area di swap è una zona della memoria di massa cioè l’hard disk. Ovviamente serve un meccanismo (pager) che si occupi del passaggio delle pagine dalla memoria principale alla memoria di massa e viceversa. In particolare il pager effettua due operazioni: - swap-out: salva il contenuto dello spazio virtuale dedicato al processo in memoria di massa; - swap-in: trasferisce il contenuto dello spazio di memoria virtuale dalla memoria di massa all’interno della zona di memoria fisica allocata al processo. Se non è disponibile il meccanismo della rilocazione dinamica degli indirizzi il trasferimento dello spazio virtuale del processo nell’operazione di swap-in deve avvenire nella stessa area di memoria dove è stato allocato la prima volta. Se al contrario il codice è rilocabile, allora lo spazio virtuale può essere trasferito in qualunque area di memoria libera. m em o ria fisica m em o ria di m assa sw ap -o ut sp azio v irtuale del p ro cesso area di sw ap swap-in Mediante la virtualizzazione, il sistema operativo permette di eseguire un processo che non è stato caricato per intero in memoria e le informazioni di cui ogni processo ha bisogno per essere eseguito sono caricate in memoria, su domanda, quando necessario. Si parla quindi di paginazione su richiesta: ad un certo istante solo alcune pagine si trovano in memoria centrale; una pagina viene trasferita da memoria di massa a memoria centrale solo quando è richiesta dal processo, ovvero quando un indirizzo tradotto da logico a fisico si riferisce una pagina non presente in memoria centrale. La presenza di una pagina in memoria è individuata da un bit di validità Bv associato ad ogni entrata della tabella delle pagine. Durante la traduzione di un indirizzo, il sistema accede alla tabella delle pagine e controlla tale bit: Bit di validità = 1, la pagina è presente in memoria centrale. Il sistema procede normalmente. Bit di validità = 0, la pagina è in memoria di massa. Se la pagina non è presente in memoria centrale (Bv=0) il sistema genera una interruzione di pagefault. Quindi il gestore delle interruzioni controlla la causa del page-fault e procede di conseguenza: • Se il page-fault è stato generato da un errore di accesso, segnalato dal bit di accesso presente nella tabella delle pagine, il programma abortisce. • Se il page-fault è stato generato dal bit di validità, si accede alla tabella che contiene il corrispondente indirizzo della pagina nell’area di swap. Individuata la pagina, il sistema cerca un frame libero in memoria centrale e carica la pagina nello spazio libero (operazione di swapin). Quindi aggiorna la tabella delle pagine e riavvia il processo che era stato bloccato dall’operazione che aveva provocato page-fault. A.C. Neve – Sistemi operativi 3.0 35 Se occorre fare una operazione di swap-in ma non esiste un blocco libero in memoria centrale il sistema attua il meccanismo di rimpiazzamento: il sistema operativo seleziona, secondo opportuni criteri, una pagina presente in memoria centrale e con l’operazione di swap-out la copia in memoria di massa, quindi effettua lo swap-in richiesto. Se la pagina selezionata non è stata mai modificata dal momento in cui è stata caricata in memoria centrale (individuato dal dirty bit contenuto nella tabella delle pagine), il sistema effettua solo l’operazione di swap-in, e non si ha quindi il trasferimento da memoria centrale a memoria di massa. Il meccanismo di paginazione su domanda permette di gestire più utenti contemporaneamente e impiega per ogni processo meno memoria fisica. La dimensione della pagina è un parametro che può influire sull’efficienza del sistema. Pagine molto piccole riducono la frammentazione interna e migliorano l’utilizzo della memoria per contro però fanno aumentare le dimensioni delle tabelle di gestione e la frequenza di movimentazione. Il contrario accade per pagine molto grandi. Strategie di allocazione Le strategie di allocazione servono per decidere quanta memoria fisica è opportuno allocare ad ogni processo. Assegnare un elevato numero di pagine fisiche ad un processo riduce la frequenza di page fault e il tempo di elaborazione delle richieste di pagina (turnaround). Si ha però una riduzione dei processi attivabili e quindi del grado di multiprogrammazione. L’assegnazione di un limitato numero di pagine incrementa la frequenza di page-fault e del turnaround. Si può verificare che, la relazione esistente tra la frequenza di page fault ed il numero di pagine allocate non è lineare. Per bassi valori del numero di pagine si ha un notevole incremento del page fault mentre per valori molto alti del numero di pagine non si ha un apprezzabile riduzione del page fault. Il valore ottimale si trova tra questi due estremi. Thrashing Se il numero di pagine allocate ad ogni processo è tale da far aumentare notevolmente la frequenza di page fault il sistema assume un comportamento detto di thrashing, il sistema cioè trascorre gran parte del suo tempo in operazioni di swapping di pagine con un impegno di CPU apparentemente basso. Lo schedulatore, notando il ridotto impegno della CPU, tenterà di aumentare il numero dei processi attivi peggiorando ulteriormente le prestazioni. A tal proposito è possibile definire una soglia inferiore e superiore della frequenza di page fault per ciascun processo. Quando un processo supera la soglia superiore si allocano più blocchi quando invece raggiunge la soglia inferiore si rallenta l’allocazione di ulteriori blocchi. Working set L’utilizzo del working set si basa sulle seguenti considerazioni: • • • Durante un qualsiasi intervallo di tempo, un processo in esecuzione favorisce un certo sottoinsieme delle proprie pagine (località spaziale) La sequenza di accesso alla memoria da parte di un processo in esecuzione evidenzia una alta correlazione tra il passato immediato e l’immediato futuro (località temporale) La frequenza con la quale una certa pagina è usata è una funzione che varia lentamente nel tempo. A.C. Neve – Sistemi operativi 3.0 36 Il working set di un processo è l’insieme delle pagine che definiscono la località delle pagine in un certo istante cioè l’insieme delle pagine nell’intorno delle quali il processo si evolve. Quando un processo si sposta da una località ad un’altra cambia anche l’insieme delle pagine che costituiscono il suo working set in quell’intervallo di tempo. La scelta dell’intervallo di tempo di osservazione del processo è molto importante: se troppo piccolo, il working set potrebbe non contenere la giusta località, se invece è troppo grande potrebbe estendersi inutilmente oltre la sua località. Dal principio del working set consegue che: • un processo può andare in esecuzione solo se il suo working set è in memoria • una pagina non potrà essere rimossa se appartiene al working set di un processo in esecuzione. Strategie di sostituzione Quando si verifica un page-fault il processo non può più continuare la sua esecuzione ed il sistema deve caricare in memoria centrale la pagina che contiene l’istruzione che ha provocato il fault. Se non c’è spazio in memoria si dovrà operare un rimpiazzamento di pagina, cioè si sostituisce una pagina con una già presente in memoria centrale selezionandola secondo il criterio adottato dal sistema operativo. La gestione dei page-fault comporta una perdita di tempo notevole: il sistema deve minimizzare la frequenza di page-fault agendo sul numero di blocchi a disposizione per ogni processo e sugli algoritmi di sostituzione delle pagine. Una volta scelta la pagina da deallocare, potrà essere necessario salvarla prima su disco nel caso in cui fosse stata modificata, in caso contrario potrà essere tranquillamente distrutta, in molti casi questa soluzione può far risparmiare il tempo di trasferimento. Questa tecnica viene implementata per mezzo della tabella che descrive lo stato dei blocchi: si fa uso di un bit (dirty bit) che viene posto a zero all’atto del caricamento della pagina e posto ad uno ad ogni scrittura indicando così la variazione del contenuto e quindi la necessità di salvataggio. Il sistema operativo deve quindi far uso algoritmi che raggiungono questo scopo e che nello stesso tempo risultino facili da implementare e da gestire. Gli algoritmi di sostituzione possono essere locali o globali: La sostituzione è locale se la pagina eliminata appartiene al processo che ha generato il page- fault. E’ invece di tipo globale se la pagine scelta può appartenere a qualsiasi processo. Algoritmo ottimale L’algoritmo ottimale (ma impossibile da implementare) sostituisce la pagina che non sarà più utilizzata o non lo sarà per molto tempo. Secondo questo criterio la frequenza del page-fault è minima. Inoltre si può dimostrare che non soffre dell’anomalia di Belady più avanti descritta. Il problema fondamentale di questo algoritmo sta nel fatto che è irrealizzabile, infatti non è possibile prevedere quando ciascuna pagina sarà riferita. E’ quindi un algoritmo ideale, utile solo come algoritmo di riferimento e può essere utilizzato insieme ad un algoritmo realizzabile per calcolarne le prestazioni. Algoritmo FIFO L’algoritmo FIFO (First In First Out) segue la politica secondo la quale si seleziona la pagina che da più tempo si trova in memoria. Il sistema ha due modi di riferire le pagine: • mantenere un timer, inizializzato all’istante in cui la pagina viene caricata in memoria centrale; ogni volta che si verifica un page-fault il sistema scorre tutte le pagine e seleziona quella più vecchia. A.C. Neve – Sistemi operativi 3.0 37 • organizzare le pagine presenti in memoria in una lista concatenata in cui le pagine sono caricate in ordine di arrivo in memoria centrale. Quando si ha il fault, la pagina in testa alla lista viene eliminata e la nuova pagina viene inserita in coda. Non si può però affermare con certezza che una pagine che risieda da molto tempo in memoria non sia comunque tra quelle di uso più frequente. Si consideri come esempio un processo che dispone in memoria di solo tre blocchi fisici e che nella sua esecuzione acceda alle pagine (1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5) il flusso di page-fault è il seguente: ST E P A Z IO N E P A G IN E IN M E M O R IA 1° 2° 3° PA GE FA U L T p asso 0 : p agin e fisich e v uo te. 0 p asso 1 : il sistem a carica le p rim e tre p agin e rich ieste. 1 2 3 3 p asso 2 : p age fault sulla p agin a 4 . Il sistem a so stituisce la p rim a p agin a. 4 2 3 1 p asso 3 : p age fault sulla p agin a 1 . Il sistem a rim p iazza la seco n da p agin a. 4 1 3 1 p asso 4 : p age fault sulla p agin a 2 . Il sistem a rim p iazza la terza p agin a. 4 1 2 1 p asso 5 : p age fault sulla p agin a 5 . Il sistem a rim p iazza la p rim a p agin a. 5 1 2 1 p asso 6 : rich iesta la p agin a 1 , già p resen te in m em o ria cen trale. 5 1 2 0 p asso 7 : rich iesta la p agin a 2 , già p resen te in m em o ria cen trale. 5 1 2 0 p asso 8 : p age fault sulla p agin a 3 . Il sistem a rim p iazza la seco n da p agin a. 5 3 2 1 p asso 9 : p age fault sulla p agin a 4 . Il sistem a rim p iazza la terza p agin a. 5 3 4 1 p asso 1 0 : rich iesta la p agin a 5 , già p resen te in m em o ria cen trale. 5 3 4 N u m ero to ta le d i p a g e fa u lt 0 9 Disponendo invece di quattro blocchi fisici si ha il seguente flusso di page-fault: ST E P A Z IO N E P A G IN E IN M E M O R IA 1° 2° 3° 4° p asso 0 : p agin e fisich e v uo te. PA GE FA U L T 0 p asso 1 : il sistem a carica le p rim e quattro p agin e rich ieste. 1 2 3 4 4 p asso 2 : rich iesta la p agin a 1 , già p resen te in m em o ria cen trale. 1 2 3 4 0 p asso 3 : rich iesta la p agin a 2 , già p resen te in m em o ria cen trale. 1 2 3 4 0 p asso 4 : p age fault sulla p agin a 5 . Il sistem a rim p iazza la p rim a p agin a. 5 2 3 4 1 p asso 5 : p age fault sulla p agin a 1 . Il sistem a rim p iazza la seco n da p agin a. 5 1 3 4 1 p asso 6 : p age fault sulla p agin a 2 . Il sistem a rim p iazza la terza p agin a. 5 1 2 4 1 p asso 7 : p age fault sulla p agin a 3 . Il sistem a rim p iazza la terza p agin a. 5 1 2 3 1 p asso 8 : p age fault sulla p agin a 4 . Il sistem a rim p iazza la p rim a p agin a. 4 1 2 3 1 p asso 9 : p age fault sulla p agin a 5 . Il sistem a rim p iazza la seco n da p agin a. 4 5 2 3 N u m ero to ta le d i p a g e fa u lt 1 10 Come si può notare, la situazione peggiora. Il fenomeno descritto è noto come anomalia di Belady secondo il quale il numero di page-fault aumenta all’aumentare del numero di pagine allocate. Si può quindi affermare che questo algoritmo non è molto efficiente. A.C. Neve – Sistemi operativi 3.0 38 Algoritmo LRU (Least Recently Used) L’algoritmo LRU si basa sull’idea che le pagine che sono state frequentemente usate nelle ultime istruzioni lo saranno anche nelle prossime istruzioni, mentre quelle non utilizzate da lungo tempo non lo saranno ancora per molto tempo. Con l’algoritmo LRU il sistema interpreta il futuro sulla base delle informazioni passate e seleziona la pagina non utilizzata da più lungo tempo, supponendo che non sia più utile. L’algoritmo quindi ragiona in modo contrario all’algoritmo ottimale in cui il sistema prevede il futuro. Il LRU viene implementato fornendo una ulteriore informazione in ogni entrata della tabella delle pagine: si introduce un campo che indica il tempo ogni volta che una pagina viene riferita. La CPU deve mantenere aggiornato un contatore, incrementato automaticamente ad ogni istruzione, che tiene traccia del tempo che passa ed è utilizzato per aggiornare il campo nella tabella delle pagine. Il sistema seleziona dalla tabella delle pagine la pagina con il campo minore. Questo algoritmo non è molto efficace in quanto: • devono essere aggiunte informazioni nella tabella delle pagine, con il conseguente aumento della dimensione della tabella stessa; • per selezionare la pagina da sostituire il sistema deve fare una scansione di tutta la lista, che comporta una perdita di tempo; • deve essere prevista una soluzione anche per la situazione in cui il campo è inizializzato a 0; • occorre stabilire un numero di bit opportuno per il campo presente nella tabella: deve essere grande abbastanza da contenere il valore del contatore, ma non troppo grande per evitare di aumentare notevolmente la dimensione della tabella delle pagine. Questo algoritmo presenta però un comportamento migliore del precedente. Algoritmo NRU (Not Recently Used) Per implementare l’algoritmo NRU il sistema operativo si avvale di due informazioni riguardanti le pagine caricate in memoria centrale. Nella tabella delle pagine vengono memorizzati due bit di stato associati a ciascuna pagina: • il bit RB (reference bit) posto a 1 ogni volta che la pagina viene riferita (read); • il bit DB (dirty bit) che quando vale 1 indica che la pagina, dal momento in cui è stata caricata in memoria centrale, è stata modificata (write). Questi due bit devono essere aggiornati ogni volta che la pagina viene riferita, per questo motivo vengono assegnati dall’hardware. Una volta che un bit è stato impostato a 1, sarà il sistema operativo a riportarlo al valore 0 via software al momento opportuno. L’algoritmo NRU utilizza il bit RB e il bit DB per selezionare la pagina da rimpiazzare. A seconda del valore dei due bit, si identificano delle classi di pagine diverse: RB 0 0 1 1 DB 0 1 0 1 CLASSE pagine da molto tempo non riferite e non modificate pagine da molto tempo non riferite ma modificate pagine da poco tempo riferite e non modificate pagine da poco tempo riferite e modificate Tra tutte le pagine appartenenti alle diverse classi, quelle ad avere la maggior probabilità di essere rimpiazzate sono le pagine della classe avente RB=0 e DB=0. Quelle invece meno convenienti da essere sostituite sono le pagine appartenenti alla classe con RB=1 e DB=1. Tra le due classi intermedie, il sistema privilegia nella scelta della sostituzione le pagine con RB=0 e DB=1 anche se A.C. Neve – Sistemi operativi 3.0 39 comporta una maggior perdita di tempo (comporta anche l’operazione di swap-out, cosa che non accade se è selezionata una pagina con RB=1 e DB=0). Algoritmo LFU (Less Frequently Used) e MFU (Most Frequently Used) Le tecniche viste fino ad ora si basano sull’istante di utilizzo delle pagine. Esistono poi altri algoritmi, poco utilizzati, che si basano su altri criteri ed in particolare sulla frequenza di utilizzo: l’algoritmo LFU e l’algoritmo MFU. Entrambi mantengono un contatore del numero di riferimenti che sono stati fatti a ciascuna pagina. Con l’algoritmo LFU il sistema controlla la frequenza di utilizzo di tutte le pagine nel passato. Se una pagina ha bassa frequenza si presuppone che la pagina non sia utile, per questo viene selezionata per il rimpiazzamento. Al contrario, con l’algoritmo MFU il sistema seleziona per la sostituzione la pagina più frequentemente utilizzata. Questo criterio si basa sull’idea che se una pagina è stata poco utilizzata significa che è stata caricata da poco in memoria centrale, quindi non è conveniente toglierla perché probabilmente fa parte del set di lavoro corrente. Modalità di assegnazione dei blocchi Il sistema può implementare due diversi schemi principali di allocazione dei blocchi della memoria ai processi che ne fanno richiesta: • statica. Il numero dei blocchi allocati ad un processo rimane invariato per tutta la sua esecuzione. • dinamica. Il numero dei blocchi allocati ad un processo può variare durante l’esecuzione. Dato uno spazio di memoria totale composto da m blocchi ed n processi in esecuzione, in entrambe le implementazioni viste sopra esistono due metodi di allocazione dei blocchi: allocazione uniforme o allocazione proporzionale. L’allocazione uniforme è la soluzione più semplice: sistema alloca a tutti i processi lo stesso numero di blocchi, pari ad m/n. Questo metodo causa iniquità tra i processi perché svantaggia i processi di dimensioni elevate che avrebbero bisogno di un numero maggiore di blocchi rispetto a quelli allocati. Con l’allocazione proporzionale il sistema alloca i blocchi della memoria in base alle dimensioni dei processi che ne fanno richiesta. E’ un criterio più equo rispetto all’allocazione uniforme poiché tiene conto delle diverse caratteristiche dei processi. In particolare il numero di blocchi allocati ad ogni processo viene calcolato in questo modo: dati Pi processi, ognuno con la rispettiva richiesta di memoria Si, si definisce la memoria totale disponibile: n S tot = ∑ S i i =1 quindi il numero di blocchi allocati ad ogni processo è definito dalla seguente relazione: mi = Si ⋅ mtot S tot A.C. Neve – Sistemi operativi 3.0 40 Quando si verifica un page-fault ma non ci sono blocchi liberi in memoria, il sistema avvia il meccanismo di rimpiazzamento che varia a seconda del tipo di allocazione dei blocchi: • rimpiazzamento locale: se l’allocazione è statica il sistema sceglierà la pagina da sostituire tra quelle appartenenti al processo stesso che ha generato il fault, perché il numero dei blocchi allocati deve rimanere uguale durante tutta l’esecuzione; • rimpiazzamento globale: se l’allocazione è dinamica la pagina rimpiazzata potrà appartenere a qualsiasi processo in esecuzione. Il sistema prima di effettuare una sostituzione deve controllare che i blocchi allocati a quel processo non scendano al di sotto del numero minimo. La sostituzione globale diminuisce il tasso di page-fault complessivo del sistema. Per il rimpiazzamento dei blocchi il sistema può basarsi anche su altri criteri, per esempio può tener conto della priorità dei processi: i processi con priorità minore non possono togliere blocchi ai processi con priorità maggiore. A.C. Neve – Sistemi operativi 3.0 41 Gestione del File System Introduzione La parte del sistema operativo che interagisce direttamente con l’utente è il File System. Questo permette all’utente di gestire e memorizzare i dati tramite l’utilizzo dei file e delle directory, che sono parte integrante del file system. Tutte le applicazioni effettuano la memorizzazione ed il recupero delle informazioni. Una possibile soluzione a questo problema prevede che un processo in esecuzione memorizzi una quantità limitata di informazioni nel suo spazio di indirizzamento. Ovviamente questa implementazione è in conflitto con le richieste delle applicazioni: • i processi devono avere la possibilità di memorizzare una grande quantità di informazioni, mentre questa soluzione offre una capacità di memorizzazione limitata dalla dimensione dello spazio di indirizzamento virtuale; • le informazioni memorizzate servono anche quando il processo che le sta utilizzando termina, mentre se vengono memorizzate nel suo spazio di indirizzamento del processo vengono perse; • i processi devono poter condividere le informazioni, ciò non è possibile con questa soluzione. L’alternativa è quella di memorizzare le informazioni sui dischi o altri supporti esterni, in entità chiamate file. Questa implementazione risolve tutti i problemi descritti in precedenza: le informazioni nei file sono permanenti e un file viene eliminato solo su esplicita richiesta dell’utente. I file sono gestiti dal sistema operativo che si occupa della loro implementazione e di tutte le problematiche relative a tali entità. La parte del Sistema Operativo che si occupa dei file è il File System ed è composto dall’insieme dei file e dalle informazioni necessarie alla gestione stessa dei file. Il File Il sistema operativo definisce una unità di informazione logica (il file) e la rende disponibile all’utente che può far riferimento al file tramite il suo nome ed i relativi attributi. L’utente interagisce direttamente con il File System e non si occupa di come sono memorizzate le informazioni all’interno del file. Il file può essere rappresentato: • • • senza struttura: il file è visto come una sequenza non strutturata di byte; con una struttura minima: il file è composto da una sequenza di record di lunghezza fissa, ognuno con una propria struttura interna; con una struttura più complessa: il file è formato da un albero di record e campi, non tutti necessariamente della stessa dimensione. Anche i dati che contiene possono essere diversi: un file può contenere informazioni numeriche, alfabetiche, alfanumeriche o in formato binario. Gli attributi che il sistema operativo associa a dei file, organizzati nella struttura di directory, sono diversi e variano a seconda del sistema: • Nome: attributo mediante il quale l’utente identifica il file. Il sistema operativo lo identifica invece tramite un ID, ovvero una sequenza di numeri. A.C. Neve – Sistemi operativi 3.0 42 • • • • • • Tipo: attributo utilizzato dal sistema operativo il quale specifica al sistema stesso l’applicazione da utilizzare per operare su quel file. Alcuni sistemi operativi impongono un tipo predefinito. Il tipo è indicato come estensione del nome del file. Locazione: specifica la posizione del file all’interno del disco o in qualsiasi altro supporto di memorizzazione. Dimensione: specifica la dimensione del file. Protezione: specifica chi può accedere al file e quali operazioni può eseguire sul file. Proprietario: proprietario corrente del file. Tempo: specifica la data di creazione del file. Il compito di un file system è quello di sollevare l’utente dalla necessità di dover compiere le operazioni di movimentazione dei dati dalla memoria principale alla memoria secondaria che generalmente consistono nel: • Traduzione delle richieste di accesso dallo spazio logico degli indirizzi a quello fisico • Gestione della memoria secondaria tenendo traccia delle allocazioni e disallocazioni • Trasferimento degli elementi che costituiscono il file • Gestione dei meccanismi di protezione e condivisione dei file. Le operazioni possibili sui file vengono eseguite tramite system call e variano in funzione della struttura del sistema operativo utilizzato. Il file system mantiene un puntatore al file: quando si effettua una scrittura tale puntatore è posizionato alla fine del file, in caso di lettura punta la locazione di memoria specificata. Oltre alla lettura e scrittura del file, le operazioni più utilizzate sono le seguenti: • • • • • • • • • Create: alloca un elemento nella struttura di directory. Il sistema individua lo spazio opportuno sul disco e assegna agli attributi del file gli opportuni valori; in particolare memorizza la posizione di partenza del file sul disco nell’attributo locazione. Delete: cancella il file. Il sistema operativo individua la locazione del file sul disco e dealloca lo spazio. Open: apre il file. Restituisce un identificatore utilizzato per effettuare le operazioni successive sul file. Close: chiude il file. Write: scrive dei dati nel file. Ha come parametri il nome del file e le informazioni da scrivere. Se la posizione corrente è alla fine del file, la dimensione del file viene estesa. Se la scrittura avviene nel mezzo del file, i dati esistenti vengono sovrascritti dai nuovi. Read: legge i dati dal file. Ha come parametri il nome del file e la locazione di memoria da leggere. Seek: posiziona il puntatore alla posizione corrente. Truncate: elimina una quantità di dati dal file. Append: Aggiungere nuovi dati in coda al file. Directory Il File System tiene traccia dei file per mezzo delle directory. Una directory è modellizzabile come un contenitore di file che viene gestito per mezzo dei descrittori di file. In generale le operazioni che si possono eseguire su una directory, oltre alla creazione e alla cancellazione della struttura, riguardano la creazione o la cancellazione di un file, la ricerca di un file tramite lo scorrimento della directory e la possibilità di elencare tutti i file contenuti in una directory, con le relative informazioni. A.C. Neve – Sistemi operativi 3.0 43 Le directory sono utilizzate per organizzare i file all’interno del File System ed esistono diversi modelli implementativi. Il sistema operativo deve prima di tutto tenere conto delle richieste dell’utente, ovvero: • • • Efficienza: le operazioni devono essere veloci. Nominazione: attributo riferito ai nomi dei file. All’interno di una directory i nomi dei file devono essere unici. Il sistema operativo deve controllare che non ci siano conflitti. Raggruppamento logico dei file: l’utente dispone di diversi file che devono essere raggruppati logicamente in classi di categorie diverse. DIRECTORY FLAT La più semplice organizzazione logica delle directory è rappresentata dalla struttura a singolo livello, in cui tutti i file sono contenuti in una directory comune. Il File System è organizzato su un unico livello, composto da una unica directory i cui elementi sono i descrittori dei file. descrittore file1 file1 directory descrittore ... file2 file2 ... descrittore fileN fileN Per creare un file il sistema deve prima di tutto controllare se esiste già un file con il nome specificato dall’utente, scorrendo tutta la struttura di directory. Se non esiste, compie una sequenza di operazioni: • • • alloca lo spazio sul disco per il file; introduce un nuovo elemento nella struttura di directory, inizializzando gli attributi secondo le specifiche dell’utente; alloca il puntatore del file allo spazio su disco. Per la cancellazione di un file, il sistema esegue analoghe operazioni: il file system cerca nella struttura di directory l’elemento con il nome specificato dall’utente, libera lo spazio sul disco e cancella il descrittore del file. L’organizzazione a singolo livello non risponde a nessuna delle esigenze richieste dall’utente in quanto scomoda e spesso inadeguata. DIRECTORY a Due LIVELLI Una alternativa all’organizzazione a singolo livello è fornita dalla struttura delle directory su due livelli, comunemente chiamata struttura a doppio livello. In questa struttura compare una directory principale costituita da tanti elementi quanti sono gli utenti; ogni elemento della directory principale contiene un puntatore ad una directory secondaria (directory utente o sottodirectory) organizzata come nella struttura a singolo livello. Rispetto alla struttura a singolo livello, con questa organizzazione la gestione dei nomi dei file risulta semplificata per l’utente e per il sistema, poiché ogni directory contiene un minor numero di file ed è possibile far uso di nomi di file uguali per sottodirectory differenti. A.C. Neve – Sistemi operativi 3.0 44 All’interno della directory secondaria si ripetono i problemi visti nella struttura a singolo livello: il sistema non fornisce un supporto per i file del singolo utente, ma li raggruppa in una unica directory. Per fare riferimento ad un file il sistema deve fare un doppio scorrimento: il primo per trovare l’utente a cui appartiene il file, il secondo per trovare il file. In generale il primo scorrimento può essere evitato, caricando in memoria la directory dell’utente che sta effettuando un accesso al sistema. Le volte successive il sistema non accede più al disco e le operazioni risulteranno più veloci. Utente1 Utente2 UtenteN ... descritt. file1 descritt. file2 descritt. file5 descritt. file7 descritt. file8 file1 file2 file5 file7 file8 ... directory principale descritt. file3 descritt. file4 sottodirectory file3 file4 file DIRECTORY GERARCHICHE Generalizzando l’organizzazione a doppio livello delle directory, si ottiene la struttura ad n livelli nelle quale il primo livello è sempre la radice, il livello successivo è composto dagli utenti ed infine per ogni utente possono essere previste più directory per formare un albero. Ogni directory può raggruppare sia file che sottodirectory. / Utente1 PROG C++ Utente2 VARIE JAVA f2.cpp directory radice ... UtenteN PROG HTML f7.htm directory utente GAMES COREL DOC sottodirectory utente WORD EXEL f3.doc f4.xls sottodirectory utente file utente DIRECTORY a GRAFO Abbiamo visto che l’organizzazione migliore per le directory è quella ad albero generico, in cui i descrittori dei file si trovano all’interno delle directory proprietarie. Con questa organizzazione di directory non è consentito che un file si trovi contemporaneamente sotto due o più directory diverse. A.C. Neve – Sistemi operativi 3.0 45 Il sistema deve quindi implementare un meccanismo che risolva il problema della condivisione dei dati tra più utenti. Questo problema riguarda sia i singoli file sia la condivisione di directory e più in generale la condivisione di un sottoalbero. Esaminiamo il caso più semplice, ovvero la condivisione dei file. La soluzione più semplice ed immediata prevede la duplicazione del file, quindi tutti gli utenti che vogliono condividerne uno creano una copia sotto la propria directory. Questo modo di operare è però svantaggioso in quanto: • occupa spazio (se i file sono molti lo spreco diventa evidente); • quando un file viene aggiornato il sistema operativo si deve preoccupare di mantenere aggiornate tutte le copie di quel file; • quando un file viene cancellato il sistema deve eliminare tutte le altre copie; • in fase di back up il sistema deve decidere quante copie del file conservare. In alternativa alla duplicazione è possibile memorizzare delle informazioni in più (attributi) per ogni file. Il sistema operativo mantiene per ogni file il nome del proprietario ed un contatore che indica il numero di directory che condividono il file. Alla creazione di un file, l’attributo “proprietario” viene inizializzato con la directory che contiene il file mentre l’attributo “contatore” viene posto a 1. Quando un’altra directory vuole condividere il file, il sistema inserisce nell’attributo locazione (memorizzato nel descrittore del file all’interno della directory) la posizione del file sul disco e incrementa l’attributo contatore. Per illustrare un’altra soluzione al problema della condivisione delle informazioni occorre prima introdurre il tipo di file link. Un link (o collegamento simbolico) è un puntatore ad un file (o ad una directory) che può contenere il path name assoluto o relativo del file (o della directory), che può essere così facilmente localizzato. La condivisione si realizza inserendo il link nell’attributo locazione del file presente. Quindi nella struttura di directory il sistema invece di specificare il puntatore alla posizione del file sul disco, indica il path name del file da condividere. Questo modo di operare realizza la struttura di directory a grafo. Poiché un file può avere più path name assoluti, diversi nomi presenti nelle directory possono riferirsi allo stesso file. Il sistema deve fare in modo che nessuna zona venga percorsa più di una volta nelle varie ricerche per evitare che si creino dei loop. Esistono appositi algoritmi che evitano il formarsi di cicli all’interno della struttura a grafo della directory. Quando un file condiviso viene cancellato il sistema deve gestire i link associati. Per i link simbolici (soft link) l’unico problema riguarda la successiva creazione di un altro file con lo stesso nome del file cancellato e avente la stessa locazione su disco. In generale si lascia all’accortezza dell’utente evitare che si crei questa situazione. In tutti gli altri casi i link simbolici rimangono pendenti. Più complicata è la gestione degli hard link, cioè i link che riferiscono indirizzi sul disco. Il sistema può tenere un contatore che indica il numero di link presenti per ogni file condiviso. La cancellazione di un file è consentita solo quando il contatore ha valore 0. A.C. Neve – Sistemi operativi 3.0 46 Struttura logica di una directory Le directory sono delle tabelle di nomi di file con una serie di attributi. Ogni entrata in tabella (quindi in directory) definisce un file. Un file è identificato da un nome, dai suoi attributi e dai puntatori d’accesso per le operazioni di read e write sui blocchi fisici. Le directory sono tenute sul disco mentre le entrate in directory di un file aperto sono copiate in memoria per velocizzare le operazioni. Per uniformare le operazioni del file system, anche le directory stesse sono considerate come file. La directory primaria, detta root (radice), viene posta ad un indirizzo predefinito del disco in modo che l’accesso non richieda ricerche e sia quindi immediato. Ogni volta che si inizializza un volume, viene automaticamente creata una directory root. Alla creazione di un nuovo file viene ricercata una riga vuota nella tabella della directory che viene riempita con le informazioni riguardanti il nuovo file. Esiste quindi una corrispondenza univoca tra le righe della tabella della directory ed i file. Esiste poi una directory dei nomi dei file FND (File Name Directory) il cui scopo è quello di definire una corrispondenza tra i nomi dei file ed un identificatore del file fisico utilizzato dal sistema per puntare fisicamente il file. Quando un utente richiede l’accesso ad un file si ricerca il nome del file nella FND per determinare il corrispondente identificatore che sarà poi utilizzato per localizzare il file stesso nella directory. Poiché le directory possono avere lunghezza variabile, la fine di una directory viene marcata con un carattere standard EOF (End Of File). Quando vengono creati nuovi file si usano le prime righe vuote della directory altrimenti si aggiungono nuove righe. Le operazioni più frequenti che si effettuano sulle directory sono: ricerca, inserimento e cancellazione di righe. Se l’organizzazione della directory è in forma di lista lineare, il numero di confronti per la ricerca sarà proporzionale al numero stesso di righe. Per migliorare le prestazioni si usa organizzare la directory secondo un albero binario bilanciato che richiederà un numero di confronti proporzionale al logaritmo in base 2 del numero di righe. Interfaccia utente E’ l’interfaccia per mezzo della quale gli utenti possono effettuare operazioni sui file per mezzo di comandi del sistema operativo. Le operazioni possibili sui file vengono eseguite tramite system call e variano in funzione del tipo di sistema operativo utilizzato. Quando è richiesta una operazione di read o write il sistema deve individuare l’elemento di directory nella struttura tramite il nome del file passato come parametro della system call. I comandi generali per la gestione dei file possono essere: • • • • • • • • • Create: alloca un elemento nella struttura di directory Delete: cancellazione di un file Open: apre il file per effettuare successivamente delle operazioni sul file Close: chiude il file Write: scrive dei dati nel file Read: legge i dati dal file Copy: copia il file in un altra directory Rename: modifica il nome del file Attribute: modifica gli attributi del file A.C. Neve – Sistemi operativi 3.0 47 Oltre alle operazioni sui file sono disponibili dei comandi per operazioni sulle directory: • • • • Dir: elenca i nomi dei file contenuti nella directory Make dir: crea una directory con un certo nome Remove dir: elimina una directory (se vuota) Change dir: cambia directory di lavoro Esistono poi i comandi per la gestione dei volumi: • • • • • • • Initdisk: inizializza i volumi mai usati in precedenza creando una radice con attributi Mount: avvisa il sistema operativo dell’avvenuta istallazione di un nuovo volume Dismount: avvisa il sistema operativo della prossima rimozione di un volume Verify: verifica l’integrità fisica del volume Backup: salvataggio, su di un’altra unità, del contenuto di un volume Restore: ripristino del contenuto di un volume in precedenza salvato Defrag: ottimizza l’allocazione dei file in un volume Modello tipizzato Con il termine tipizzazione si intende la capacità del sistema operativo di distinguere tra i vari tipi di file potendo così automatizzare la loro apertura dal relativo software applicativo. Il metodo più semplice consiste nel corredare il nome del file di una estensione che, mnemonicamente, ne ricordi la tipologia (exe, txt, doc, obj, jpg, hlm ecc.). Un altro metodo, meno utile all’utente, consiste nel codificare la tipologia del file all’interno del file stesso. Naturalmente la tipizzazione rende più complesso il lavoro del sistema operativo. Allocazione Allocazione a blocchi Un disco è suddiviso in partizioni, ognuna delle quali rappresenta un disco virtuale. In generale una partizione indica una parte del disco, ma, in alcuni sistemi operativi, può anche accadere il contrario, cioè esistono partizioni che occupano più dischi. In generale esistono diverse partizioni per separare le informazioni dai programmi. Una partizione è dedicata all’installazione del sistema operativo e dei programmi, un’altra memorizza i dati utilizzati dai programmi. Con una organizzazione di questo tipo, se si verifica un crash del sistema la partizione dei dati è salva. Inoltre la suddivisione del disco in partizioni permette di installare più sistemi operativi su una stessa macchina, ognuno su partizioni diverse. La prima partizione contiene sempre il file system. Il primo blocco contiene il master boot record (MBR), utilizzato per l’avvio della macchina. Il secondo blocco contiene la tabella delle partizioni che memorizza le informazioni sulle partizioni del disco; per ogni partizione viene indicato l’inizio e la fine. All’interno della tabella una partizione è contrassegnata attiva: questa è la partizione che contiene il sistema operativo che deve essere caricato su quella macchina. All’avvio della macchina parte il programma bios che esegue il MBR e carica la tabella delle partizioni, indicando anche la partizione attiva. Quindi viene caricato il sistema operativo tramite l’esecuzione di un apposito programma presente nella partizione attiva. Da questo momento in poi la macchina è sotto il controllo del sistema operativo. A.C. Neve – Sistemi operativi 3.0 48 Nei sistemi basati sull’uso della memoria virtuale esiste una partizione (la seconda) dedicata all’area di swap, la cui grandezza dipende dal tipo di programmi che vengono eseguiti nel sistema. Poiché è difficile da dimensionare, si definisce uno spazio di swap grande due volte lo spazio di memoria RAM. Il File System ha anche il compito di gestire lo spazio della memoria secondaria che viene considerata come un insieme lineare di blocchi. Il modulo di I/O si interessa poi della gestione degli indirizzi fisici. Il File System deve mantenere traccia di tutti i blocchi del disco allocati ad un certo file e di tutti i blocchi liberi e disponibili per altre allocazioni. Le strategie utilizzate per l’allocazione dello spazio disco dovranno cercare sempre di ottimizzare alcuni dei seguenti fattori: • • • • velocizzazione di tutte le operazioni di accesso ai file e di allocazione e disallocazione, questo parametro viene valutato con il numero di accessi al disco per identificare un blocco utilizzazione del trasferimento multisettore e multitraccia per ridurre i tempi di accesso ottimizzazione dell’utilizzo dello spazio disco per ridurre la frammentazione esterna impegno di CPU e di memoria centrale per la gestione delle tabelle di traccia Il problema della gestione del disco è molto simile a quello della memoria centrale ma con tempi molto più elevati per il disco. Come nel caso della memoria centrale, si potranno anche ora utilizzare le diverse strategie di allocazione contigua e non contigua. Allocazione contigua L’allocazione contigua è quella più semplice. L’idea di base consiste nell’allocare il file sul disco assegnandoli un certo numero di blocchi contigui secondo lo schema tipico dell’array lineare. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Start Block 15 16 Block 5 20 Block 1 Block 2 Block 3 Block 4 17 18 19 End Block 21 22 23 24 L’indirizzo iniziale e la dimensione del file sono memorizzati nella directory e sono sufficienti per accedere a qualsiasi blocco del file. L’accesso sequenziale è molto semplice e veloce poiché i blocchi sono adiacenti sia logicamente che fisicamente favorendo così il trasferimento multisettore. Altrettanto semplice e veloce risulta anche l’accesso casuale poiché l’indirizzo fisico del generico blocco può essere calcolato dall’indirizzo di partenza del file memorizzato nella directory. Per es., l’indirizzo del blocco 2 si può calcolare sommando all’indirizzo dello Start Block (10) un displacement pari a 2 ottenendo l’indirizzo del Block 2 (12). Gli indirizzi e le dimensioni delle aree libere sul disco sono mantenuti in una lista separata e memorizzati nelle entrate non usate della directory. A.C. Neve – Sistemi operativi 3.0 49 In fase di allocazione, dovranno essere cercate delle aree libere, la scelta di queste aree potrà essere effettuata con i classici algoritmi di best fit o first fit. Il problema della frammentazione interna non è particolarmente critico mentre lo è quello della frammentazione esterna in quanto si possono venire a determinare numerosi gruppi di blocchi liberi aventi dimensioni totali troppo piccole per essere utilizzati ma che complessivamente rappresentano uno spazio disco rilevante. Questo problema può essere risolto con una periodica operazione di deframmentazione del disco. L’allocazione contigua richiede che le dimensioni del file siano note in anticipo e questo non sempre è possibile come nel caso di file che possono aumentare di dimensione nel tempo. Si ipotizzi che un file aumenti le sue dimensioni dopo che il sistema lo ha allocato. Se l’allocazione è contigua ed è libero il blocco successivo all’ultimo allocato a quel file, il sistema estende l’allocazione normalmente apportando le opportune modifiche agli attributi del file. Se il blocco successivo è occupato da un altro file, per evitare di abortire il programma, il sistema deve trovare un insieme di blocchi contigui liberi in grado di soddisfare la nuova dimensione del file e spostare tutto il file nel buco individuato, rilasciando quello vecchio. Con questo tipo di organizzazione il sistema impiega molto tempo nel trasferimento dei file da una parte all’altra del disco. Una alternativa consiste nel gestire il problema preventivamente, allocando il file in uno spazio maggiore di quello richiesto prevedendo le sue variazioni nel tempo. Allocazione concatenata Con l’allocazione dei file a lista concatenata non è più richiesta la contiguità dei blocchi. Il sistema alloca i blocchi liberi ovunque trovi spazio libero all’interno del disco e li collega tramite l’utilizzo di puntatori. 0 1 5 6 2 3 7 8 8 10 Start Block 15 20 Block 1 11 4 9 9 12 Block 2 13 Block 3 14 Block 4 17 18 19 22 23 24 12 6 16 16 End Block 21 e n d In ogni blocco vengono riservati alcuni byte per definire il puntamento al blocco successivo. L’ultimo blocco contiene l’indicatore di terminazione del file. Nella directory, il file è individuato dal puntatore iniziale corrispondente all’indirizzo, sul disco, del primo blocco. L’accesso ad un file di questo tipo deve necessariamente iniziare dal primo blocco il cui indirizzo è contenuto nella directory e proseguire attraverso tutti i blocchi fisici utilizzando i puntatori ai blocchi successivi. Questo modo di operare facilita notevolmente l’accesso sequenziale in quanto il sistema individua l’indirizzo del primo blocco contenuto nella struttura di directory mentre gli indirizzi dei blocchi successivi li individua scorrendo i blocchi. Più complesso è invece l’accesso multiblocco in quanto necessita della conoscenza anticipata del numero di blocchi che dovranno essere trasferiti e dei loro indirizzi. A.C. Neve – Sistemi operativi 3.0 50 L’incremento delle dimensioni del file avviene in modo abbastanza semplice in quanto basta allocare un qualsiasi altro blocco libero e concatenarlo con quello che era l’ultimo. La disallocazione avviene invece contemporaneamente su tutti i blocchi. Questo tipo di allocazione non soffre del problema della frammentazione: quella esterna non esiste perché il sistema recupera tutti i blocchi liberi, mentre quella interna è presente solo nell’ultimo blocco allocato ad ogni file, come accade con qualsiasi metodo di allocazione. La deframmentazione è comunque utile in quanto migliora il tempo di accesso. Il problema fondamentale di questo tipo di allocazione riguarda lo spazio occupato dai puntatori. Per ogni blocco occupato il sistema deve mantenere un puntatore al blocco successivo: ciò implica minor spazio disponibile per la memorizzazione dei dati, quindi lo spazio su disco non è efficientemente sfruttato. Lo svantaggio di questa tecnica è costituito dalla lentezza degli accessi casuali e dall’impossibilità di utilizzare il trasferimento multiblocco. Un altro svantaggio riguarda l’eventuale possibilità di danneggiamento di un blocco che implica l’impossibilità di accesso a tutti i blocchi successivi. Allocazione indicizzata L’indicizzazione è il metodo più efficiente di allocazione non contigua sul disco. Utilizza ancora il sistema dei puntatori ai blocchi allocati ma migliora il tempo dell’accesso casuale raggruppando i puntatori nel primo blocco detto i-node (index block). La directory dei nomi, anziché contenere l’indirizzo del primo blocco, contiene l’indirizzo del blocco indice. L’indicizzazione elimina così le differenze tra i tempi di accesso esistenti tra l’accesso sequenziale e l’accesso casuale. 0 1 2 3 4 5 6 Start Block 11 7 8 9 12 Block 1 13 Block 2 14 10 Index Block 6 8 9 12 16 Block 3 15 16 17 18 19 20 End Block 21 22 23 24 Ovviamente ad ogni file deve essere allocato un numero maggiore di blocchi rispetto alle richieste: un file composto da k blocchi, viene allocato in (k+1) blocchi fisici. Quindi la presenza del blocco indice comporta l’introduzione di overhead che risulta maggiore per i file di dimensioni minori. Inoltre un file con dimensione molto estesa può richiedere un numero elevato di blocchi indice. Per esempio, facendo uso di blocchi da 2048 byte e puntatori da 4 byte, potranno essere indicizzati soltanto 512 blocchi (nel blocco indice si potranno inserire 2048/4 puntatori) corrispondenti ad una dimensione totale del file di 512*2048 = 1 megabyte che potrebbe non risultare sufficiente. Si risolve questo problema facendo uso dell’indicizzazione multilivello: il blocco indice di primo livello viene usato per puntare ad un blocco indice di secondo livello il quale, a sua volta, è usato per puntare ai blocchi di dati. Utilizzando l’indicizzazione a due livelli dell’esempio precedente si avrà: un primo blocco indice che punta a 512 blocchi indice di secondo livello, ognuno dei quali punterà a 512 blocchi dati per un totale di 524 megabyte. Per migliorare i tempi di risposta si mantengono in memoria i blocchi indice di uso recente e frequente come pure i blocchi indice dell’insieme dei blocchi liberi contigui. A.C. Neve – Sistemi operativi 3.0 51 FAT (File Allocation Table) Per ridurre la percentuale di spazio utilizzato per la gestione della lista concatenata è stato introdotta l’allocazione a lista concatenata a cluster. Un cluster è costituito da un gruppo di blocchi contigui. Se per la memorizzazione di un file serve un numero maggiore di blocchi di quelli contenuti in un cluster, il sistema collega più cluster tramite puntatori. La lista concatenata è quindi gestita a cluster. Adottando questo metodo il sistema risolve il problema dell’overhead introdotto dai puntatori nell’allocazione a blocchi poiché il numero dei puntatori è notevolmente ridotto. Introduce però frammentazione interna perché aumenta la quantità di disco inutilizzato relativo all’ultimo cluster. La dimensione del cluster è maggiore della dimensione del blocco fisico; un cluster può essere paragonato ad un blocco molto grande: se però aumenta la dimensione del blocco aumenta la frammentazione interna. Nell’allocazione concatenata (a blocchi o a cluster) per leggere un file la testina si deve spostare molte volte: il sistema ricava dalla struttura di directory l’indirizzo del primo blocco su cui è allocato il file e vi accede; quindi tramite il puntatore del primo blocco individua il secondo blocco e così via finché arriva alla fine. In generale i blocchi di un file si trovano su tracce diverse. Per ottimizzare e diminuire il numero di spostamenti della testina il sistema utilizza la file allocation table (FAT), una tabella presente all’inizio di ogni partizione (quindi sul disco) in cui il numero di entrate corrisponde al numero di blocchi totali presenti sul disco. E’ indirizzata tramite il numero del primo blocco che il sistema può estrarre dalla struttura di directory ed ogni entrata contiene l’indirizzo del blocco successivo che compone il file, oppure contiene il valore “null” o un codice di “end of file” se è l’ultimo blocco del file. I blocchi liberi sono indicati con 0. Elemento della struttura di directory Name Start ... FILE.doc 1 ... blocco libero 1 2 3 4 5 6 7 8 9 10 … FAT 4 … … 9 … … 0 … null … ... La FAT permette di realizzare la concatenazione dei blocchi allocati ai file, senza memorizzare questi puntatori all’interno del blocco stesso migliorando la velocità e consentendo la duplicazione della FAT. La presenza della FAT rende più veloce l’accesso diretto ai file: gli spostamenti della testina sono minori, le letture avvengono sullo stesso blocco o su blocchi appartenenti alla stessa traccia. La FAT è soggetta a modifiche, dovute al cambiamento dello stato di allocazione di un disco. Ogni volta il sistema deve accedere al disco (supporto di memorizzazione ad accesso lento) per operare sulla tabella. Per rendere più veloci gli accessi il sistema mantiene una copia della FAT in memoria (se il numero dei blocchi che compongono il disco è elevato solo una parte sarà copiata in memoria) alla quale accede ogni volta che è richiesto e periodicamente trasferisce le modifiche dalla memoria al disco. Nel caso in cui si verificasse un crash del sistema quando non fosse ancora avvenuto A.C. Neve – Sistemi operativi 3.0 52 l’aggiornamento della tabella sul disco, tutte le modifiche fatte dall’ultimo accesso alla FAT sul disco verrebbero perse, quindi la tabella presente sul disco non sarebbe più attendibile. La FAT è memorizzata sul disco ed ha una entrata per ogni cluster del volume con un codice che indica il numero del cluster successivo. Codice FAT 0000H 0002H – FFEFH FFF0H – FFF6H FFF7H FFF8H – FFFFH Cluster libero Indirizzo cluster successivo Cluster riservato Cluster guasto Ultimo cluster di un file Il massimo numero di cluster dipende dalla dimensione dell’entrata della FAT Cluster Size Dim Volume FAT 12 0.5 KB 2 MB 1 KB 4 MB 2 KB 8 MB 4 KB 16 MB 8 KB 16 KB 32 KB Dim Volume FAT 16 Dim Volume FAT 32 128 MB 256 MB 512 MB 1024 MB 2048 MB 1 TB 2 TB 2TB 2 TB Cluster di grandi dimensioni ottimizzano i tempi di I/O ma aumentano la frammentazione interna. L’organizzazione della FAT di un volume è fatta in modo tale che: • • • • il primo cluster contiene il bootstrap con le informazioni sul volume dopo è presente la FAT che fornisce la lista dei puntatori ai cluster e la lista dello stato dei cluster il resto del volume contiene i cluster dei file o directory allocati il primo file del volume è detto root directory e genera l’albero gerarchico Il boot record contiene le seguenti informazioni: • • • • • istruzione di salto all’inizio del codice di boot descrizione del volume (BIOS Parameter Block) codice di bootstrap eventuale tabella delle partizioni error checking Il BIOS Parameter Block contiene invece le seguenti informazioni: • • • • • • • dimensioni del settore fisico numero di settori per traccia numero di tracce per cilindro numero totale di settori del volume numero di settori per cluster numero di settori riservati numero di entrate della root directory A.C. Neve – Sistemi operativi 3.0 53 L’utilizzo delle partizioni del disco ha determinato un cambiamento del boot record. Il primo settore del disco viene ora detto MBR (Master Boot Record) che include la FDISK table ed il codice di bootstrap della partizione attiva dalla quale legge il primo settore detto PBR (Partition Boot Record) che è organizzato come in precedenza esposto. Nella FAT 32, una directory è un file contenente una serie di entrate a 32 byte ognuna delle quali descrive un file facendo uso della seguente struttura: N° del byte 0 – 10 11 12 – 21 22 –23 24 – 25 26 – 27 28 – 31 Descrizione Nome corto del file SFN (Short Filename): 8 byte per il nome e 3 per l’estensione Se è più corto si aggiungono degli spazi. Il punto non si memorizza. Attributi: sono usati solo 6 bit con i seguenti significati Flag Archivio (settato se il file è stato modificato dopo l’ultimo backup) Flag Only read (non può essere modificato) Flag System ( file di sistema) Flag Hidden (file nascosto che non compare nei file della directory) Flag Directory (indica che il file è una directory) Flag Volume (indica che non è un file ma una etichetta di volume) Si usano solo nella FAT 32 e contengono le seguenti informazioni: 12: riservato 13: millisecondi dell’orario di creazione 14,15: orario di creazione 16,17: data di creazione 18,19: data dell’ultimo accesso 20,21: parte più significativa del numero del primo cluster Orario dell’ultima modifica Data dell’ultima modifica Parte meno significativa del numero del primo cluster Dimensione del file (4Gbyte) A.C. Neve – Sistemi operativi 3.0 54 Glossario background batch BIOS (Basic I/O System) bootstrap boundless buffer cluster context switch deadlock dispatcher displacement embedded foreground fork interleaving interrupt join Sfondo. Continuamente presente. Termine applicato ad un processo a bassa priorità che viene eseguito quando la risorse non sono usate per altre applicazioni. Processi in stato di sospensione pronti però ad attivarsi per fornire servizi (utility, antivirus ecc). Tipo di sistema operativo che prevede l’esecuzione di un programma alla volta sempre residente nella memoria: il calcolatore legge un programma, lo carica in memoria e lo esegue fino alla fine o fino all’errore, poi stampa il risultato. Passa poi ad un successivo programma Insieme di routine contenute nella ROM utilizzate all’avviamento del sistema, per le comunicazioni tra gli elementi base del sistema e per il settaggio delle varie parti. Tecnica di trasferimento di un grosso programma in memoria centrale che consiste nel caricamento di un ristretto numero di istruzioni (bootstrap loader) prelevate da una ROM le quali sono poi usate per caricare il resto del programma. Questa tecnica è tipicamente usata nella fase iniziale di avviamento di un computer. Senza limiti. I/O bound o CPU bound sono detti i processi che fanno un uso elevato di I/O o di CPU Cuscinetto. Area di memoria usata per contenere, temporaneamente, dei dati. Viene utilizzata per facilitare il trasferimento di dati tra dispositivi aventi differenti velocità o non sincronizzabili. Grappolo. Gruppo di settori di un hard disk costituenti una singola unità di trasferimento dati. Commutazione di contesto. Indica il passaggio della CPU da un processo ad un altro con il salvataggio dei registri del processo sospeso ed il ripristino di quello attivato. Viene effettuato dal dispatcher. Punto morto. Indica un conflitto non risolvibile. Condizione di impedimento dell’esecuzione di due processi in attesa di una risorsa posseduta dall’altro. Condizione di stallo. Colui che spedisce. E’ un modulo del sistema operativo che attua il cambio di contesto. Spostamento, spiazzamento. Differenza numerica tra due valori uno dei quali è un valore fisso o di riferimento. Incluso, incassato. Riferito a computer inclusi in sistemi la cui attività non rientra in quella tipica dei sistemi di elaborazione. In primo piano. Termine applicato ad un processo ad alta priorità che interagisce con l’utente ed è, quindi, in primo piano. Biforcazione. Operazione di creazione di un nuovo processo. Fenomeno dovuto al fatto che, durante il trasferimento, il disco continua a ruotare ed il successivo settore passa sotto la testina mentre è in corso il trasferimento. Richiesta asincrona di asservimento. Determina la sospensione del processo in corso di esecuzione per trasferirlo ad un altro. Generalmente la richiesta proviene da un device esterno ma può essere anche generata da una condizione di errore software. Unione, giuntura. Operazione usata per unificare due sequenze di codice sdoppiate da una fork. A.C. Neve – Sistemi operativi 3.0 55 latency time link locality MBR (Master Boot Record) overhead path name PCB (Process Control Block) polling preemptive race condition scheduling seek time sezione critica spooling Termine usato per indicare il ritardo tra l’inizio di una richiesta e la ricezione del dato richiesto. Generalmente indica il tempo che occorre per trovare il settore all’interno della traccia di un disco. Collegamento. Connessione logica tra due differenti oggetti sia hardware che software. Vicinanza. Termine utilizzato per descrivere la velocità di accesso al codice eseguibile da parte di un programma e la relativa disponibilità dello stesso nei vari livelli di memoria. Può essere inteso sia in termini spaziali che temporali. Rappresenta il primo settore di un hard disk che contiene il codice di bootstrap della partizione attiva necessario per l’avviamento. In alto, sopraelevato. Termine utilizzato per indicare la quantità di tempo dedicato all’esecuzione di funzioni di sistema operativo piuttosto che all’esecuzione di programmi di utente. Nome del percorso che deve essere seguito per raggiungere un file all’interno di una gerarchia di directory. In una rete indica il percorso che un messaggio deve effettuare tra i vari nodi della rete. E’ una struttura dati mediante la quale il sistema operativo gestisce un processo e ne rappresenta lo stato globale. Per poter sospendere e poi riattivare un processo è necessario poter ricordare le condizioni in cui si trovava all’atto della sospensione. Questo insieme di informazioni è chiamato stato del processo ed è costituito da tutte le informazioni necessarie per poter riprendere l’esecuzione dopo una sospensione Sondaggio, scrutinio, interrogazione sequenziale. Procedura di interrogazione ciclica di un sequenza di periferiche per verificare l’esistenza di una richiesta di asservimento. Riferito ad un processo, si dice preemptive quando è possibile togliere la CPU ad un processo in relazione ad eventi esterni al processo stesso e cioè può essere sospeso anche se dispone di tutte la risorse necessarie. Con riferimento ad una risorsa,di dice preemptive se può essere sottratta al processo senza conseguenze. Situazione che si verifica quando più processi accedono e manipolano gli stessi dati concorrentemente e l’esito varia a seconda dell’ordine con il quale sono avvenuti gli accessi. Catalogare. Si intende per scheduling il criterio di individuazione, di uno tra più richiedenti, al quale assegnazione l’esecuzione di un sevizio. Operazione che, generalmente, consistente nella determinazione dell’ordine secondo il quale un insieme di programmi dovrà essere eseguito. In un hard disk è il tempo impiegato per l’individuazione della traccia che contiene il blocco fisico cercato; questo tempo è proporzionale alla distanza che la testina deve percorre ad ogni spostamento. Parte di programma dalla quale si può accedere a risorse comuni o modificare dati comuni ad altri processi. Ad un solo processo per volta è consentito eseguire la propria sezione critica: l’esecuzione delle sezioni critiche da parte dei processi deve essere mutuamente esclusiva nel tempo, cioè, quando un processo opera in sezione critica, nessun altro processo può fare altrettanto. Operazione consistente nell’uso dell’hard disk per il trasferimento dati da memoria centrale a dispositivi periferici più lenti migliorando l’efficienza d’uso della CPU. Molto usato per i processi di stampa. A.C. Neve – Sistemi operativi 3.0 56 starvation swapping thrashing thread throughput timesharing transfer time turnaround Condizione per la quale un processo non riesce ad iniziare o completare la sua esecuzione perché continuamente sospeso da altri a priorità superiore. Scambio. Trasferimento di parti (pagine) di programma da o per l’hard disk durante l’esecuzione a causa della mancanza di spazio libero in memoria centrale. Bastonatura. Fenomeno per il quale il sistema tende a far aumentare notevolmente la frequenza di page fault, il sistema cioè trascorre gran parte del suo tempo in operazioni di swapping di pagine con un impegno di CPU apparentemente basso ma con un notevole degrado delle prestazioni del sistema. Unità di base di utilizzo della CPU. Un thread è una parte di un processo che può essere eseguita contemporaneamente ad altri thread riuscendo così ad aumentare lo pseudoparallelismo. Portata. Quantità di lavoro (istruzioni) eseguita nell’unità di tempo. Nei sistemi di comunicazione è la quantità di dati trasmessi nell’unità di tempo. Suddivisione del tempo di CPU tra più processi Tempo impiegato per la lettura da una unità ed il trasferimento in un’altra unità del singolo blocco di dati. Cambiamento di direzione. Tempo di elaborazione delle richieste di pagina. A.C. Neve – Sistemi operativi 3.0 57