I Sistemi Operativi

Sito culturale

Funzionalità del sistema operativo

Gestione dell’unità di elaborazione

Il concetto di macchina virtuale è estendibile alla struttura del sistema operativo, in quella che viene chiamata architettura a buccia di cipolla. In tale astrazione il sistema operativo è visto come una serie di strati di moduli software; ogni modulo è un livello di macchina virtuale che può utilizzare tutte le risorse hardware e software delle macchine sottostanti (ma non di quelle soprastanti). Queste macchine ricoprono quindi la parte fisica fino ad arrivare al livello visibile dai programmi applicativi.

Il gestore dei processi o, più generalmente, il gestore dell’unità di elaborazione si occupa del modo in cui vari programmi, contemporaneamente residenti in memoria centrale, sono eseguiti, utilizzando il processore (o i processori) di cui è dotato il sistema in modo da ottimizzare tale risorsa critica.

Per processo si intende un programma nel suo divenire; ogni processo è quindi formato da una parte statica costituita dalle istruzioni del programma e da una parte dinamica determinata dai dati su cui il programma opera e dal suo stato di avanzamento. In linea di principio, si può pensare che ad ogni programma sia associato un processo, anche se, in alcuni contesti, ciò non è necessariamente vero (un programma potrebbe essere spezzato in tre processi diversi che si occupano, ad esempio, dell’acquisizione, dell’elaborazione e della stampa dei risultati).

Se pensiamo ad un sistema operativo multitasking (la quasi totalità dei sistemi in uso), ogni processo può trovarsi in uno dei tre seguenti stati: in esecuzione, pronto o in attesa; solo un processo tra quelli presenti può essere realmente in esecuzione, cioè solo uno di essi può occupare, in un dato istante, la risorsa processore.
Gli altri processi possono o essere pronti per andare in esecuzione o essere in attesa del termine di alcune operazioni di input/output per poter essere poi considerati pronti.
Il percorso del processo in un sistema time sharing, dal suo caricamento in memoria centrale alla sua completa esecuzione, può essere riassunto come segue: il processo entra in una coda di processi pronti e, quando viene il suo turno, va in esecuzione; mentre si trova in esecuzione possono verificarsi degli eventi che ne consigliano la sospensione detti interruzioni (interrupt) e che fanno cambiare di stato il processo.


Le interruzioni possono essere interne al processo (il programma richiede un’operazione di input/output) o esterne (scade il periodo di tempo dedicato a quello specifico processo).
Nel caso di interruzione interna il processo entra in attesa e verrà rimandato nella coda dei processi pronti solo quando il lavoro della periferica richiamata sarà terminato; se, invece, l’interruzione è esterna allora il processo viene direttamente spostato nella coda dei processi pronti.
Questo alternarsi di stati continua fino a che tutto il processo è andato in esecuzione e, quindi, il programma è terminato.

Il gestore dell’unità di elaborazione deve anche preoccuparsi della sincronizzazione dei processi, risolvendo le eventuali contese per una risorsa critica o valutando le esigenze di processi che cooperano per un fine comune.
Nel caso di sistemi multiprocessore, a questa parte del sistema operativo spetta l’onere di gestire il flusso delle istruzioni dei processi ai vari processori e tutte le interazioni fra i vari processi che possono essere contemporaneamente in memoria o in esecuzione.

Gestione della memoria centrale

La memoria centrale, similmente all’unità di elaborazione, è una risorsa di sistema generalmente scarsa, da suddividere tra i vari processi in competizione. Il sistema operativo deve quindi occuparsi del modo in cui i singoli programmi occupano la memoria, in modo che ogni processo abbia l’illusione di una risorsa memoria virtualmente infinita.

In realtà, la memoria centrale è piuttosto deficitaria rispetto alle esigenze del processo o dei processi che vogliono andare in esecuzione; di conseguenza viene caricata in memoria centrale solo una parte di programma, mentre il resto permane nella memoria secondaria. Quando il processore richiede ulteriori istruzioni viene caricata un’altra parte del programma e così via fino ad esaurire il programma stesso. In questo modo gli utenti lavorano come se avessero in memoria centrale tutti i programmi da loro richiesti vedendo una memoria molto più grande di quella reale detta, appunto, memoria virtuale.

