Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Indice
1 Concetti introduttivi ...................................................... 2
1.1 Principali funzioni di un sistema operativo ............................ 2
1.1.1 Facilità di programmazione e portabilità dei programmi ............... 4
1.1.2 Gestione delle risorse ............................................... 5
1.1.3 Protezione, sicurezza, tolleranza ai guasti .......................... 6
1.1.4 Astrazione di macchina virtuale ...................................... 7
1.2 Cenni storici sull'evoluzione dei sistemi operativi .................... 8
1.2.1 I primi sistemi di elaborazione ..................................... 8
1.2.2 I primi sistemi batch............................................... 9
1.2.3 Sistemi batch multi-programmati .................................... 10
1.2.4 Sistemi time-sharing............................................... 15
1.2.5 Sistemi in "tempo reale" ........................................... 17
1.2.6 Sistemi operativi per personal computer ............................ 19
1.2.7 Sistemi operativi per sistemi paralleli e distribuiti .............. 20
1.3 Richiami di architetture dei sistemi di elaborazione .................. 21
1.3.1 Il processore...................................................... 22
1.3.2 La memoria......................................................... 23
1.3.3 Dispositivi d'ingresso/uscita ...................................... 26
1.3.4 Meccanismo d'interruzione .......................................... 26
1.3.5 Accessi diretti alla memoria (DMA) ................................. 31
1.3.6 Meccanismi di protezione ........................................... 33
Riassunto ................................................................. 35
2
_______________________________________________________________________________
1 Concetti introduttivi
In questo primo capitolo vengono presentate le principali funzioni che il sistema
operativo svolge nell'ambito di un sistema di elaborazione. Viene anche presentata
una breve panoramica storica sull'evoluzione dei sistemi operativi per meglio
comprendere i principi che stanno alla base del loro progetto e le motivazioni
che hanno portato al loro sviluppo. Vengono, quindi, passate in rassegna le varie
tipologie di sistemi operativi, distinguendoli in base alle finalità per cui sono
stati progettati.
Per affrontare le problematiche relative allo sviluppo delle componenti di un
sistema operativo è necessario conoscere la struttura di un sistema di
elaborazione e, in particolare, le componenti hardware con le quali il sistema
operativo deve interfacciarsi.
Per questo motivo vengono brevemente richiamate quelle componenti della struttura
fisica di un sistema di elaborazione a cui verrà fatto riferimento nei capitoli
successivi del testo.
1 https://it.wikipedia.org/wiki/Cat_(Unix)
2 https://learn.microsoft.com/it-it/windows-server/administration/windows-commands/type
5
_______________________________________________________________________________
Un esempio, il blocco critico, sarà trattato nel seguito, dove verranno illustrate
le varie strategie adottate per far fronte a questi particolari tipi di
malfunzionamenti.
Come si può notare, per completare l'esecuzione dei tre programmi sono necessarie
27 unità di tempo durante le quali, però, la CPU esegue programmi solo per 10
unità di tempo. L'efficienza complessiva con cui viene usata la CPU è quindi pari
a 10/27 e cioè pari al 37% del suo tempo.
In Figura 1.5 viene riportato un analogo diagramma temporale nel caso in cui il
sistema sia multiprogrammato, e cioè con i tre programmi presenti
contemporaneamente in memoria e con la CPU che, quando non può proseguire
l'esecuzione di un programma, viene commutata, se possibile, su un diverso
programma in grado di essere eseguito.
funzioni del sistema. Sono stati introdotti meccanismi di protezione sia della
memoria (per evitare che lo spazio dedicato a un programma sia erroneamente
manomesso durante l'esecuzione di un altro programma), sia della CPU, con
l'introduzione del doppio stato di esecuzione (privilegiato/non privilegiato) per
garantire che le istruzioni macchina con cui è possibile modificare lo stato di
una qualunque risorsa fisica siano eseguibili solo da parte di funzioni del
sistema operativo.
I sistemi batch multiprogrammati hanno costituito i primi complessi sistemi
operativi funzionanti sui grossi mainframe. Un classico esempio è costituito dal
sistema OS/360 funzionante sulle macchine IBM della serie 360 e, successivamente
370.
Altri sistemi, noti col nome di “soft real-time” hanno esigenze meno stringenti.
In questi casi, la violazione di una deadline non compromette il corretto
funzionamento dell'ambiente operativo ma ne riduce le prestazioni e quindi la
qualità del servizio (QoS - Quality of Service).
Una categoria di sistemi in tempo reale molto importante per la loro diffusione
è costituita dai cosiddetti sistemi immersi (embedded systems). Si tratta di
sistemi progettati su particolari specifiche e dedicati a singole applicazioni
di grande consumo. In questa categoria di applicazioni non è sempre facile
distinguere fra il sistema operativo e l'applicazione. In realtà, come indicato
anche dal nome, tutto il software è al tempo stesso software di sistema e software
applicativo, immerso spesso nell'ambito di una singola scheda, o di un singolo
chip, e dedicato a un'unica applicazione. Rientrano in questa categoria tutte le
schede di controllo di varie apparecchiature, dalle schede controllo motore di
un'auto a quelle inserite all' interno di normali elettrodomestici ecc.
I primi sistemi in tempo reale erano costituti da programmi ad hoc scritti
direttamente in assembler e adatti a gestire una sola particolare applicazione.
Successivamente, sfruttando lo sviluppo dei sistemi operativi time-sharing, sono
stati sviluppati molti sistemi operativi in tempo reale di tipo generale sia per
applicazioni hard sia per applicazioni soft. Fra questi possiamo citare il sistema
VxWorks [www.sun.com/software/solaris/] o, in alcuni casi, varianti di sistemi
time sharing adattate per funzionare con applicazioni in tempo reale, come RT-
Linux [www.linux.org].
1.3.1 Il processore
È compito del processore eseguire, una dopo l'altra, le istruzioni di un programma
contenuto nella memoria centrale. Durante l'esecuzione di un'istruzione, il
processore attraversa diverse fasi. I processori esistenti in commercio sono
molto diversi l'uno dall'altro. Nel seguito ci limiteremo a descrivere alcune
delle loro principali caratteristiche, comuni a quasi tutti i processori.
L'aspetto che maggiormente caratterizza un processore, dal punto di vista del
programmatore, è costituito dall'insieme dei registri interni (unità di memoria
interne al processore e ad accesso molto più veloce rispetto alle locazioni della
memoria centrale). Normalmente i registri interni vengono distinti in registri
generali o registri programmabili (e cioè direttamente visibili al programmatore)
e in registri di stato e controllo.
Un altro impattante registro, normalmente noto col nome di Program Status Word
(PS), è disponibile su tutti i processori anche se talvolta individuato con nomi
diversi (e in certi casi costituto anche da un insieme di più registri). Il suo
compito è quello di mantenere alcune informazioni sullo stato interno del
processore come per esempio i bit che codificano il risultato di un'istruzione e
che sono utilizzati per condizionare una successiva istruzione di salto (condition
code) oppure i bit che condizionano le modalità di funzionamento del processore
(a cui accenneremo nel seguito di questo paragrafo). Un esempio di questi ultimi
è costituito dai bit che specificano il livello di privilegio con cui il
processore sta eseguendo le istruzioni o il bit che abilita il processore a
ricevere interruzioni.
1.3.2 La memoria
La memoria di un sistema di elaborazione si distingue in memoria volatile (o
memoria RAM - Random Access Memory), memoria non volatile (o memoria ROM - Read
Only Memory) e memoria di massa esterna. La memoria ROM contiene delle
informazioni non modificabili. Di solito, essa contiene il programma di
inizializzazione del sistema (Bootstrap).
La memoria RAM è la memoria principale ed è utilizzata per contenere i programmi
24
_______________________________________________________________________________
La cache contiene, di volta in volta, una "immagine" ridotta del contenuto della
memoria principale. Ogni volta che il processore esegue un accesso in memoria,
come prima cosa si controlla se la locazione riferita si trova nella cache. Se
la risposta è positiva (cache hit), alla cache si accede direttamente, senza
eseguire l'accesso in memoria, riducendo quindi la durata dell'operazione. Se
invece la locazione riferita non è presente nella cache (cache miss), si effettua
un accesso in memoria e contemporaneamente la locazione viene memorizzata nella
cache. Quindi, la prossima volta che il processore accederà a quella locazione,
molto probabilmente la troverà nella cache. Il meccanismo di funzionamento della
memoria cache sfrutta il principio di località dei riferimenti. Secondo questo
principio i riferimenti alla memoria da parte di un programma, in un certo
intervallo di tempo, tendono a essere "vicini" fra loro.
Consideriamo per esempio un programma che, durante la sua esecuzione, esegue un
ciclo while ( ... ) per un certo numero di volte. Durante l'intervallo di tempo
in cui viene eseguito il ciclo, il processore accederà allo stesso gruppo di
istruzioni che fanno parte del ciclo. Molto probabilmente, dopo la prima
esecuzione del ciclo, tutte le istruzioni si troveranno nella cache, quindi i
cicli successivi saranno molto più veloci ed efficienti.
Nei moderni sistemi che forniscono un supporto hardware al sistema operativo per
la gestione della memoria principale (sistemi con paginazione e/o segmentazione),
esistono anche altre piccole unità di memoria con accesso associativo (Memory
Management Unit) che si comportano come un'ulteriore memoria cache (cache di
indirizzi o Translation Lookaside Buffer).
Man mano che ci allontaniamo dal processore le dimensioni delle unità di memoria
crescono, ma diminuiscono le velocità di accesso oltre che il costo per bit di
tali unità. La memoria più veloce è costituita dai registri interni del
processore, cui si può accedere in un solo ciclo di clock. Subito dopo, in termini
di velocità, viene la cache. Dato il costo di tale dispositivo, le dimensioni
tipiche di una cache si aggirano intorno a qualche centinaio di kilobyte anche
se, nei sistemi moderni, sono presenti ormai memorie cache molto più capienti;
inoltre è ormai normale avere più livelli di memorie cache. Segue la memoria RAM,
che è più lenta di una cache, ma che nei moderni PC può arrivare fino a qualche
gigabyte. Abbiamo quindi i dischi magnetici che vanno dalla decina di gigabyte
fino al terabyte (1012 byte). Infine, i nastri magnetici e i dischi ottici che
vengono utilizzati per memorizzare grandi archivi di dati che non sono di facile
accessibilità (per esempio gli archivi storici delle operazioni contabili di una
banca).
programma chiamante (2) che quindi continua la sua esecuzione con l'istruzione
successiva alla call.
In particolare, nella parte in alto a sinistra (1a) sono indicati i valori dei
due registri dopo che l'istruzione call è stata prelevata ma prima che inizi la
sua esecuzione. Nella parte in alto a destra (1b) sono riportati, invece, i valori
dei registri alla fine dell'esecuzione della call. Come si può notare, l'indirizzo
di ritorno è stato memorizzato in cima alla pila e nel PC è stato caricato
l'indirizzo della prima istruzione della funzione chiamata. Analogamente, nella
parte bassa, vengono riportati i valori contenuti nei registri rispettivamente
all'inizio (a sinistra) e alla fine (a destra) dell'esecuzione dell'istruzione
return, la quale riprende dalla pila l'indirizzo di ritorno (indirizzo
dell'istruzione successiva alla call) e lo ricarica nel PC per riprendere
l'esecuzione del programma chiamante. Ovviamente se la funzione chiamata deve
usare dei registri generali, prima di usarli è necessario salvarne i valori nella
pila al fine di ripristinare tali valori prima di ritornare al programma
29
_______________________________________________________________________________
In Figura 1.14 sono riportati i valori dei registri di macchina coinvolti: PC,
PS e SP nell'ipotesi che la pila del programma sia localizzata in memoria a
partire dall' indirizzo 3900. In particolare, nella parte in alto a sinistra (1a)
sono indicati i valori dei tre registri alla fine dell'istruzione durante la
quale è arrivato il segnale d'interruzione.
Il valore contenuto nel registro PS è indicato come "PSW prog". Nella parte in
alto a destra (1b) sono riportati, invece, i valori dei registri dopo che è stato
accettato il segnale d'interruzione. Come si può notare, in cima alla pila sono
stati salvati i valori dei registri PC e PS relativi al programma interrotto e
in essi sono stati caricati i valori prelevati dal vettore d'interruzione.
Analogamente, nella parte bassa, vengono riportati i valori contenuti nei registri
rispettivamente all'inizio (a sinistra) e alla fine (a destra) dell'esecuzione
dell'istruzione iret, la quale recupera dalla pila i valori dei registri PC e PS
per riprendere l'esecuzione del programma interrotto.
Oltre alle interruzioni cui abbiamo fatto riferimento finora, nei moderni sistemi
31
_______________________________________________________________________________
Il primo inconveniente è legato al fatto che, per ogni dato della sequenza, deve
essere generata un'interruzione per far intervenire la CPU al fine di trasferire
il dato in (o dalla) memoria. Ciò implica un overhead di sistema non indifferente
poiché la funzione di servizio dell'interruzione, oltre a trasferire il dato,
deve anche salvare il valore degli eventuali registri di macchina utilizzati e,
32
_______________________________________________________________________________
Questo tipo di funzionamento del canale viene anche indicato col nome di
funzionamento in "cycle stealing" e cioè a sottrazione di cicli nel senso che,
per ogni accesso alla memoria, il canale ruba un ciclo di memoria alla CPU, la
quale viene quindi leggermente rallentata ma senza l'onere di dover eseguire
un'intera funzione di servizio di un'interruzione.
Nelle varie componenti del sistema operativo viene dedicata molta attenzione a
questi problemi. In certi casi la soluzione viene offerta dal sistema operativo
mediante la realizzazione di particolari meccanismi all'interno dei propri
componenti. Alcuni meccanismi di base sono però generalmente disponibili su tutti
i processori direttamente in hardware in modo tale da semplificare la
realizzazione delle varie componenti del sistema operativo e, al tempo stesso,
da renderle più efficienti.
funzionamento: il modo utente (user mode, detto anche modo non privilegiato) e
il modo supervisore (kernel mode o modo privilegiato).
La necessità del doppio stato deriva proprio dalle esigenze del sistema operativo
che, come abbiamo già messo in evidenza, ha il compito di gestire tutte le risorse
della macchina fisica. È proprio per non vanificare le scelte implementate dal
sistema operativo che è necessario "obbligare" un programma applicativo ad
accedere alle risorse fisiche esclusivamente per mezzo delle chiamate di sistema.
Per questo motivo, quando gira un programma applicativo, lo stato del processore
deve essere quello utente, nel quale viene proibita l'esecuzione di tutte le
istruzioni che modificano lo stato di una risorsa fisica (istruzioni
privilegiate). Viceversa, una qualunque funzione del sistema operativo deve
girare col processore in modo supervisore così da avere il controllo completo
della macchina fisica.
Per chiamare una funzione del sistema operativo può essere quindi utilizzato il
meccanismo d'interruzione che consente di eseguire la funzione chiamata con un
valore del registro PS corrispondente allo stato privilegiato. È questo il motivo
per cui, nei moderni processori, sono disponibili istruzioni come la int.
35
_______________________________________________________________________________
Per lo stesso motivo per cui, in stato utente, non è possibile modificare il
registro PS, risultano privilegiate anche le istruzioni STI e CLI che modificano
il bit di PS relativo all'abilitazione delle interruzioni.
Questo aspetto, come sarà meglio illustrato nel Capitolo 2, è legato alla
necessità di realizzare le funzioni di sistema in modo tale che il loro
comportamento sia analogo al comportamento delle operazioni primitive della
macchina fisica cui abbiamo già fatto riferimento.
Sono, infine, istruzioni privilegiate anche quelle che modificano lo stato delle
altre risorse fisiche, per esempio quelle relative alle operazioni per modificare
il meccanismo di gestione della memoria (Memory Management Unit).
Riassunto
Il sistema operativo rappresenta un componente fondamentale di ogni sistema di
elaborazione. I principali compiti che questo deve svolgere sono classificabili
in due categorie.
Esistono oggi molte categorie di sistemi operativi, diverse le une dalle altre
in base ad alcuni fattori.