Introduzione ai sistemi operativi
Un sistema operativo è un insieme di programmi (software) che gestisce gli elementi fisici di un calcolatore (hardware); fornisce una piattaforma ai programmi applicativi e agisce da intermediario fra l’utente e la struttura fisica del calcolatore.
Ruolo del sistema operativo nell'insieme dei sistemi di calcolo
Un sistema di calcolo si può suddividere in quattro componenti: dispositivi fisici, sistema operativo, programmi applicativi e utenti.
I dispositivi fisici - composti dall’unità centrale d’elaborazione (CPU), dalla memoria e dai dispositivi d’immissione ed emissione dei dati - forniscono al sistema le risorse di calcolo fondamentali.
I programmi applicativi (editor di testo, fogli di calcolo, compilatori e programmi di consultazione del Web) definiscono il modo in cui si usano queste risorse per la risoluzione dei problemi computazionali degli utenti.
Il sistema operativo controlla e coordina l’uso dei dispositivi da parte dei programmi applicativi per gli utenti; offre gli strumenti per impiegare in modo corretto le risorse; non compie operazioni di per sé utili, ma definisce semplicemente un ambiente nel quale altri programmi possono lavorare in modo utile.
Dal punto di vista del calcolatore, il sistema operativo è il programma più strettamente correlato ai suoi elementi fisici. In tale contesto è possibile considerarlo come un assegnatore di risorse. Di fronte a numerose ed eventualmente conflittuali richieste di risorse, il sistema operativo deve decidere come assegnarle agli specifici programmi e utenti affinché il sistema di calcolo operi in modo equo ed efficiente.
Un sistema operativo è in effetti un programma di controllo che gestisce l’esecuzione dei programmi utenti in modo da impedire che si verifichino errori o che il calcolatore sia usato in modo scorretto, soprattutto per quel che riguarda il funzionamento e il controllo dei dispositivi di I/O.
La percezione di un calcolatore da parte di un utente dipende principalmente dall’interfaccia impiegata. In questo caso il sistema operativo si progetta considerando principalmente la facilità d’uso. Le prestazioni sono naturalmente importanti; questi sistemi, tuttavia, si focalizzano sull’esperienza del singolo utente più che sull’utilizzo delle risorse in generale.
Alcuni utenti usano terminali connessi a mainframe o minicalcolatori condividendone le risorse con altri utenti anche loro connessi tramite terminali. In questo caso il sistema operativo si progetta per massimizzare l’utilizzo delle risorse, garantendo che tutto il tempo disponibile di CPU, la memoria e le periferiche di I/O siano impiegati in modo equo ed efficiente.
In altri casi ci sono utenti che usano stazioni di lavoro, connesse a reti di altre stazioni di lavoro e a server; dispongono di risorse loro riservate: altre devono condividerle, come la rete e i server, per l’accesso ai file e per i servizi di stampa di elaborazione. Tutto ciò richiede che il sistema operativo sia progettato ponderando l’adeguatezza all’uso individuale e l’utilizzo ottimale delle risorse.
Alcuni calcolatori sono integrati nei prodotti più vari (embedded system), e hanno poca o nessuna visibilità per gli utenti: i calcolatori integrati negli elettrodomestici e nelle automobili, per esempio, possono avere una tastiera numerica, e accendere o spegnere alcuni indicatori luminosi per segnalare il proprio stato; questi apparati e i relativi sistemi operativi, nella maggior parte dei casi, sono tuttavia progettati per funzionare senza l’intervento degli utenti.
In generale non si dispone di una definizione completa ed esauriente di sistema operativo. Non si dispone nemmeno di una definizione universalmente accettata di che cosa faccia parte di un sistema operativo; la questione riguardante i componenti di un sistema operativo ha assunto una certa rilevanza anche in seguito all’azione legale promossa nel 1998 dal Dipartimento della giustizia degli Stati Uniti contro la Microsoft, accusata di includere troppe funzioni nel sistema operativo e quindi di concorrenza sleale nei confronti dei produttori e rivenditori di applicazioni. Di conseguenza fu dichiarata colpevole di sfruttamento di monopolio a danno della concorrenza.
Organizzazione di un sistema di calcolo
Un moderno calcolatore d’uso generale è composto da una CPU e da un certo numero di controllori di dispositivi connessi attraverso un canale di comunicazione comune (bus) che permette l’accesso alla memoria condivisa dal sistema. Ciascuno di questi controllori si occupa di un particolare tipo di dispositivo fisico (per esempio, unità a disco, dispositivi audio e unità video). La CPU e questi controllori possono operare in modo concorrente, contendendosi i cicli d’accesso alla memoria. La sincronizzazione degli accessi alla memoria è garantita dalla presenza di un controllore di memoria.
L’avviamento del sistema, conseguente all’accensione fisica di un calcolatore, così come il riavvio di un calcolatore già acceso, richiede la presenza di uno specifico programma iniziale, di solito non troppo complesso, detto programma d’avviamento (bootstrap program), in genere contenuto in tipi di memoria noti con il termine generale di firmware. Esempi di firmware sono le memorie a sola lettura (ROM), e le memorie programmabili cancellabili elettricamente (EEPROM). La funzione di tale programma consiste nell’inizializzare i diversi componenti del sistema, dai registri della CPU ai controllori dei diversi dispositivi, fino al contenuto della memoria centrale. Il programma d’avviamento deve caricare nella memoria il sistema operativo e avviarne l’esecuzione, perciò individua e carica nella memoria il kernel del sistema operativo; il sistema operativo avvia quindi l’esecuzione del primo processo d’elaborazione, per esempio init, e attende che si verifichi qualche evento.
Un evento è di solito segnalato da un’interruzione dell’attuale sequenza d’esecuzione della CPU, che può essere causata da un dispositivo fisico o da un programma.
Nel primo caso si parla di segnale d’interruzione o, più brevemente, interruzione (interrupt); si tratta di segnali che i controllori dei dispositivi e altri elementi dell’architettura possono inviare alla CPU, di solito attraverso il bus di sistema.
- Ogniqualvolta riceve un segnale d’interruzione, la CPU interrompe l’elaborazione corrente e trasferisce immediatamente l’esecuzione a una locazione fissa della memoria.
- Di solito, questa locazione contiene l’indirizzo iniziale della procedura di servizio per quel dato segnale d’interruzione.
- Una volta completata l’esecuzione della procedura richiesta, la CPU riprende l’elaborazione precedentemente interrotta.
Ciascun tipo di calcolatore ha il proprio meccanismo delle interruzioni; ciò nonostante molte funzioni sono comuni.
Un segnale d’interruzione deve causare il trasferimento del controllo all’appropriata procedura di servizio dell’evento a esso associato. Il modo più semplice per gestire quest’operazione è quello di impiegare una procedura generale che esamina le informazioni presenti nel segnale d’interruzione, e invoca la procedura di gestione dello specifico segnale d’interruzione. Per chiamare un interrupt da programma, si usa l’istruzione assembly INT n dove n è un numero intero maggiore o uguale a zero che viene usato come indice per accedere alla n-esima posizione del vettore degli interrupt: posizionato a partire dall’indirizzo 0 in memoria, è una tabella di puntatori alle specifiche procedure contenente gli indirizzi delle procedure di servizio delle interruzioni.
Sistemi operativi radicalmente differenti, come Windows e UNIX, usano lo stesso meccanismo di gestione delle interruzioni. Quando la CPU esegue l’istruzione INT n, l’Instruction Pointer viene fatto puntare all’istruzione successiva ad INT, il processore passa in modalità kernel con il livello di privilegio da usare, salva i valori attuali dei registri SS, SP, CS, IP nello stack di sistema del processo che ha chiamato l’interrupt, cioè lo stack dedicato all’esecuzione in modalità kernel per quel processo, poi carica i registri SS e SP con i valori dello stack di sistema per quel livello di privilegio e carica i registri CS e IP con l’indirizzo della routine di gestione dell’interrupt trovato nell’n-esima posizione del vettore di interrupt.
La routine dell’Interrupt termina con l’istruzione IRET (Interrupt Return) che carica i registri IP, CS, SP e SS con i valori salvati sullo stack di sistema. Fa tornare il processore in modalità utente. Terminato il servizio dell’interruzione, l’indirizzo di ritorno precedentemente salvato viene caricato nel contatore di programma (program counter), che contiene l’indirizzo della prossima istruzione da eseguire, consentendo la ripresa della computazione interrotta.
Tipi di interrupt
- Sincroni (interrupt software) - esplicitamente chiamati dalla CPU mediante l’istruzione INT n (ad esempio la CPU ordina di fare output su video di un carattere chiamando una syscall).
- Asincroni (interrupt hardware) - scatenati da una periferica che, mediante il BUS, avvisa la CPU che è accaduto un evento che deve essere gestito (ad esempio, la tastiera avvisa che l’utente ha premuto un tasto). La periferica invia un segnale che codifica un numero intero, tale numero è l’indice dell’Interrupt da chiamare. La CPU interrompe quello che sta facendo e gestisce l’Interrupt.
Priorità e gestione degli interrupt
- Mascherabili (ad esempio: è arrivato un carattere da tastiera, ma se c’è qualcosa più urgente da fare è meglio svolgere i compiti più urgenti).
- Non mascherabili (ad esempio, un avviso di errore sul Bus - bisogna gestirlo immediatamente).
Durante l’esecuzione di una routine di gestione degli Interrupt, la CPU setta un bit che disabilita la gestione degli Interrupt Mascherabili.
I moderni SO sono detti "Interrupt Driven" poiché gran parte delle funzionalità del kernel viene eseguita all'interno di una qualche routine di gestione di un interrupt, cioè viene eseguita come reazione al verificarsi di un evento che ha sollevato un interrupt o un’eccezione.
Uno dei più importanti eventi che sollevano un interrupt è lo scadere del "quanto" di tempo di esecuzione che lo scheduler ha concesso ad un processo (o ad un thread all'interno di un processo). Allo scadere del quanto di tempo, il timer di sistema genera un interrupt che risveglia lo scheduler. Lo scheduler decide quale altro processo (o thread) dovrà essere eseguito e passa il controllo a tale processo (thread).
Eccezioni e gestione delle eccezioni
Nel secondo caso si parla di segnale di eccezione o, più brevemente, eccezione (exception), che può essere causata da un programma in esecuzione a seguito di un evento eccezionale, riconosciuto tramite l’architettura della CPU; oppure a seguito di una richiesta specifica effettuata da un programma utente per ottenere l’esecuzione di un servizio del sistema operativo, attraverso una chiamata di sistema (system call).
Tipologie di eccezioni:
- TRAP: l’istruzione che ha causato l’eccezione viene momentaneamente sospesa, ma alla fine della gestione del trap viene portata a buon fine.
- FAULT: l’istruzione che ha causato l’eccezione viene interrotta, e viene eseguita la routine di gestione. In un successivo momento l’istruzione verrà rieseguita da principio (es: Page Fault, la pagina di memoria che contiene l’indirizzo a cui stiamo cercando di accedere è swappata su disco. La pagina viene caricata in memoria. Prima o poi l’istruzione verrà nuovamente eseguita).
- ABORT: causata da errore irrimediabile, l’istruzione viene abortita e il processo killato (es: divisione di un numero per zero o segmentation fault).
Struttura della memoria
La CPU può caricare istruzioni esclusivamente dalla memoria, quindi tutti i programmi da eseguire devono esservi caricati. I computer general-purpose eseguono la maggior parte dei programmi da una memoria riscrivibile, la memoria principale, chiamata anche memoria ad accesso diretto (RAM). La memoria principale è realizzata solitamente con una memoria dinamica ad accesso diretto (DRAM). I computer utilizzano anche altri tipi di memoria. Dal momento che la memoria di sola lettura (ROM) non può essere modificata, solo i programmi statici vi sono salvati. Le EEPROM non possono essere cambiate di frequente, e per questo contengono per lo più programmi statici. Sulle EEPROM degli smartphone, ad esempio, sono memorizzati i programmi installati inizialmente dalla fabbrica.
Tutte le tipologie di memoria forniscono un vettore di parole. Ciascuna parola possiede un proprio indirizzo. L’interazione avviene per mezzo di una sequenza di istruzioni load e store opportunamente indirizzate. L’istruzione load trasferisce il contenuto di una parola della memoria centrale in uno dei registri interni della CPU, mentre store copia il contenuto di uno di questi registri nella locazione di memoria specificata. Oltre agli accessi dovuti alle operazioni load e store, che si richiedono in modo esplicito, la CPU preleva automaticamente dalla memoria centrale le istruzioni da eseguire.
La tipica sequenza d’esecuzione di un’istruzione, in un sistema con architettura di von Neumann, comincia con il prelievo (fetch) di un’istruzione dalla memoria centrale e il suo trasferimento nel registro d’istruzione. Quindi si decodifica l’istruzione che eventualmente può richiedere il trasferimento di alcuni operandi dalla memoria in alcuni registri interni. Una volta terminata l’esecuzione dell’istruzione sugli operandi, il risultato si può scrivere nella memoria.
In teoria, si vorrebbe che sia i programmi sia i dati da essi trattati potessero risiedere in modo permanente nella memoria centrale. Questo non è possibile per i seguenti due motivi:
- La capacità della memoria centrale non è di solito sufficiente a contenere in modo permanente tutti i programmi e i dati richiesti.
- La memoria centrale è un dispositivo di memorizzazione volatile: perde il proprio contenuto quando si spegne il sistema o si ha un’interruzione dell’alimentazione elettrica.
Per queste ragioni la maggior parte dei sistemi di calcolo comprende una memoria secondaria come estensione della memoria centrale. La caratteristica fondamentale di questi dispositivi è la capacità di conservare in modo permanente grandi quantità di informazioni. Il dispositivo più comunemente impiegato a questo scopo è l’unità a disco magnetico. Una corretta gestione delle unità a disco è di fondamentale importanza per un sistema di calcolo.
Le caratteristiche che differenziano i diversi sistemi di memorizzazione sono velocità, costo, dimensioni e volatilità. L’ampio ventaglio dei sistemi di memorizzazione disponibili in un elaboratore può essere ordinato secondo una scala gerarchica, sulla base della velocità e del costo. I gradini più alti ospitano i dispositivi più veloci, ma anche più dispendiosi. Andando verso il basso il costo per bit generalmente diminuisce, mentre il tempo di accesso tende ad aumentare. Oltre a caratterizzarsi per velocità e costo, i sistemi di memorizzazione si suddividono in volatili e non volatili.
Nel progettare un sistema di memorizzazione completo si deve attribuire la giusta rilevanza a ciascun fattore: l’uso di memoria costosa va limitato al necessario; in compenso, è bene prevedere la massima quantità possibile di memoria non volatile ed economica. L’installazione di memorie cache sopperisce a eventuali macroscopiche disparità nei tempi di accesso o nella velocità di trasferimento tra due componenti, consentendo miglioramenti nelle prestazioni.
Struttura di I/O
Una percentuale cospicua del codice di un sistema operativo è dedicata alla gestione dell’I/O.
Un calcolatore d’uso generale è composto da una CPU e da un insieme di controllori di dispositivi connessi mediante un bus comune. Ciascun controllore deve occuparsi di un particolare tipo di dispositivo e, secondo la sua natura, può gestire uno o più dispositivi a esso connessi. Un controllore SCSI (small computer-systems interface), per esempio, è capace di controllare sette o più dispositivi.
Un controllore di dispositivo dispone di una propria memoria interna, detta memoria di transito (buffer), e di un insieme di registri specializzati. Il controllore è responsabile del trasferimento dei dati tra i dispositivi periferici a esso connessi e la propria memoria di transito. I sistemi operativi in genere possiedono, per ogni controllore del dispositivo, un driver del dispositivo che si coordina con il controllore e funge da interfaccia uniforme con il resto del sistema.
Per avviare un’operazione di I/O, il driver del dispositivo carica i registri interessati all'interno del controllore, il quale, dal canto suo, esamina i contenuti di questi registri per scegliere l’azione da intraprendere (per esempio “leggi un carattere dalla tastiera”). Il controllore comincia a trasferire i dati dal dispositivo al proprio buffer locale. A trasferimento completato, il controllore informa il driver, tramite un’interruzione, di avere terminato l’operazione. Il driver passa quindi il controllo al sistema operativo, restituendo i dati (o un puntatore a essi) se l’operazione è di lettura; per altre operazioni, il driver restituisce delle informazioni di stato.
Questa forma di I/O guidato dalle interruzioni è adatta al trasferimento di piccole quantità di dati, ma in caso di trasferimenti massicci può generare un pesante sovraccarico. Per fare fronte a questo problema si utilizza la tecnica dell’accesso diretto alla memoria (DMA). Una volta impostati i buffer, i puntatori e i contatori necessari al dispositivo di I/O, il controllore trasferisce un intero blocco di dati dalla propria memoria buffer direttamente nella memoria centrale, o viceversa, senza alcun intervento della CPU.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.