La dimensione della parte di programma da tenere in memoria centrale è un parametro critico: una piccola quantità richiederebbe molti trasferimenti dalla memoria secondaria per terminare l’esecuzione, mentre una grande quantità diminuirebbe il numero dei possibili processi presenti in memoria.
Oltre a dover risolvere il problema della memoria virtuale, il sistema operativo deve decidere come assegnare la memoria centrale ai vari processi; può scegliere infatti di suddividere la memoria in segmenti uguali alla parte di programma da caricare o in pagine di dimensione fissa più grandi di ogni singola parte di programma.
Ciascuna delle due tecniche presenta pregi e difetti: la prima, detta segmentazione, porta la memoria a mettere a disposizione segmenti sempre più piccoli arrivando a quella che si chiama frammentazione della memoria,  dove già risiedono in maniera ottimale alcuni programmi, la terminazione di alcuni e l’arrivo di altri porta a spezzettare la memoria in segmenti disponibili sempre più piccoli e, in definitiva, inutilizzabili.

La seconda tecnica, detta paginazione, evita la frammentazione ma, in modo insito, spreca memoria visto che assegna al processo sempre più memoria di quella necessaria.

Organizzazione della memoria secondaria

Per poter organizzare correttamente l’enorme mole di dati che può essere contenuta nella memoria di massa si ricorre ad una unità astratta di memorizzazione detta file; un file è una sequenza di caratteri che raggruppa una serie di dati omogenei, come le istruzioni di un programma o il testo di una relazione. L’insieme di programmi del sistema operativo che si occupano della gestione della memoria secondaria, sia dal punto di vista logico che fisico, viene quindi detto file system.
Scopo principale di un file system, dal punto di vista dell’utente, è quello di consentire una organizzazione logica dei vari file raggruppandoli secondo criteri stabiliti dall’utente stesso.

Un file system deve, inoltre, dare anche la possibilità di associare ad ogni file un nome, deve gestire la corrispondenza fra tale nome e la posizione fisica realmente occupata (in definitiva trasformare le operazioni logiche su file in operazioni fisiche su blocchi di memoria di massa), fornendo gli opportuni metodi per accedere ai dati in modo da ottimizzare i tempi di reperimento delle informazioni; deve, infine, gestire dei meccanismi di protezione della riservatezza dei dati e lo spazio libero derivante da file cancellati. Il modo in cui un file system organizza i vari documenti contenuti all’interno della memoria di massa può essere spiegato ricorrendo al paragone dei contenitori. Immaginiamo che la memoria secondaria sia un grande contenitore dove riporre in modo ordinato alcuni oggetti presenti in una camera (i file); all’interno di questo contenitore possono trovare posto altri contenitori più specifici: ad esempio, uno per i giochi, uno per gli oggetti di cancelleria e uno per gli arnesi del bricolage. A loro volta, tali contenitori potranno essere suddivisi in ulteriori scomparti (uno per i giochi di pezza, uno per quelli da tavolo, ecc.) e così via fino ad ottenere un livello di ordinamento sufficientemente preciso, dove riporre gli oggetti in modo che il loro reperimento sia agevole.
Il contenitore principale prende il nome di radice (root), mentre gli altri contenitori vengono detti directory e la loro dipendenza gerarchica può essere evidenziata da quella che si chiama struttura ad albero.

Il nome completo di un file (path name) deve quindi includere, oltre allo specifico nome del file, anche il percorso (path) necessario per raggiungerlo; un ipotetico file di nome Orsetto che si trovi nella directory Pezza sarà quindi individuato con la path name /Giochi/Pezza/Orsetto (la radice, in quanto unica, può essere omessa).
Il sistema operativo deve gestire anche quello che si chiama descrittore del file, l’insieme cioè delle informazioni relative alla posizione fisica e logica del documento e al suo modificarsi nel tempo.

Un tipico descrittore di file conterrà sia dati utili all’utente quali il nome del documento, le date di creazione e di ultima modifica, il tipo di file e le protezioni attive, sia informazioni utili e disponibili solo al sistema operativo quali posizione fisica nella memoria secondaria e descrizione della struttura interna.
Normalmente, per specificare il tipo di file, si fa uso di quella che viene detta estensione; l’estensione è un gruppo di caratteri (usualmente 3) che segue il nome del file ed è separato da questo da un punto. Tali caratteri specificano, tramite un codice, il contenuto del documento; file con estensione .COM o .EXE conterranno dei programmi, altri con estensione .DOC dei documenti generati dal programma Word e così via.

Driver

