Sistemi operativi/Funzioni base: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
The Doc (discussione | contributi)
m wikificare
Nessun oggetto della modifica
Riga 1:
{{w}}
 
==* [[Facoltà di Informatica:Sistemi operativi/Funzionalità dei sistemi operativi|Funzionalità ==dei sistemi operativi]]
* [[Facoltà di Informatica:Sistemi operativi/Evoluzione dei sistemi operativi|Evoluzione dei sistemi operativi]]
== Gestione del processore ==
 
== Gestione del processore ==
Il processore, o unità centrale di elaborazione (o CPU, dall'inglese Central Processing Unit), è una risorsa hardware la cui gestione presenta due problemi.
* In ogni momento, più task possono essere pronti per l'esecuzione, ma solo uno può essere eseguito da un processore; pertanto, occorre decidere quale task eseguire, facendo attendere gli altri.
Line 43 ⟶ 44:
== Gestione degli utenti ==
== Comunicazione tra elaboratori ==
== Storia dei sistemi operativi ==
I primi elaboratori non avevano bisogno di un sistema operativo, in quanto venivano usati da un programma per volta.
All'inizio di ogni elaborazione, si caricavano i programmi e i dati da un pacco di schede perforate, o da un nastro perforato, o da un nastro magnetico, si eseguiva l'elaborazione, e si emettevano i risultati su stampante o su nastro magnetico.
Per passare all'elaborazione successiva, si sostituivano i pacchi di schede e i nastri.
I programmi applicativi avevano accesso diretto a tutto l'hardware, tipicamente tramite librerie di routine di input/output.
 
=== Il caricatore ===
Ci si rese rapidamente conto che tale procedimento era inefficiente, perché mentre l'operatore sostituiva i nastri e le schede, l'elaboratore era inutilizzato. Inoltre, per sfruttare l'elaboratore, occorreva la presenza continua di almeno un operatore.
Pertanto, si passò in seguito al seguente procedimento.
 
* I pacchi di schede e i nastri perforati relativi a più elaborazioni venivano caricati in sequenza su uno stesso nastro magnetico, usando un dispositivo digitale dedicato, che non impegnasse l'elaboratore.
* I programmi applicativi erano progettati in modo da leggere i dati escusivamente da un nastro magnetico e scrivere i risultati esclusivamente su un altro nastro magnetico.
* L'elaboratore aveva a disposizione più unità a nastro magnetico, almeno due per la scrittura e due per la lettura. In tal modo, mentre l'operatore sostituiva un nastro, l'elaboratore ne utilizzava un altro.
* Un dispositivo digitale dedicato permetteva di stampare il contenuto di un nastro, senza impegnare l'elaboratore.
Con tale procedimento, fin tanto che i nastri di ingresso contenevano dati da elaborare e i dati di uscita avevano spazio per ricevere risultati, l'elaboratore non aveva mai tempi morti.
 
Tuttavia, per ottenere questo risultato, è stato necessario sviluppare il primo rudimentale sistema operativo.
Il compito di tale software, caricato in memoria all'accensione dell'elaboratore, era eseguire la seguente sequenza di operazioni:
# Caricare in memoria principale il prossimo programma contenuto nell'unità nastro di ingresso.
# Avviare l'esecuzione del programma caricato.
# Quando il programma ha finito l'esecuzione, tornare al punto 1.
Questo software di base era un semplice caricatore ("loader"), ma, pur occupando un po' di memoria principale, consentiva una notevole riduzione dei tempi morti, a condizione che i programmi fossero corretti.
Si tenga presente che stiamo parlando di elaboratori con 16 o 32 KB (non GB né MB) di memoria principale, ma del costo orario pari a quello di decine di impiegati.
 
=== Il monitor residente ===
Un problema emerso con l'uso del caricatore era la presenza di errori nel software applicativo.
Quando l'elaboratore era completamente gestito da un programma per volta, qualunque cosa facesse tale programma, non danneggiava gli altri programmi. Comunque, se un programma non aveva finito la sua esecuzione dopo il tempo previsto, l'operatore lo arrestava e passava al programma successivo.
 
Usando il caricatore automatico, un programma poteva danneggiare gli altri programmi in molti modi, tra cui:
* Continuare a funzionare indefinitamente, o comunque per un tempo superiore a quello economicamente conveniente.
* Non leggere tutti i dati spettanti contenuti nei nastri di ingresso.
* Leggere dati o programmi non spettanti, contenuti nei nastri di ingresso.
* Scrivere sui nastri di uscita così tanti dati da riempirli, o comunque una quantità di dati superiore a quella economicamente conveniente.
* Sovrascrivere la parte di memoria principale in cui risiede il caricatore o i suoi dati.
 
Per rimediare a tali problemi, si adottarono le seguenti soluzioni:
* L'aggiunta di schede di controllo tra le sequenze di schede contenenti programmi e dati.
* La protezione hardware delle risorse (processore, memoria principale, unità a nastri).
* L'utilizzo di un sistema operativo più sofisticato, chiamato "monitor".
 
==== Le schede di controllo ====
Siccome i primi elaboratori leggevano i dati direttamente dalle schede perforate, e siccome anche successivamente la preparazione iniziale dei dati avveniva perforando schede che poi venivano riversate su nastri, anche il contenuto dei nastri di ingresso era organizzato come una sequenza di record, ognuno corrispondente a una scheda.
Per analogia, tali record venivano talvolta chiamati "schede".
 
Il nastro di ingresso conteneva una sequenza di elaborazioni (dette anche "job"), ognuna strutturata nel seguente modo:
* Record di inizio programma.
* Numerosi record, ognuno contenente un'istruzione in linguaggio macchina.
* Record di inizio dati di ingresso.
* Numerosi record, ognuno contenente un blocco di dati.
 
I record di inizio programma e di inizio dati avevano un segno che li distingueva dagli altri, pertanto era possibile sapere con certezza quando finiva il programma e quando finivano i dati.
 
In seguito, tali schede di controllo evolsero aggiungendo sempre più informazioni e diventarono veri linguaggi di comandi.
 
Per esempio, inizialmente i programmi erano scritti solamente in linguaggio macchina, ma rapidamente si passò al linguaggio assemblativo, al FORTRAN e al COBOL, e poi ad altri linguaggi di programmazione.
Pertanto, la scheda di inizio programma doveva indicare anche in quale linguaggio era scritto il programma stesso.
Leggendo la scheda si sapeva quale compilatore doveva essere utilizzato.
 
Altre informazioni contenute nelle schede di controllo erano i requisiti del programma, cioè la quantità di memoria necessaria, la durata massima dell'elaborazione, e il numero massimo di record che verranno emessi come risultato.
Avendo a disposizione tali informazioni, il sistema operativo poteva ottimizzare l'allocazione delle risorse e terminare le elaborazioni che superassero i requisiti dichiarati.
 
==== La protezione hardware ====
Con l'uso di un sistema operativo, l'elaboratore è utilizzato da almeno due utenti simultanei: l'operatore di sistema, che ''utilizza'' il sistema operativo per far funzionare in modo efficiente l'elaboratore; e l'utente che ha consegnato all'operatore il programma applicativo e i dati, che utilizza il programma applicativo per elaborare i propri dati.
Nei sistemi multiprogrammati ci possono essere altri utenti simultanei.
 
Se un programma contiene degli errori o comunque degli algoritmi che usano le risorse in modo non economicamente conveniente (per esempio, impiegano dei minuti per eseguire un calcolo che potrebbe essere eseguito in pochi secondi), danneggia gli altri utenti.
Siccome i programmi applicativi spesso contengono difetti, un sistema di elaborazione dati multiutente deve impedire ai programmi applicativi tali comportamenti devianti e dannosi.
 
Il problema non si presenta per i difetti che può avere il sistema operativo stesso, in quanto, se il sistema operativo genera un malfunzionamento, comunque il funzionamento del software applicativo è compromesso.
 
Quindi l'obiettivo è fare in modo che qualunque comportamento possa avere un programma applicativo, questo non possa danneggiare l'esecuzione del sistema operativo e di altri programmi.
Questo era semplicementi impossibile nei primi elaboratori negli anni '50 e nei primi microprocessori (quelli a 8 bit).
 
Per renderlo possibile sono state apportate modifiche all'hardware dell'elaboratore.
Tali accorgimenti tecnici prendono il nome di "protezione hardware".
 
La protezione hardware è quindi l'insieme dei dispositivi elettronici che permettono al sistema operativo di assicurare che un programma applicativo non possa eseguire operazioni che danneggino l'esecuzione di altri programmi.
 
La protezione hardware si basa essenzialmente su tre concetti: il registro base, il timer, e la modalità del processore.
 
Il registro base contiene l'indirizzo di memoria a partire dal quale viene caricato il programma applicativo.
Le posizioni di memoria che precedono tale indirizzo sono quelle che contengono codice e dati del sistema operativo.
 
Tutte le volte che il processore emette un indirizzo sul bus indirizzi, per leggere un dato, per scrivere un dato, o per caricare un'istruzione, un circuito verifica che tale indirizzo non sia inferiore al registro base.
In caso negativo, cioè se il programma applicativo tenta di accedere a una posizione di memoria riservata al sistema operativo, viene generato un interrupt e il controllo torna al sistema operativo, che arresta il programma e passa al programma successivo.
 
Il timer è un dispositivo che viene impostato da un'apposita istruzione ad un certo intervallo di tempo, e quando tale tempo scade, invia un interrupt che passa il controllo al sistema operativo.
Prima di lanciare un programma applicativo, il sistema operativo imposta il timer al tempo massimo previsto per l'esecuzione.
Se il programma applicativo non ha ancora terminato l'esecuzione dopo tale tempo, il timer passa il controllo al sistema operativo che arresta il programma e passa al programma successivo.
 
Il processore ha (almeno) due modalità di funzionamento:
* La modalità sistema (detta anche modalità protetta, o modalità supervisore, o modalità kernel).
* La modalità utente.
 
La modalità sistema è quella priva di qualunque limitazione.
Il processore è in modalità sistema solo quando è in esecuzione il codice del sistema operativo.
 
La modalità utente è quella sottoposta a varie limitazioni.
Il processore è in modalità utente quando è in esecuzione il codice applicativo, o anche il codice del sistema operativo che non richiede i privilegi propri della modalità sistema.
 
Le istruzioni di alcuni tipi, dette istruzioni privilegiate, possono essere eseguite solo in modalità sistema.
Esse sono quelle che svolgono i seguenti compiti:
* Abilitazione e disabilitazione degli interrupt.
* Ingresso/uscita (input/output).
* Cambio di modalità del processore.
* Impostazione del registro base.
* Impostazione del timer.
 
Il fatto che le istruzioni di ingresso/uscita siano privilegiate costringe i programmi applicativi a invocare il sistema operativo per effettuare qualunque operazione di ingresso/uscita.
Questo genera un continuo rimpallo del controllo tra il codice applicativo e il codice di sistema.
 
Quando il sistema operativo lancia l'applicazione, pone il processore in modalità utente, e quando il controllo torna al sistema operativo a causa della termina dell'applicazione il processore torna in modalità sistema.
 
Ma come fa l'applicazione a chiedere al sistema operativo di eseguire un'operatione di ingresso/uscita, dato che non può saltare direttamente nel codice del sistema operativo?
 
Per risolvere questo problema è stata introdotta una nuova istruzione, la "chiamata di sistema" (in inglese "system call" o "supervisor call").
Eseguendo tale istruzione il processore passa automaticamente in modalità sistema.
 
==== Il flusso di controllo del sistema operativo ====
 
Si tenga presente che questa non è la descrizione di uno specifico sistema operativo, bensì una descrizione semplificata della modalità di funzionamento dei primi sistemi operativi esistenti negli anni '50.
 
Tramite l'uso delle schede di controllo e dai dispositivi di protezione hardware, il sistema operativo può avere la seguente operatività:
# Carica dal nastro magnetico di ingresso il primo record, verificando che si tratti di un record di inizio programma.
# Carica eventuali altri record che completino la descrizione del programma, verificandone la correttezza formale. Tra le informazioni lette, ci sono il tempo massimo previsto per l'esecuzione del programma, il numero di byte di memoria richiesti dal programma e dai suoi dati, e il numero massimo di record che potranno essere emessi.
# Carica i record che contengono il programma, fino a quello che segnala la fine del programma e l'inizio dei dati.
# Imposta il registro base all'indirizzo a partire dal quale è stato caricato il programma.
# Imposta il timer al tempo previsto per l'elaborazione.
# Imposta il processore in modalità utente.
# Salta alla prima istruzione del programma applicativo.
# Il programma applicativo esegue le sue istruzioni. Qualunque tentativo di eseguire istruzioni privilegiate o di accedere allo spazio di memoria riservato al sistema operativo provoca un interrupt. Anche il fatto che scatti il timer provoca un interrupt. L'interrupt provoca il passaggio del controllo alla routine del sistema operativo di gestione dell'interrupt, e l'impostazione del processore in modalità sistema. Tale routine emette in output un record di segnalazione dell'errore, provvede a leggere tutti i record di dati rimanenti fino al prossimo record di inizio programma e torna al punto 1.
# Quando il programma applicativo vuole leggere uno o più record dal nastro magnetico di ingresso o scrivere uno o più record sul nastro magnetico di uscita, esegue un'istruzione di chiamata di sistema, impostando in alcuni registri il tipo di operazione (lettura o scrittura), il numero della periferica, il numero di record da elaborare, e l'indirizzo di memoria da utilizzare per il trasferimento. Tale istruzione imposta automaticamente il processore in modalità sistema. Il sistema operativo effettua la lettura o la scrittura e poi memorizza in un registro l'esito dell'operazione, reimposta il processore in modalità utente, e salta all'istruzione del programma successiva a quella di chiamata di sitema.
# Quando il programma ha terminato il proprio compito, torna il controllo al sistema operativo, che emette sul nastro un record di fine elaborazione, e torna al punto 1.
 
=== L'uso delle memorie secondarie ad accesso diretto ===
 
La memoria principale dei primi elaboratori era molto piccola, in quanto di elevatissimo costo unitario, era possibile contenere in memoria, oltre al sistema operativo, solo piccoli programmi, che elaborassero pochi dati per volta.
 
Tuttavia, alcuni problemi di calcolo richiedono che il programma o i dati abbiano dimensioni significative.
Per poter eseguire questo tipo di elaborazioni su sistemi con poca memoria, si è pensato di caricare e scaricare dalla memoria i dati e le porzioni di programma di volta in volta richiesti.
 
Siccome tali caricamenti e scaricamenti non seguono un ordine lineare, l'uso dei nastri magnetici, che sono ad accesso sequenziale, si è dimostrato inadeguato, e sono state inventate memoria secondarie magnetiche ad accesso diretto.
Tali memorie erano i "tamburi" magnetici, oggi non più utilizzati, e i "dischi" magnetici, tuttora ampiamente utilizzati.
I tali dispositivi avevano costi unitari (cioè per bit), intermedi tra quelli della memoria principale e quelli delle unità nastro.
Pertanto avevano tipicamente anche capacità intermedie, dell'ordine di alcune centinaia di chilobyte, o di pochi megabyte.
 
Inizialmente, il tamburo magnetico era a completa disposizione del programma applicativo.
 
Quando il programma veniva caricato dal sistema operativo, in realtà lo copiava sul tamburo, e poi da lì caricava in memoria i pezzi di programma che servivano di volta in volta.
Ogni pezzo era detto "overlay" (inglese per "copertura"), perché si sovrapponeva ad una precedente porzione di codice.
Un overlay era costituito da una o più routine.
 
Quando da una routine si chiamava una routine appartenente a un altro overlay, il controllo passava al sistema operativo che caricava dal tamburo l'overlay contenente la routine chiamata, sostituendo in memoria principale un altro overlay.
 
Ovviamente il programmatore doveva organizzare il programma in modo da evitare eccessivi cambi di overlay, perché tali operazioni rallentavano l'elaborazione.
 
Il tamburo era a disposizione anche dell'applicazione per salvare dati temporanei.
 
Comunque, quando si passava a un altro programma, il dati presenti sul tamburo venivano persi.
 
Con il crescere delle dimensioni delle memorie secondarie, si pensò di conservarvi dei dati persistenti, cioè che fossero riutilizzabili anche dopo l'esecuzione di un programma.
 
Un tipico utilizzo dei dati persistenti su dispositivi ad accesso diretto era la ricerca di informazioni.
Per esempio, se un archivio su nastro contiene tutti i saldi dei conti correnti di una banca in ordine di numero di conto, e un altro archivio contiene tutti i movimenti della giornata in ordine temporale, e si vuole aggiornare i saldi dei conti correnti, si può procedere nel seguente modo:
* Si scandiscono i saldi dei conti correnti. Per ogni conto corrente si cercano sull'archivio dei movimenti tutti i movimenti che lo riguardano e si aggiorna il saldo.
Oppure nel seguente modo:
* Si scandiscono i movimenti dei conti correnti. Per ogni movimento, si cerca nell'archivio dei saldi il record relativo e lo si aggiorna.
In entrambi i casi, supponendo che sia i record dei movimenti che i record dei saldi siano così tanti da non essere contenuti in memoria, si devono effettuare un gran numero di ricerche. E usando un nastro magnetico, tali ricerche hanno un costo proibitivo.
 
Invece, si può caricare su un disco magnetico l'elenco dei saldi, lasciare i movimenti su nastro, e usare il secondo dei due algoritmi citati sopra.
La ricerca su disco magnetico di un record contenente un saldo può essere effettuata cercando con un metodo dicotomico, eventualmente agevolato da un piccolo indice mantenuto in memoria principale.
 
L'uso di memorie magnetiche persistenti ad accesso diretto, che d'ora in poi chiameremo semplicemente "dischi", faceva però sorgere il seguente problema.
 
Se l'elaboratore è usato in sequenza da numerosi programmi, e non ci sono tanti dischi quanti sono i programmi, si deve condividere tra più programmi la stessa unità disco con i suoi dati.
Se i programmi devono anche condividere i dati basta che i programmatori si mettano d'accordo, ma se ogni programma ha i suoi dati, non è ragionevole che ogni programmatore debba conoscere quali parti del disco sono o saranno utilizzate da altri programmi, con il rischio che un errore di programmazione possa compromettere il funzionamento di altri programmi.
 
La gestione del disco condiviso è chiaramente un compito del sistema operativo.
 
Siccome gli archivi su disco si chiamano anche "file", il sottosistema del sistema operativo che si occupa della gestione dell'organizzazione degli archivi su disco viene chiamato "file system".