Sistemi operativi/Evoluzione dei sistemi operativi
La preistoria dei sistemi operativi
modificaI primi elaboratori elettronici (in seguito chiamati "computer") non avevano bisogno di alcun sistema operativo, in quanto venivano usati da un programma per volta, come descritto in seguito.
Per comprendere come venivano usati i primi computer, occorre descrivere come erano strutturati. Essenzialmente, i computer del periodo tra il 1945 e il 1955 avevano quattro componenti:
- Il processore (processor), costituito dapprima da valvole termoioniche, poi sostituite da transistor.
- La memoria centrale (core memory), costituita da alcune decine di migliaia di anellini di ferrite magnetizzabili.
- Un dispositivo di ingresso (input device), costituito dapprima da un lettore di nastro di carta perforato, poi sostituito da un lettore di schede di cartoncino perforate.
- Un dispositivo di uscita (output device), costituito da una stampante alfanumerica.
I primi computer non avevano memorie di massa. Inoltre, il pannello del processore aveva numerose lucette e interruttori, utili per la riparazione dei frequenti guasti.
Per semplicità, d'ora in poi supporremo che il dispositivo di input fosse solamente un lettore di schede perforate, in quanto in effetti i nastri perforati sono stati rapidamente sostituiti da queste ultime, che avevano il vantaggio di consentire la modifica parziale della sequenza di input, aggiungendo o togliendo schede da un pacco di schede.
I computer, essendo molto costosi, venivano usati non da singoli individui, ma da numerose persone, solitamente matematici o ingegneri, che si contendevano l'accesso a tale strumento di calcolo per risolvere problemi scientifici o tecnici.
Ogni utente preparava il programma in linguaggio macchina e i dati da sottoporre a tale programma, scrivendolo a matita su un foglio di carta.
Poi trascriveva tale programma e tali dati in formato leggibile dal computer perforando schede tramite una perforatrice meccanica, che era un oggetto simile a una macchina per scrivere.
Poi prenotava un intervallo di tempo (di pochi minuti o di alcune ore) nel quale il computer era a sua completa disposizione. La gestione di un computer era quindi simile a quella di un campo da tennis di un centro sportivo.
Quando arrivava il proprio turno, l'utente inseriva il pacco di schede perforate nell'apposito lettore, e avviava il computer. Il computer iniziava la sua attività caricando in memoria dal lettore di schede il programma, e poi eseguiva il programma caricato. Tale programma solitamente caricava dallo stesso lettore di schede i dati che seguivano il programma, ed eseguiva le istruzioni di calcolo contenute nel programma, che solitamente terminavano con l'invio di numerosi caratteri alla stampante.
Al termine dell'elaborazione, l'utente si riprendeva le sue schede, raccoglieva i fogli stampati, e lasciava il computer a disposizione di un altro utente.
A questo punto, solitamente l'utente tornava nel suo ufficio a studiarsi i fogli stampati, ed eventualmente usava tali informazioni per modificare il programma o i suoi dati, o per scrivere un nuovo programma, mentre il computer veniva usato da altri utenti.
I programmi avevano libero accesso diretto a tutto l'hardware, tipicamente tramite librerie di routine di input/output. Si usavano routine di input per caricare i dati dal lettore di schede, e routine di output per inviare caratteri alla stampante.
Questo modo di procedere aveva molti inconvenienti:
- L'utente poteva non essere pratico di come si controlla il computer, e dare comandi sbagliati che non fornivano il risultato desiderato.
- L'utente poteva eseguire lentamente le operazioni di carico delle schede o del nastro e di prelievo della carta dalla stampante, sprecando il prezioso tempo del computer.
- L'utente poteva aver prenotato un tempo insufficiente ad eseguire il programma, e quindi dover terminare il programma prima che avesse finito, o sforare nell'intervallo prenotato da un altro utente.
- L'utente poteva aver prenotato un tempo più lungo del necessario ad eseguire il programma, e quindi andarsene lasciando il computer inutilizzato prima che scadesse l'intervallo di tempo prenotato.
- Per sfruttare il computer in ogni ora del giorno e in ogni giorno della settimana, gli utenti erano costretti a fare turni notturni o festivi.
Pertanto, una prima evoluzione organizzativa è stata affidare l'esecuzione dei programmi ad un tecnico specializzato, chiamato "operatore". Ogni utente consegnava quindi all'operatore il proprio pacco di schede perforate, dichiarando, con una certa abbondanza, quanto tempo massimo avrebbe dovuto impiegare il programma, e poi se ne andava via. L'operatore quindi aveva sempre a disposizione alcuni pacchi di schede perforate pronti da essere caricati ed eseguiti.
Appena il computer segnalava il completamento di un programma, l'operatore inseriva nel lettore il nuovo programma in lista d'attesa, riavviava il computer, e andava a prendere i fogli stampati dal programma appena concluso.
Se un programma non era ancora terminato dopo che era trascorso il tempo dichiarato come massimo, l'operatore bloccava forzosamente il computer, e passava al programma successivo. Se viceversa un programma terminava prima del previsto, l'operatore caricava subito il programma successivo, senza lasciare lunghi tempi morti.
Dopo un congruo intervallo di tempo, tipicamente il giorno lavorativo successivo, l'utente andava dall'operatore a farsi restituire le schede perforate, e a farsi dare i fogli stampati dal programma.
Solo gli operatori facevano turni notturni e festivi per sfruttare al massimo il computer. Gli utenti avevano un normale orario d'ufficio.
Ma anche con l'impiego di operatori specializzati, non si utilizzava alcun sistema operativo.
Il caricatore
modificaCi si rese rapidamente conto che tale procedimento era comunque inefficiente, per i seguenti motivi:
- Mentre l'operatore sostituiva i pacchi di schede, il computer era inutilizzato.
- Se l'operatore si distraeva e non caricava subito un programma quando era terminato il precedente, il computer era inutilizzato fino a che l'operatore provvedeva a caricare il programma successivo.
- Il lettore di schede e la stampante, per quanto progettati e costruiti per avere un'altissima velocità meccanica, erano molto più lenti dei componenti elettronici, e quindi mentre si leggevano le schede o si stampavano i caratteri, il processore svolgeva molto meno lavoro di quello che avrebbe potuto svolgere con dispositivi di ingresso o di uscita più veloci. In altre parole, per moli tipi di elaborazioni, la velocità dei dispositivi di ingresso/uscita costituiva il fattore limitante delle prestazioni del sistema.
Inoltre, per sfruttare al massimo il computer, era necessaria la vigilanza continua di almeno un operatore.
Per rimediare a questi inconvenienti, è stato inventato un nuovo tipo di periferica di memorizzazione, il registratore digitale su nastri magnetici ad alta velocità. Tale dispositivo era un registratore a bobine simile ai registratori audio, ma si differenziava da questi ultimi in primo luogo per il fatto che la registrazione era digitale invece che analogica, ma soprattutto per il fatto che le bobine giravano ad alta velocità. Inoltre, un apposito meccanismo faceva scorrere il nastro per brevi tratti davanti alla testina ad una velocità superiore alla velocità di rotazione della bobina, con un movimento a impulsi.
Pertanto, utilizzando tali registratori su nastro magnetico, si passò in seguito alla seguente architettura di elaborazione:
- L'operatore aveva a disposizione un apposito dispositivo digitale, che non impegnava il computer centrale, in grado di leggere il contenuto di un pacco di schede, e di scriverlo su un nastro magnetico, accodandolo al contenuto esistente.
- L'operatore aveva inoltre a disposizione anche un altro apposito dispositivo digitale, che non impegnava il computer centrale, in grado di leggere il contenuto di un nastro magnetico e di inviarlo a una stampante alfanumerica.
- L'elaboratore aveva a disposizione più unità a nastro magnetico ad alta velocità, almeno due per la scrittura e due per la lettura.
- Le routine di input/output usate dai programmi sono state riscritte in modo che le istruzioni di input leggessero i dati esclusivamente da un nastro magnetico, e le istruzioni di output scrivessero i risultati esclusivamente su un altro nastro magnetico. In tal modo, senza dover modificare il codice applicativo, i programmi che usavano le nuove routine di input/output comunicavano solamente con i veloci nastri magnetici invece che con i lenti dispositivi cartacei.
Questa architettura, consentiva il seguente procedimento di utilizzo del computer:
- Uno o più programmi e i relativi dati vengono caricati dalle schede perforate a un nastro magnetico.
- Un programma per volta viene caricato da un nastro magnetico alla memoria del computer.
- Il programma in esecuzione legge i dati da un nastro magnetico e scrive i risultati su un altro nastro magnetico.
- I risultati di uno o più programmi vengono inviati da un nastro magnetico alla stampante.
Ovviamente, ogni volta che un nastro magnetico è stato letto oppure scritto, viene riavvolto rapidamente. Altrettanto ovviamente, per ottimizzare i tempi, in ogni istante vengono eseguite almeno tre operazioni contemporanee:
- Uno o più caricatori di schede leggono altrettanti pacchi di schede, caricandone il contenuto su altrettanti nastri magnetici.
- Uno o più scaricatori di schede leggono altrettanti nastri magnetici, inviandone il contenuto ad altrettanti stampanti.
- Il computer carica un programma da un nastro magnetico, oppure carica i dati dallo stesso nastro magnetico, oppure esegue calcoli in memoria, oppure scrive righe da stampare su un altro nastro magnetico.
Con tale procedimento, fin tanto che i nastri di ingresso contenevano dati da elaborare e i nastri di uscita avevano spazio per ricevere risultati, l'elaboratore non aveva mai tempi morti, e, pur di avere un numero sufficiente di lettori di schede, di stampanti, e di registratori magnetici, i fattori limitanti della velocità di elaborazione erano diventati la potenza del computer e la velocità del nastro magnetico, e non più quella dei supporti cartacei.
Tuttavia, per ottenere questo risultato, è stato necessario sviluppare il primo rudimentale sistema operativo. Il compito di tale software, caricato in memoria e mandato in esecuzione 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, cioè rispettassero le regole di utilizzo del computer. Si tenga presente che stiamo parlando di elaboratori con 4 o 8 KB (non GB né MB) di memoria principale, mentre il caricatore occupava meno di 1 KB.
Il monitor residente
modificaUn 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, i principali dei quali erano:
- Continuare a funzionare indefinitamente, o comunque per un tempo superiore a quello economicamente conveniente.
- Non leggere tutti i dati spettanti, contenuti nel nastro di ingresso.
- Continuare a leggere il nastro di ingresso dopo che sono finiti i dati spettanti al programma.
- Scrivere sul nastro di uscita così tanti dati da riempirlo, o comunque scrivere una quantità di dati superiore a quella economicamente conveniente.
- Sovrascrivere la parte di memoria principale in cui risiede il caricatore o i suoi dati, e quindi impedirne il funzionamento al termine del programma applicativo.
Per rimediare a tali problemi, erano necessarie le seguenti politiche.
Per evitare il problema che un programma funzionasse per troppo tempo, ogni programma doveva comunicare al sistema operativo il tempo massimo che avrebbe impiegato l'elaborazione; il sistema operativo attivava un timer pari a tale intervallo di tempo, e lanciava l'esecuzione. Se l'esecuzione terminava prima dello scadere dell'intervallo di tempo, il timer veniva fermato. Se invece il timer raggiungeva la fine dell'intervallo di tempo, il controllo veniva immediatamente passato al sistema operativo, che terminava forzosamente il programma, e stampava una pagina che descriveva il motivo della terminazione.
Per evitare i problemi che un programma non leggesse tutti i dati spettanti o che continuasse a leggere anche dopo che erano finiti i dati spettanti, il nastro conteneva al termine di ogni programma e al termine di ogni sequenza di dati di ingresso una scheda diversa da ogni possibile scheda che avrebbe potuto scrivere il programmatore. Tale scheda era detta "scheda di controllo". Se il sistema operativo stava caricando un programma, leggeva comunque fino alla scheda di controllo che segnalava la fine del programma, e non oltre; mentre se stava caricando i dati, leggeva comunque fino alla scheda di controllo che segnalava la fine dei dati, e non oltre.
Per evitare il problema che un programma scrivesse troppe pagine sul nastro di uscita, ogni programma doveva comunicare al sistema operativo il massimo numero di pagine che avrebbe stampato durante l'elaborazione. Il sistema operativo contava le pagine emesse sul nastro di uscita, e se veniva superato il limite dichiarato, il programma veniva fermato.
Infine, per evitare il problema che un programma sovrascrivesse la parte di memoria principale in cui risiedono le istruzioni o i dati del sistema operativo, fu necessario aggiungere all'elettronica del processore un circuito di controllo degli indirizzi. Tale circuito conteneva un registro chiamato "registro base", e una logica che ogni volta che il processore emetteva un indirizzo sul bus di memoria per leggere o scrivere dei dati o delle istruzioni, verificava se tale indirizzo di memoria era maggiore o minore del registro base, e, nel caso fosse stato minore, il controllo veniva immediatamente passato al sistema operativo.
In tal modo, bastava caricare il sistema operativo nella parte di memoria avente indirizzi bassi, e impostare il registro base all'indirizzo in cui finiva la memoria usata dal sistema operativo. In tal modo, ogni tentativo di accedere alla memoria del sistema operativo da parte dei programmi applicativi veniva intercettato da tale circuito prima di effettuare l'accesso alla memoria, e provocava l'immediata terminazione del programma.
Chiaramente, queste politiche richiesero i seguenti meccanismi:
- La definizione di una pseudo-scheda, diversa da tutte le possibili schede perforate, inserita dai caricatori su nastro dei pacchi di schede all'inizio o alla fine di ogni programma e di ogni sequenza di dati.
- La definizione di un formato delle schede di ingresso con cui poter dichiarare il tempo massimo e il massimo numero di pagine richiesto dall'elaborazione.
- L'aggiunta di un circuito per la generazione e gestione delle interruzioni (interrupt) del programma in esecuzione.
- L'aggiunta di un timer hardware programmabile, in grado di generare interrupt.
- L'aggiunta di un registro base e di un circuito in grado di generare interrupt in presenza di un indirizzo inferiore al valore di tale registro.
Ma anche questo non bastava, in quanto erano ancora possibili i seguenti inconvenienti:
- Un programma poteva disattivare il timer e proseguire indefinitamente.
- Un programma poteva riprogrammare il registro base e poi sovrascrivere la memoria assegnata al sistema operativo.
- Un programma poteva accedere direttamente ai nastri leggendo o scrivendo dati non spettanti, scavalcando il protocollo convenzionale di accesso ai nastri.
Per evitare tali inconvenienti è stato necessario introdurre il concetto di "protezione hardware".
La protezione hardware
modificaPer impedire ai programmi applicativi di danneggiare gli altri programmi, deliberatamente o accidentalmente, direttamente o indirettamente danneggiando il sistema operativo, sono state introdotte modifiche all'hardware dei computer, che vanno sotto il nome collettivo di "protezione hardware".
Tali modifiche, per i primi computer, erano:
- Le schede di controllo.
- Il registro base e il circuito di verifica degli indirizzi.
- Il timer.
- La modalità di funzionamento, che può essere "supervisore" o "utente".
- Le istruzioni privilegiate.
Le prime tre modifiche sono già state viste nella sezione precedente. La quarta modifica consiste nel fatto che il processore aveva due modalità di funzionamento:
- Modalità utente (o "user mode").
- Modalità supervisore (o "supervisor mode"). In seguito, questa modalità è stata chiamata anche "system mode" o "kernel mode".
Quando il computer esegue il codice del sistema operativo, è in modalità supervisore. In tale modalità, non ha alcuna limitazione, cioè può eseguire tutte le operazioni fisicamente consentite dal computer.
Invece, quando il computer esegue il codice di un programma applicativo, è in modalità utente. In tale modalità, le seguenti operazioni provocano un interrupt e il passaggio del controllo al sistema operativo, in modalità supervisore:
- L'esecuzione di istruzioni privilegiate.
- L'accesso alla memoria a un indirizzo inferiore a quello memorizzato nel registro base.
Le istruzioni privilegiate, consentite solamente in modalità supervisore, sono le seguenti:
- Impostazione del timer.
- Impostazione del registro base.
- Istruzioni di ingresso/uscita.
- Cambio di modalità del processore.
Vediamo quindi il ciclo di vita dell'esecuzione di un programma.
Ciclo di vita di un processo
modificaCome abbiamo visto prima, il sistema operativo che siamo arrivati a esaminare non fa altro che lanciare un programma per volta, verificando che tale programma si comporti correttamente.
L'esecuzione di un programma veniva inizialmente chiamata "job" (che significa "lavoro"), e una sequenza di "job" veniva chiamata "batch", che significa "lotto", "gruppo", "infornata".
In seguito, la parola "job" è stata sostituita dalla parola "processo" (process), e d'ora in poi useremo questa parola.
Per lanciare un processo, il computer parte in modalità supervisore, carica in memoria il programma dal nastro, passa in modalità utente, e passa il controllo al programma appena caricato. Il programma esegue le sue istruzioni in modalità utente, fino a quando succedono uno dei seguenti eventi:
- Il programma tenta di eseguire un'istruzione privilegiata, o di accedere alla memoria del sistema operativo.
- Il programma termina.
- Il programma richiede al sistema operativo di eseguire un'operazione di ingresso/uscita.
In tutti e tre i casi, il controllo torna al sistema operativo in modalità supervisore.
Tuttavia, nei primi due casi, il sistema operativo invia alla stampante una pagina che descrive il motivo della terminazione, e passa a elaborare il programma successivo.
Nel terzo caso, se è stata richiesta un'operazione di ingresso ma non ci sono più schede di ingresso per tale processo, e se è stata richiesta un'operazione di uscita ma è già stato raggiunto il numero massimo di pagine stampabili dal processo, il sistema operativo termina il processo, emettendo la suddetto stampa diagnostica. Se invece l'operazione di ingresso/uscita richiesta è consentita, viene eseguita per conto del processo, il processore viene riportata alla modalità utente e il controllo salta all'istruzione del programma immediatamente successiva a quella che ha richiesto l'operazione di ingresso/uscita.
Pertanto, l'esecuzione di un processo inizia sempre in modalità supervisore, passa in modalità utente per eseguire le istruzioni del programma, torna temporaneamente in modalità supervisore per effettuare ogni operazione di ingresso/uscita, e termina sempre in modalità supervisore.
Le chiamate di sistema
modificaDato che i programmi applicativi non possono eseguire direttamente istruzioni di ingresso/uscita, devono ottenere tale servizio richiedendolo al sistema operativo.
E dato che i programmi applicativi non possono accedere alla memoria del sistema operativo, non possono neanche chiamare direttamente una routine di ingresso/uscita facente parte del sistema operativo.
Pertanto, si è dovuto inventare un meccanismo per passare il controllo dai programmi applicativi al sistema operativo. Tale meccanismo era un'apposita nuova istruzione del processore, denominata "chiamata di supervisore" (o "supervisor call" o "SVC"), oppure "interrupt software" (o "software interrupt").
La seconda denominazione era adottata nei casi in cui per tale operazione è stata sfruttata la gestione degli interrupt. Invece di inventare un meccanismo completamente nuovo, la nuova istruzione genera un nuovo tipo di interrupt, distinto da tutti i possibili interrupt hardware, ma gestito in modo analogo.
In seguito, tali operazioni sono state denominate "chiamate di sistema" ("system call").
Comunque siano implementate e denominate, tali chiamate di sistema hanno l'effetto di passare il controllo al sistema operativo e di impostare la modalità supervisore.
Quando riceve una chiamata di sistema, il sistema operativo esegue una routine in base al parametri della chiamata, che identifica l'operazione richiesta (ingresso oppure uscita). Tale routine legge dalla memoria o, più tipicamente, da registri del processore, i parametri necessari a eseguire la chiamata. Per esempio, per un'operazione di input, i parametri saranno il numero dell'unità nastro da cui leggere i dati, l'indirizzo di memoria in cui devono essere scritti, la lunghezza di tali dati.
Inizialmente c'erano solo due tipi di chiamate di sistema, una per l'ingresso e una per l'uscita; ma in seguito, man mano che i sistemi diventavano più complessi, sono stati aggiunti numerosi altri tipi.
Il flusso di controllo del sistema operativo
modificaRicapitolando, tramite l'uso delle schede di controllo e dai dispositivi di protezione hardware, il sistema operativo può avere la seguente operatività, che tuttavia non è la descrizione di uno specifico sistema operativo, bensì una descrizione semplificata della modalità di funzionamento dei primi sistemi operativi esistenti negli anni '50:
- Carica dal nastro magnetico di ingresso la prima scheda, verificando che si tratti di una scheda di inizio programma.
- Carica eventuali altre scheda 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 pagine che potranno essere stampate.
- Carica le schede che contengono il programma, fino a quella 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 una pagina di segnalazione dell'errore, provvede a leggere tutte le schede di dati rimanenti fino alla prossima scheda di inizio programma, e torna al punto 1.
- Quando il programma applicativo vuole leggere una o più schede dal nastro magnetico di ingresso o scrivere una o più pagine sul nastro magnetico di uscita, esegue un'istruzione di chiamata di sistema, impostando preventivamente in alcuni registri il tipo di operazione (lettura o scrittura), il numero della periferica, l'indirizzo di memoria dove inizia l'area da utilizzare per il trasferimento, e la lunghezza di tale area (ossia dei dati da trasferire). Tale istruzione imposta automaticamente il processore in modalità sistema. Il sistema operativo tenta di effettuare 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 sistema.
- Quando il programma ha terminato il proprio compito, torna il controllo al sistema operativo, che emette sul nastro una pagina di fine elaborazione, e torna al punto 1.
Le memorie secondarie ad accesso diretto
modificaLa memoria principale dei primi elaboratori era molto piccola, di pochi chilobyte, in quanto di elevatissimo costo unitario, e quindi era possibile contenere in memoria, oltre al sistema operativo, solo piccoli programmi, che elaborassero pochi dati per volta. E quindi molti problemi tecnici o scientifici non potevano essere risolti per a causa del costo eccessivo che avrebbe avuto la memoria di un computer sufficientemente grande da contenere tutto il programma e tutti i dati necessari a risolvere tale problema.
Tuttavia, molti problemi di calcolo, sebbene nel loro complesso richiedono molte istruzioni e molti dati, sono scomponibili in varie fasi di elaborazione, ognuna delle quali richiede ovviamente meno istruzioni dell'intero programma, e a volte anche meno dati di quelli richiesti dall'intero programma.
Pertanto, si è pensato che la soluzione per risolvere tali problemi fosse utilizzare una memoria, che, per quanto più lenta della memoria principale, avesse un costo unitario molto inferiore, ossia, a parità di costo, fosse molto più capiente.
Utilizzando una tale memoria secondaria, si poteva procedere nel seguente modo. La memoria principale contiene solamente le routine e i dati necessari alla fase corrente dell'elaborazione, e, quando si deve passare a un'altra fase, si copiano sulla memoria secondaria le routine e i dati non più necessari, liberando lo spazio da loro occupato nella memoria principale, e si copiano da un'altra porzione di tale memoria secondaria in memoria principale le routine e i dati necessari per la nuova fase di elaborazione.
L'operazione con cui si copiano informazioni dalla memoria principale alla memoria secondaria e poi si libera lo spazio da loro occupato nella memoria principale veniva detta "scaricamento", mentre l'operazione di copia di informazioni dalla memoria secondaria a uno spazio libero della memoria principale veniva detta "caricamento".
Tali caricamenti e scaricamenti non seguono un ordine strettamente sequenziale, in quanto si deve poter caricare e scaricare porzioni di programma e di dati che si trovano in vari punti della memoria. Pertanto l'uso dei nastri magnetici, che sono ad accesso sequenziale, si è dimostrato inadeguato a tale scopo.
D'altra parte, i caricamenti e gli scaricamenti riguardavano porzioni dati o di programma relativamente lunghe (rispetto ai singoli byte), in quanto si trattava di routine o strutture dati che occupavano alcuni KB.
Pertanto un sistema che accedesse in modo immediato a porzioni di alcune decine di KB, e in modo sequenziale all'interno di tali porzioni, poteva essere appropriato.
Con tali requisiti sono stati inventati i "tamburi magnetici", che erano dei cilindri rotanti ad alta velocità, ricoperti dello stato materiale magnetico dei nastri magnetici, e dotati di un braccio contenente numerose testine simili a quelle dei registratori magnetici.
Tali "tamburi", in seguito soppiantati dai dischi a testine mobili, avevano costi unitari (cioè per bit), intermedi tra quelli della memoria principale e quelli delle unità nastro, e 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, sovrascrivendo un altro overlay già presente in memoria principale.
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 dell'applicazione anche per salvare temporaneamente i dati per cui non c'era spazio sufficiente in memoria principale.
Comunque, quando si passava a un altro programma, il dati presenti sul tamburo venivano persi.
Le memorie secondarie persistenti ad accesso sequenziale
modificaCon il diminuire dei costi dei nastri magnetici, e la conseguente crescita delle loro dimensioni medie, si pensò di conservarvi dei dati persistenti, cioè che fossero riutilizzabili anche dopo l'esecuzione di un programma, e non solo per caricare le schede e scaricare le stampe.
Pertanto un programma poteva, durante il suo funzionamento scrivere dei dati in un nastro e farli leggere a un altro programma.
Un uso tipico era la compilazione di programmi scritti in un linguaggio di programmazione. Ricordiamo che i primi programmi erano scritti direttamente in linguaggio macchina. Il primo linguaggio di programmazione è stato il linguaggio assemblativo (assembly language).
Il programmatore scriveva sulle schede perforate il programma in linguaggio assembly e poi componeva il proprio pacco di schede mettendo prima le schede del programma assemblatore (assembler) in linguaggio macchina, poi le schede del proprio programma in linguaggio assembly, poi le schede dei dati da far elaborare al proprio programma. L'assemblatore veniva caricato in memoria, caricava in memoria il codice assembly, generava il codice macchina corrispondente, e a questo punto mandava in esecuzione tale codice al posto dell'assemblatore stesso. La stessa cosa succedeva usando il primo linguaggio di programmazione ad alto livello, il FORTRAN.
Tutto ciò costringeva a caricare su nastro le numerose schede dell'assemblatore o le ancora più numerose schede del compilatore FORTRAN, e questo era uno spreco di tempo per l'utente, e costringeva anche a compilare il programma ogni volta che doveva essere eseguito, e questo era uno spreco di tempo per il computer.
Pertanto, avendo a disposizione una unità nastro aggiuntiva, si poteva tenere a disposizione su tale unità l'assemblatore o il compilatore. Il pacco di schede dell'utente, contenente un programma in linguaggio sorgente, iniziava con il nome del compilatore necessario. Il sistema operativo, leggendo la scheda di controllo che specificava il compilatore, caricava dal nastro il compilatore appropriato.
Inoltre, il programma assemblatore o il compilatore FORTRAN poteva scrivere su un nastro magnetico il programma in linguaggio macchina ottenuto. In seguito, anche giorni o mesi dopo, si poteva lanciare il programma caricandolo direttamente dal nastro, senza passare per le schede perforate. Queste ultime servivano solo per modifiche al programma o per inserire i dati da elaborare.
L'uso di memorie secondarie persistenti (cioè le unità a nastro accessibili esplicitamente dai programmi applicativi) introduce i seguenti problemi per il sistema operativo:
- Dato che i programmi possono leggere da più unità nastro, la chiamata di sistema di ingresso adesso deve specificare da quale unità nastro intende leggere.
- Dato che i programmi possono scrivere su più unità nastro, la chiamata di sistema di uscita adesso deve specificare su quale unità nastro intende scrivere.
- Dato che i programmi possono leggere o scrivere lo stesso nastro più volte, e devono scoprire quali parti del nastro contengono dati validi e quali parti contengono i dati di una precedente scrittura, non più valida, ci devono essere nuove chiamate di sistema per riavvolgere il nastro, per scoprire quando il contenuto sul nastro è terminato, per marcare il nastro come terminato nel punto corrente.
Mentre i primi computer erano usati solamente per calcoli tecnici o scientifici, l'impiego di nastri magnetici disponibili ai programmi applicativi ha consentito l'utilizzo dei computer anche per elaborazioni di tipo gestionale, come la seguente. Un nastro contiene l'elenco dei dipendenti di una grande azienda, ognuno con i suoi dati relativi al contratto di lavoro, in ordine di numero di matricola; un altro nastro contiene i dati sulle presenze al lavoro, su ferie e permessi goduti, e sulle giornate di malattia, in ordine di numero di matricola, e per ogni dipendente in ordine temporale. Il programma legge dal primo nastro i dati di un dipendente, legge dal secondo i dati dello stesso dipendente, calcola la busta paga, e le scrive su un terzo nastro o la emette sul nastro di stampa.
Le memorie secondarie persistenti ad accesso diretto
modificaGrazie all'uso delle unità nastro e del loro supporto da parte del sistema operativo, sono stati possibili molti tipi di applicazioni del computer. Tuttavia, per molti problemi le unità a nastro avevano un grosso difetto: i loro accesso era inerentemente sequenziale. Infatti se si doveva elaborare un dato che si trovava alla fine del nastro, e poi uno all'inizio, e poi uno ancora verso la fine, il computer passava moltissimo tempo ad aspettare che il nastro si svolgesse e riavvolgesse fino al punto dove si trovava il dato richiesto.
Per risolvere tali problemi sono necessarie memorie secondarie ad accesso diretto, cioè che consentono molto rapidamente l'accesso a qualunque dato in esse contenuto.
In effetti i tamburi sono ad accesso diretto, ma hanno un costo troppo elevato per memorizzare grandi quantità di dati (cioè superiori a 10 MB).
Perciò sono stati inventato i dischi magnetici, che sono sempre dispositivi magnetici rotanti, ma costituiti da una pila di dischi orizzontali fissati allo stesso perno verticale, e con una sola testina di lettura/scrittura per ogni disco. Le testine sono fissate a un braccio verticale che, spostandosi rapidamente verso il perno dei dischi o lontano da tale perno, consente alle testine di accedere a tutta la superficie dei dischi.
Mentre per i tamburi il tempo di accesso è solo la latenza di rotazione del cilindro, per i dischi il tempo di accesso è la somma di tale latenza di rotazione con il tempo impiegato dalle testina a raggiungere il cilindro contenente le tracce su cui ci sono i dati a cui si è interessati.
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, si può procedere 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 enorme numero di ricerche. E usando solo nastri magnetici, tali ricerche avrebbero 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 dei 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".
I file system
modificaD'ora in poi, riferendosi alle memorie secondarie, non si intenderanno più i tamburi, in quanto abbandonati, ma si userà l'espressione "unità disco". L'unità disco non va confusa con il disco, che è il volume contenente i dati, così come il registratore di nastri non viene confuso con la bobina di nastro. Infatti, i primi dischi erano normalmente rimovibili, analogamente ai nastri. Il supporto dei dati, che sia un disco o un nastro, viene chiamato "volume".
In realtà, non esistono solo dischi magnetici, ma anche ottici e magneto-ottici, ed esistono anche memorie secondarie che non sono affatto dei dischi, come le memorie flash. Tuttavia, dal punto di vista della logica del sistema operativo, tali memorie sono gestite in modo analogo ai dischi, e quindi tuttora, impropriamente, si parla di dischi a stato solido per riferirsi a memorie di massa elettroniche, anche se non hanno affatto una forma discoidale.
Per condividere una unità disco tra vari programmi, assicurando che nessun programma possa danneggiare le funzionalità degli altri programmi, è ovviamente necessario che tutti gli accessi all'unità disco siano mediati dal sistema operativo, cioè che l'unico modo in cui un programma possa leggere o scrivere dati su un disco sia chiedendo al sistema operativo di svolgere tale servizio per conto del programma applicativo.
Infatti, se si consentisse a ogni programma di accedere direttamente all'unità disco, si correrebbe il rischio che un errore di programmazione distrugga l'intero contenuto del disco.
In realtà, i primi sistemi operativi per microcomputer, come CPM e MS-DOS, non avevano alcuna protezione hardware, neanche per l'accesso all'unità disco, e tuttavia erano abbastanza robusti. Questo era dovuto al fatto che la stragrande maggioranza dei programmatori si atteneva alle raccomandazioni di accedere alle unità disco solo tramite richieste al sistema operativo, e che per scrivere sul disco era comunque richiesta una sequenza di operazione talmente complessa che era molto improbabile che un errore di programma provocasse una scrittura su disco non voluta da parte di un programma che non aveva nessuna intenzione di scrivere sul disco.
Tuttavia, tali sistemi non protetti erano sistemi monoutente, non critici per la vita. Invece, un sistema multiutente, o che debba garantire un'elevata sicurezza ha bisogno di protezione hardware anche per l'accesso alle unità disco.
In realtà, esistono vari livelli di protezione, per vari livelli di rischi.
Il primo livello, come abbiamo già detto, consiste nell'impedire ai programmi applicativi l'accesso diretto alle unità disco.
Un secondo livello consiste nell'impedire comunque ai programmi applicativi l'accesso a parti dell'unità disco per cui l'utente del programma non è autorizzato. Questo è il livello dei "diritti d'accesso".
Un terzo livello consiste nell'impedire accessi non autorizzati neanche smontando il disco e collegandolo a un altro computer, per proteggere dati segreti (o sensibili come si dice oggi). Questo si ottiene tramite la crittografia.
Un quarto livello consiste nell'impedire a un programma applicativo di occupare troppo spazio su disco, con il rischio di impedire il funzionamento di altri programmi per l'esaurimento dello spazio libero. Questo si ottiene tramite le "quote" di disco.
Ulteriori livelli sono quelli di "tolleranza ai guasti" (in inglese, "fault tolerance"), che consiste nell'utilizzare sistemi ridondanti che continuino a funzionare anche se ci sono guasti nell'hardware.
Inizialmente, veniva implementato solo il primo livello, cioè l'accesso al disco mediato dal sistema operativo.
Per assicurare che i dati di ogni utente non si confondessero con i dati di altri utenti, si dovette suddividere logicamente lo spazio di un disco in porzioni dette "file".
La gestione dei file è così complessa da non poter essere trattata qui, se non per descrivere un importante aspetto di programmazione relativa ai file.
Concettualmente, per scrivere un blocco di dati in un file potrebbe bastare una sola chiamata di sistema, con la quale il programma applicativo specifica su quale file vuole scrivere, in che posizione di tale file, in quale parte della memoria si trovano i dati da scrivere, e quanto sono lunghi.
Tuttavia, per motivi di praticità si voleva fare in modo che i file fossero identificabili con delle parole, e non solo con dei numeri. Inoltre, per motivi di correttezza funzionale, ma a volte anche per questioni di riservatezza o di responsabilità personale, si voleva fare in modo che ogni utente potesse accedere solamente ai propri file. Infine, per poter leggere o scrivere un file, il sistema operativo aveva bisogno di sapere in quali parti del disco si trovano i suoi dati.
Usando la chiamata di sistema descritta prima, in un computer con poca memoria principale il sistema operativo avrebbe dovuto effettuare le seguenti operazioni:
- Caricare da un'apposita area del disco la lista dei nomi dei file e cercarvi quello specificato, per poi prelevarne le informazioni principali sul file.
- Verificare in base a tali informazioni se l'utente è autorizzato ad accedere a tale file.
- Caricare da un'apposita area del disco la descrizione delle aree del disco occupate da tale file.
- Scrivere sul disco i dati forniti dal processo applicativo.
Chiaramente tale sequenza di operazioni richiede parecchio tempo.
Per migliorare l'efficienza del sistema operativo, l'accesso ai file è invece costituito da tre fasi:
- Apertura del file. Con la chiamata di sistema "open" il processo passa al sistema operativo il nome del file, e il sistema operativo effettua tutte le suddette operazioni, tranne l'ultima. Al termine della chiamata, mantiene in memoria varie informazioni relative al file aperto, ossia un "descrittore" del file, e rende al processo chiamante un numero che rappresenta l'indice di tale descrittore nella tabella dei descrittori dei file aperti.
- Lettura o scrittura del file. Con le chiamate di sistema "read" e "write" il processo specifica solamente l'indice del descrittore, la posizione nel file e l'indirizzo di memoria da usare per la lettura o la scrittura.
- Chiusura del file. Con la chiamata di sistema "close", il descrittore del file viene annullato, e il suo spazio diventa disponibile per altri file.
La multiprogrammazione
modificaI primi monitori residenti dotati di file system elaboravano un solo programma per volta. Quando un programma terminava, perché aveva finito la sua elaborazione o perché veniva arrestato dal sistema operativo, veniva caricato e lanciato il programma successivo.
Ogni programma aveva a disposizione tutta la memoria principale, eccetto quella in cui risiedeva il sistema operativo, poteva eseguire istruzioni di elaborazione senza essere interrotto dal sistema operativo fino a quando scadeva il lotto di tempo assegnato per l'esecuzione del programma, e aveva il sistema operativo sempre pronto a soddisfare richieste di lettura o scrittura di dati sulle memorie di massa.
Questa architettura aveva il pregio della semplicità, e, conseguentemente, della piccola dimensione del sistema operativo. Il sistema operativo insieme al software applicativo potevano stare in poche decime di chilobyte di memoria.
Le prime applicazioni dei computer erano principalmente di due tipi: calcolo numerico e contabilità. Per le applicazioni di calcolo numerico, questo tipo di sistema operativo era abbastanza soddisfacente, ma per le applicazioni di contabilità emerse rapidamente il fatto che il processore risultava ampiamente sottoutilizzato. Infatti, mentre le applicazioni di calcolo numerico eseguono lunghe elaborazioni su pochi dati, le applicazioni di contabilità eseguono brevi elaborazioni su molti dati.
Una tipica applicazione di contabilità è, come detto sopra, l'aggiornamento dei saldi dei conti correnti di una banca. Tale applicazione deve semplicemente sommare o sottrarre l'importo di ogni movimento dall'importo del saldo del conto. Anche con i lentissimi processori degli anni '50, il tempo necessario al processore per effettuare la somma era enormemente inferiore al tempo necessario per leggere il movimento bancario, leggere il vecchio saldo, e scrivere il nuovo saldo, sia usando i nastri che i dischi.
In pratica, usando il monitor residente per un'applicazione di contabilità, il processore era quasi sempre in attesa che qualche dato venisse letto da una memoria di massa o scritto su una memoria di massa. D'altra parte, le prime applicazioni di contabilità erano così semplici da poter stare in pochissimi chilobyte di memoria, e gli bastavano pochissimi altri chilobyte di memoria per i dati in corso di elaborazione.
Pertanto, appena sono stati costruiti computer con alcune decine di chilobyte di memoria, risultava possibile tenere in memoria più programmi contemporaneamente. Per fare un esempio, avendo un sistema di 32 KB di memoria, e un sistema operativo che occupava 12 KB, rimaneva spazio per tenere in memoria ben 4 processi di 5 KB ciascuno, dei quali 4 di codice e 1 di dati.
Avendo più processi contemporaneamente in memoria, si poteva ottimizzare l'utilizzo del computer nel seguente modo.
Quando un processo chiede al sistema operativo di leggere una scheda, il sistema operativo invia una richiesta corrispondente al dispositivo elettronico che gestisce l'unità nastro o l'unità disco, e, invece di attendere la risposta, attiva un altro processo, che incomincia a eseguire le sue istruzioni.
Se anche questo secondo processo dovesse fare una richiesta al sistema operativo che non potesse essere soddisfatta immediatamente, come stampare una riga, il sistema operativo dovrebbe scegliere un terzo processo da mandare in esecuzione, in quanto i primi due sono in attesa del completamento delle loro richieste di ingresso/uscita.
Solo se tutti i processi in memoria fossero in attesa del completamento delle loro chiamate di sistema, allora il processore si fermerebbe ozioso in attesa di qualche evento.
Quindi ogni processo può trovarsi in uno dei seguenti stati:
- In esecuzione (running).
- In attesa di un evento esterno, tipicamente il completamento di un'operazione di ingresso/uscita (waiting).
- Pronto ad andare in esecuzione (ready).
Inizialmente, tutti i processi sono ready. Poi ne viene scelto uno, che viene mandato in esecuzione, e quindi passa in stato running.
Essendoci un solo processore, al più un processo è in stato running. Ma e se c'è almeno un processo ready, allora ce n'è almeno uno running, in quanto ready in realtà vuol dire in attesa che si liberi il processore, "occupato" da un altro processo. Però potrebbe darsi che tutti i processi siano in stato waiting, e quindi il processore sarebbe ozioso (idle) in attesa di un evento che riporti un processo in stato running.
Non appena una memoria di massa ha completato l'operazione richiestale dal sistema operativo, cioè quando è riuscita a leggere la scheda richiesta o a scrivere la riga fornita, il dispositivo di controllo dell'unità nastro o dell'unità disco invia un segnale di interrupt (hardware) al processore. Quando il processore riceve un interrupt, sospende immediatamente il processo running, se ce n'è uno, e passa il controllo a un'apposita routine di gestione dell'interrupt, la quale prende atto di quale operazione è stata completata, e può rimettere in stato running il processo che era in attesa di tale evento, e in stato ready il processo sospeso, oppure può mettere in stato ready il processo che era in attesa di tale evento, e far ripartire il processo sospeso per gestire l'interrupt.
Questa architettura, dato che consente l'esecuzione in parallelo di più processi, viene detta multiprogrammazione (multiprocessing).
La multiprogrammazione consente di sfruttare meglio il processore, perché, mentre alcuni processi sono in attesa del completamento di operazioni di ingresso/uscita, un altro processo è in esecuzione e quindi il processore è sfruttato maggiormente.
Un sistema operativo multiprogrammato è tuttavia più complesso di un semplice monitor residente, in quanto deve suddividere la memoria principale in segmenti assegnati ai vari programmi, deve decidere quale tra i programmi in attesa deve essere mandato in esecuzione, e deve gestire l'accesso in parallelo alle periferiche.
Man mano che i computer hanno aumentato la dimensione della propria memoria principale, anche i sistemi usati per calcolo numerico o altre applicazioni sono diventati multiprogrammati.
La protezione nei sistemi multiprogrammati
modificaIn un sistema multiprogrammato, per attuare un'efficace protezione del sistema operativo, ossia l'insieme di tecniche per impedire che un processo possa danneggiare, direttamente o indirettamente, altri processi, si presentano ulteriori problemi.
In primo luogo, la memoria non è più divisa in due sole parti, una per il sistema operativo e l'altra per il processo, ma in varie parti, una per il sistema operativo e ognuna delle altre per un processo distinto.
Pertanto, per impedire a un processo di accedere alla memoria di un altro processo non basta più un solo registro base, ma servono due registri, che delimitano inferiormente e superiormente la parte di memoria assegnata al processo.
Tali registri devono essere impostati ogni volta che il controllo passa dal sistema operativo a un processo.
In secondo luogo, il controllo può passare da un processo a un altro appena dopo che il primo processo ha letto alcuni dati da un nastro o da un file su disco, e appena prima che legga altri dati dallo stesso nastro o dallo stesso file.
Mentre i dischi sono condivisi tra processi, cioè due processi potrebbero leggere da due file diversi dello stesso disco, tipicamente i file su disco e gli interi nastri non sono condivisi tra processi, cioè non si ammette che più processi leggano o scrivano alternativamente porzioni dello stesso file su disco o dello stesso nastro.
A tale scopo, anche per i nastri si introducono le chiamate di sistema "open" e "close", ma non per migliorare le prestazioni, bensì con lo scopo, rispettivamente, di "riservare" e di "rilasciare" la risorsa nastro.
Dopo aver "aperto" un nastro, solamente il processo che ha eseguito tale operazione può accedere a tale nastro, fino a quando "chiude" il nastro. Ovviamente se un processo tenta di aprire un nastro già aperto, la chiamata di sistema fallisce, e falliscono pure tutti gli accessi successivi a tale nastro, dato che per poter accedere a un nastro si deve aver effettuato una chiamata "open" di successo.
Analogo comportamento viene introdotto per l'apertura dei file, che ha l'effetto di garantire l'accesso esclusivo al file da parte di un solo processo fino alla chiusura di tale file.
Se un processo termina, spontaneamente o per azione del sistema operativo, senza aver chiuso tutti i file e i nastri che aveva aperto, il sistema operativo deve occuparsi di rilasciare tali risorse, per consentire ad altri processi di accedervi.
A tale scopo, ogni volta che un processo termina, per qualunque motivo, il sistema operativo scandisce la sua lista dei descrittori di file e chiude i file che risultano ancora aperti.
La schedulazione a lungo termine
modificaUno dei problemi introdotti dall'uso di sistemi operativi è costituito dal criterio da usare per decidere quali programmi eseguire tra la lista dei programmi pronti.
Una prima tipo di scelta si ha quando un processo termina, lasciando libero dello spazio di memoria e si deve decidere quale programma caricare. Questo problema è detto "schedulazione a lungo termine".
Un secondo tipo di scelta si ha quando un processo va in attesa di un'operazione di ingresso/uscita, e si deve scegliere a quale dei processi pronti si deve passare il controllo del processore. Questo problema è detto "schedulazione a breve termine".
I sistemi a time sharing
modificaIl fatto che un sistema fosse multiprogrammato era principalmente un aspetto interno del sistema operativo e del computer. I programmatori scrivevano i programmi come se dovessero essere eseguiti da soli su un computer con pochissima memoria. Gli operatori caricavano i programmi (o job) in successione, come se ogni programma dovesse essere eseguito dopo che il precedente era terminato.
Era solo il sistema operativo che ottimizzava l'elaborazione caricando più programmi contemporaneamente, e caricando un nuovo programma appena ne terminava uno.
I primi computer non avevano un'interfaccia utente, ma ricevevano l'input da nastri, in cui erano state precedentemente caricate le schede perforate, e inviavano l'output su altri nastri, che poi venivano inviati alla stampante. Questo era dovuto al fatto che, a causa dell'enorme costo dei computer, sarebbe stato un costo economicamente ingiustificabile far usare un computer a un solo utente.
Per fare un parallelo, un computer di oggi è come un ciclomotore, o una bella bicicletta, che tutti possono comprarsi, ma i primi computer erano come locomotive ferroviarie. Nessuno usa una locomotiva per un solo passeggero.
Negli anni '60, grazie all'accresciuta potenza dei processori e al diminuito costo della memoria principale, divenne però fattibile l'uso diretto dei computer da parte degli utenti, cioè con un'interfaccia utente basata su schermo e tastiera.
Questa possibilità emerse sfruttando un sistema multiprogrammato in cui viene posto in esecuzione un programma per ogni utente collegato tramite terminale interattivo (cioè tramite telescrivente, o tramite tastiera e schermo alfanumerico).
Il sistema multiprogrammato dovette essere però modificato per tenere conto del seguente fenomeno. Se tra i programmi in esecuzione parallela alcuni ricevono continuamente attenzione da parte del sistema operativo, altri possono rimanere in attesa anche per tempi piuttosto lunghi (alcuni minuti). Questo non è un grande problema quando il tempo complessivo dell'elaborazione è comunque di decine di minuti.
Invece, in un sistema interattivo, risulterebbe inaccettabile che quando l'utente invia un comando dalla tastiera questo comando rimanesse completamente ignorato per parecchi secondi.
Per avere un sistema operativo che sia realmente utilizzabile da più utenti, si deve dare ad ogni utente l'illusione di essere l'unico utente del computer, anche se apparirà essere un computer non molto veloce.
A tale scopo, il sistema operativo deve garantire tempi di attesa contenuti a ogni programma, e questo può essere fatto solo con una politica di attivazione dei programmi (detta schedulazione) che assegni la priorità agli utenti in attesa e, che sospenda i programmi in esecuzione da più di qualche centesimo di secondo.
Un tale sistema è detto a divisione di tempo (in inglese time sharing), in quanto il tempo di elaborazione viene suddiviso equamente tra tutti gli utenti.