Altro delicato compito del sistema operativo è quello di gestire i rapporti fra l’unità di elaborazione e le varie periferiche in modo che l’utente che richiede una particolare azione su di un dispositivo periferico non sia costretto a conoscere l’esatto modo fisico in cui tale operazione viene eseguita.
Per far ciò esistono particolari programmi, detti device driver (o semplicemente driver), che racchiudono tutte le informazioni necessarie alla gestione di una particolare periferica; risulta evidente che per ogni dispositivo esterno deve esistere un particolare driver che ne conosca l’esatto funzionamento fisico. Ci saranno così un driver per gestire la tastiera, uno per gestire il monitor, uno per ogni tipo di stampante e così via.
Ognuno di questi programmi (che fanno parte del sistema operativo) deve inoltre gestire la comunicazione di segnali da e verso la periferica (buffering) e coordinare l’accesso alla risorsa, dato che quest’ultima potrebbe essere richiesta contemporaneamente da più processi.

Relativamente alla gestione dell’input/output, il sistema operativo deve assicurare una corretta gestione degli errori che si possono verificare nei vari dispositivi periferici (lettura impossibile su di un dischetto, carta terminata in una stampante, ecc.).
Nel caso di sistemi complessi, con molti processi in esecuzione in competizione per le risorse a disposizione, il sistema operativo deve vigilare sulla distribuzione delle risorse garantendo che non si possano verificare blocchi delle elaborazioni dovuti ad attese infinite.

Immaginiamo, ad esempio, la seguente situazione: il processo P1 sta utilizzando la risorsa R1 e viene posto in attesa per poter utilizzare anche la risorsa R2 che è momentaneamente impegnata; il processo P2, attuale utilizzatore della risorsa R2, viene invece posto in attesa visto che vuole servirsi della risorsa R1. Evidentemente i due processi sono irrimediabilmente bloccati visto che si è verificata una situazione di stallo non risolvibile facilmente, detta deadlock .

Gestione della coda di stampa

La gestione della comunicazione tra unità di elaborazione e stampante risulta essere particolarmente critica per la notevole diversità dei tempi di esecuzione delle operazioni proprie delle due unità. Senza un meccanismo di coordinamento di tale dialogo, l’unità di elaborazione potrebbe completare il suo compito (spedire una serie di dati alla stampante) in un periodo dell’ordine dei microsecondi ed attendere l’esecuzione effettiva dell’operazione (stampa su carta delle informazioni) per un tempo di 8 o 9 ordini di grandezza maggiore prima di poter continuare il suo compito.

Lo SPOOL (Simultaneous Peripheral Operation On Line) è la parte di sistema operativo che si occupa di ottimizzare tale comunicazione; l’unita di elaborazione, invece di inviare i dati alla periferica e controllare l’esecuzione dell’operazione, li registra sulla memoria secondaria in versione printer-ready e si volge ad eseguire altre operazioni.
Viene quindi generato un particolare processo che, nei momenti di inattività della unità di elaborazione (in background), si occupa di gestire il colloquio e l’eventuale coda di stampa che può venirsi a creare se le stampe richieste sono più di una.

 Nei sistemi multiutenti dotati di molti terminali o nelle reti locali esistono delle stampanti ad alte prestazioni disponibili a tutti dette stampanti di rete; in tal caso si usa dedicare un computer (spooler server) per eseguire i processi di spooling comandati dai vari utenti svincolando così i singoli terminali anche dalla gestione della stampa in background.

Interprete dei comandi

L’interprete dei comandi rappresenta lo strato superiore del sistema operativo, responsabile di ricevere ed interpretare i comandi formulati dall’utente; queste istruzioni richiamano funzioni del sistema operativo o mandano in esecuzione programmi dell’utente o di sistema. In definitiva l’interprete dei comandi si occupa di ricavare, dai comandi forniti dall’utente, le effettive richieste da inviare ai moduli sottostanti.
Nei sistemi ad interfaccia grafica, l’utente è aiutato nel colloquio con l’elaboratore grazie al fatto che i comandi sono comunicati o utilizzando semplici menù a tendina o attraverso la selezione, tramite mouse, di disegni che simboleggiano l’operazione richiesta detti icone.

Quando è invece presente una interfaccia testuale, il dialogo tra utente e macchina è più faticoso: l’utente deve infatti conoscere un linguaggio elementare, dotato di propria grammatica e sintassi, per poter impartire ordini all’elaboratore.
In tal caso la logica di funzionamento dell’interprete dei comandi è la seguente:

 - nella linea di comando l’interfaccia indica la disponibilità ad accettare comandi tramite una serie convenzionale di simboli detta prompt;

- l’utente digita il comando relativo all’operazione richiesta con gli eventuali parametri e opzioni;

- l’elaboratore verifica la presenza di errori sintattici: in caso positivo fornisce l’indicazione del tipo di errore, altrimenti esegue il comando richiesto;

 - quando l’esecuzione è terminata, riappare il prompt sulla linea di comando e l’elaboratore è pronto per ricevere un nuovo comando.