Introduzione
I primi elaboratori elettronici sono stati progettati tra il 1945 e il 1955, erano molto ingombranti, lentissimi e costosi. In questo periodo non vi erano ancora dei sistemi operativi per cui i programmi venivano scritti in codice binario ed eseguiti attraverso dei lettori di schede perforate.
Tra il 1955 e il 1965, grazie ai transistor gli elaboratori avevano acquisito una certa affidabilità e quindi iniziò la loro vendita in serie. Per eseguire dei programmi (job) un programma doveva essere scritto prima su carta, poi veniva trasferito su schede, poi veniva caricato nel computer ed infine bisognava attendere il termine dell’esecuzione e la stampa del risultato (operazione molto costosa in termini di tempo).
Successivamente nacquero i sistemi batch (a lotti) che consentivano di dividere i tre lavori su macchine distinte (lettura dei dati, calcolo, stampa). I sistemi operativi tipici per questi elaboratori, programmati in FORTRAN e Assembler, erano il FMS e IBSYS. Vennero introdotte delle politiche di ordinamento dei job.
Nel 1964 IBM creò il primo elaboratore con una netta distinzione tra architettura hardware e implementazione che consentiva la multiprogrammazione, ovvero dava la possibilità di avere più programmi in memoria contemporaneamente (grazie all’utilizzo di hardware specializzato per difendere i programmi dalle reciproche interferenze). Quando un job aveva bisogno di effettuare un’operazione di I/O allora veniva sospeso dal SO per far girare un altro job.
Anni '60 e lo sviluppo del timesharing
Negli anni '60 nacque il timesharing che diede la possibilità all’utente di comunicare al calcolatore con la tastiera (I) e il monitor (O). Successivamente venne sviluppato il PDP-1 che montava il sistema operativo UNIX progettato da Thompson, Richie e Mclloroy e successivamente Linux sviluppato dallo studente finlandese Linux Torvalds.
L'era dei personal computer
Solo negli anni '80 grazie alla tecnologia LSI furono sviluppati dei chip integrati facendo sorgere così l’era dei personal computer. I primi SO per i primi PC furono (CP/M80 ed MS-DOS). La Xerox lanciò il primo SO con interfaccia grafica e la Apple nel 1984 lanciò il Mac OS; successivamente (1985) la Microsoft commercializzò Windows. Tutte le varie innovazioni hanno portato allo sviluppo di SO per diversi tipi di architetture: Cellulari, Smartphone, PDA.
Funzionalità dei sistemi operativi
Un Sistema Operativo è un insieme di programmi che interagiscono da intermediari tra l’utente e le risorse di un sistema di calcolo (o computer). Gli scopi di un sistema operativo sono:
- Eseguire i programmi degli utenti
- Rendere un computer semplice da utilizzare
- Sfruttare in maniera efficiente le risorse hardware
Un Sistema Operativo fornisce un ambiente nel quale i programmi possono essere eseguiti in modo utile e produttivo. Un sistema di calcolo è composto da: Hardware, Sistema Operativo, Programmi Applicativi, Utenti.
Assegnazione delle risorse e controllo
Il Sistema Operativo è un assegnatore di risorse: Gestisce tutte le risorse, Decide la risoluzione dei conflitti per un uso efficiente e sicuro delle risorse di calcolo. Il Sistema Operativo è un programma di controllo: Controlla l’esecuzione dei programmi per prevenire possibili errori o evitare l’uso improprio delle risorse di calcolo. Esistono svariate definizioni di SO. Il SO è l’unico programma continuamente in esecuzione chiamato kernel, in particolare, nei SO per dispositivi mobili al kernel è associato un middleware che mette a disposizione dei servizi aggiuntivi per gli sviluppatori.
Componenti hardware di un sistema di calcolo
All’accensione, un elaboratore, ha bisogno di un programma di avviamento chiamato bootstrap program, il suo scopo è duplice: inizializzare ogni aspetto del sistema (registri della CPU, controllori dei diversi dispositivi) e caricare nella memoria il kernel del Sistema Operativo. I processi di sistema restano in esecuzione per tutto il tempo in cui è in esecuzione il kernel (tali processi una volta avviati si mettono in attesa di un evento I/O di ulteriori istruzioni).
I sistemi di calcolo sono composti da una o più CPU, alcuni controllori di dispositivi I/O connessi attraverso un bus alla memoria che gli consente di poter lavorare in squadra per ottenere cicli di accesso alla memoria. Ogni dispositivo di I/O ha un controllore che gestisce le operazioni del dispositivo attraverso un buffer locale; la CPU per poter usare questi dispositivi di I/O sposta dati dalla memoria di sistema alla memoria interna del controllore (buffer locale). Ogni controllore possiede un driver del dispositivo che funge da interfaccia con il resto del sistema.
Interruzioni e gestione della memoria
Ogni qualvolta che un dispositivo riceve nuovi dati, causa un interrupt che richiama l’attenzione dalla CPU che trasferisce il controllo a una routine di servizio. Normalmente esiste una sequenza di indirizzi, detta vettore delle interruzioni, che punta alle diverse routine di servizio. Il Sistema Operativo deve salvare l’indirizzo dell’istruzione appena interrotta per poterla continuare a eseguire successivamente. Ulteriori interrupt sono normalmente disabilitati per non causare incoerenze nella normale esecuzione dell’interrupt corrente. Una eccezione o trap è normalmente un interrupt causato dal software da un errore oppure dall’utente (una divisione per zero per cui possiamo dire che un sistema operativo è quindi anche un gestore di interrupt).
L’accesso diretto alla memoria (DMA) consente ad un dispositivo di trasmettere i dati alla memoria ad una velocità prossima a quella di memoria. Il controllore del dispositivo trasferisce blocchi di dati dal proprio buffer locale alla memoria principale senza nessun intervento della CPU.
Memoria principale e memoria secondaria
La memoria principale viene utilizzata prevalentemente dalla CPU tramite accesso diretto per le istruzioni e i dati. La memoria secondaria è un’estensione della memoria principale per la memorizzazione di grandi quantità di dati in maniera non volatile.
Cache e gerarchia di memorizzazione
Dischi magnetici forniscono una memoria non-volatile di grandissime dimensioni. Il controllore dei dischi determina la logica di interazione tra il dispositivo ed il computer. I dispositivi di memorizzazione sono organizzati in gerarchie basate su criteri di velocità, costi, e volatilità.
La cache è una memoria intermedia veloce che permette di aumentare l’efficienza negli accessi ai dati. Le cache sono presenti solitamente su tutti i livelli e inoltre la memoria principale può essere vista come una enorme cache per la memoria secondaria. Il meccanismo di caching è presente in diversi livelli di un sistema di calcolo che copia le informazioni di uso frequente da una memoria lenta ad una con accesso più veloce. Se queste informazioni non sono già presenti, allora i dati sono copiati nella cache e utilizzati dalla stessa; altrimenti, se sono presenti già le informazioni all’interno di una memoria veloce, vengono utilizzate dalla cache.
Sistemi monoprocessore e multiprocessore
I sistemi monoprocessore (MAINFRAME, PDA), un solo processore gestisce l’esecuzione delle applicazioni degli utenti mentre altri processori di potenza computazionale diversa vengono utilizzati per altri compiti come: il controllo dei dischi, scheda grafica, gestione della tastiera.
I sistemi multiprocessore coinvolgono più CPU collegate tra di loro attraverso un bus che consente di eseguire più applicazioni contemporaneamente, portando a una maggiore produttività e affidabilità, e una maggiore tolleranza ai guasti nel caso in cui una delle unità di calcolo sia fuori uso. Essi possono essere asimmetrici o simmetrici a seconda se ogni unità elabora un compito specifico o ha le stesse capacità delle altre.
Cluster e parallelizzazione
I cluster sono sistemi multiprocessore che consentono di collegare più calcolatori (nodi) tra di loro per mezzo di una LAN o WAN che consente di poter condividere la propria memoria di massa tra di loro. Possono avere due strutture: quando un calcolatore in attesa attiva monitora un altro calcolatore e se questo presenta un problema allora sarà pronto a prendere il suo posto, quando i due calcolatori eseguono le applicazioni contemporaneamente controllandosi reciprocamente. Essi utilizzano le tecniche della parallelizzazione.
Multiprogrammazione e time-sharing
La multiprogrammazione consente di aumentare l’efficienza della CPU e dei dispositivi I/O poiché organizza i lavori (jobs) in modo tale da tenere sempre la CPU occupata. Una parte di job totali del sistema sono caricati in memoria, mentre i restanti vengono ubicati in un’area del disco detta job pool. I job vengono selezionati attraverso un algoritmo di scheduling e quando uno di essi è in attesa di I/O il Sistema Operativo può passare all’esecuzione di un altro job.
Il time sharing (multitasking) è una estensione logica della multiprogrammazione, la CPU esegue per un istante un job per poi passare all’esecuzione di un altro job e così via. Ogni utente ha almeno un programma in esecuzione chiamato processo. Se diversi job sono pronti per essere trasferiti in memoria centrale ma lo spazio non è sufficiente allora il sistema fa una selezione chiamata job scheduling.
Gestione della memoria e dei processi
Se diversi job sono pronti per essere eseguiti nello stesso istante allora la CPU decide chi avrà la priorità attraverso opportuni algoritmi di scheduling della CPU. Se un processo non risiede completamente in memoria allora lo swapping lo trasferisce verso una memoria secondaria. La memoria virtuale permette l’esecuzione di processi che non risiedono completamente in memoria.
I moderni SO sono guidati da interruzioni (richieste di intervento o errori generati dal software). In caso di eccezioni è necessario non arrecare danni ad altri processi. Il SO deve prepararsi a gestire le varie eccezioni in due modalità: modalità utente o modalità di sistema (kernel). È possibile distinguere in che tipo di esecuzione si trova il SO grazie ad un bit di modalità fornito dall’hardware (le system call sono eseguite in modalità kernel).
Processi e thread
Un processo è una entità attiva mentre un programma è una entità passiva. Nel proprio ciclo di vita, il processo ha bisogno di alcune risorse come la CPU, memoria, I/O, files, che al termine dell’esecuzione lo stesso rilascerà completamente. Nei processi single-threaded il contatore di programma tiene conto della prossima istruzione da eseguire mentre, nei processi multi-threaded ogni thread ha un suo contatore di programma.
Quindi il SO attraverso il gestore di processi, ha il compito di: schedulare processi e thread sulla CPU, creare e rimuovere i processi degli utenti e di sistema, sospendere e riprendere l’esecuzione dei processi, fornire meccanismi per la sincronizzazione, la comunicazione dei processi e per la gestione dei deadlock. Il gestore della memoria si occupa di caricare in memoria le istruzioni del processo per poter avviare la sua esecuzione e in particolare di cosa tenere allocato in memoria in ogni istante in modo tale da rendere più efficiente l’utilizzo della CPU.
Gestione dei file e della memoria di massa
Il SO utilizza il concetto di file per distinguere le proprietà fisiche dell’unità di memorizzazione. Ogni device ha caratteristiche diverse (velocità, capacità, velocità di trasferimento). Per gestire correttamente la memoria il SO deve essere in grado di organizzare i file in directory e controllare gli accessi per verificare chi può accedere a determinati file. Normalmente la memoria di massa è adoperata per memorizzare informazioni che non possono essere allocate in memoria o che devono essere mantenute per lunghi periodi di tempo. Le velocità di un SO sono scaturite anche da come viene gestita la memoria di massa (gestione dello spazio libero, allocazione dello stesso e scheduling su disco). Inoltre, deve nascondere la complessità dell’hardware agli utenti; l’accesso alle risorse da parte dei processi e degli utenti deve avvenire con massima protezione e sicurezza in maniera tale da poter fronteggiare eventuali attacchi interni ed esterni.
Strutture dei sistemi operativi
Un Sistema Operativo è uno dei compiti più complessi per l’ingegneria del software. Deve fornire un insieme di servizi utili all’utente: interfaccia con l’utente, esecuzione di un programma (corretta esecuzione anche in presenza di errori), operazioni di I/O, gestione del file system. Inoltre, deve consentire la comunicazione tra i diversi processi, sullo stesso PC o tra PC condivisi in rete. Può avvenire mediante una memoria condivisa o attraverso il message passing. Deve essere in grado di rilevare errori che possono presentarsi sulla CPU, in memoria o nei programmi utenti e di gestirli opportunamente assicurando la corretta continuazione.
Inoltre, per poter assicurare il corretto utilizzo del sistema mediante le risorse condivise è opportuno tenere traccia degli utenti e della quantità di risorse che si possono utilizzare; la corretta assegnazione delle risorse ad ogni processo ed infine bisogna garantire una certa protezione e sicurezza, le informazioni devono poter essere archiviate secondo politiche di protezione e vi devono essere delle precauzioni ovunque.
Interazione e comando
È possibile comunicare con un SO mediante l’interprete dei comandi o un’interfaccia grafica. Solitamente l’utente ha la possibilità di scegliere diversi interpreti di comando chiamate shell che preleva il comando impartito e lo esegue (può essere un comando implementato internamente alla shell o un programma esterno da avviare .exe). il desktop rappresenta l’interfaccia grafica, inventata dalla XEROX, copiata dalla Apple Computer. Molti sistemi includono sia una CLI che una GUI come il prompt per Windows.
System call e API
Le system call rappresentano l’interfaccia ai servizi forniti dal SO, scritte in un linguaggio ad alto livello (C/C++), accessibili attraverso un API e quasi mai direttamente (WIN32). Ad ogni system call è associato un numero che viene memorizzato in una tabella con tutti gli indici delle system call. Il programmatore ha bisogno di sapere solo l’interfaccia delle system call, ovvero deve solo capire come funziona l’API. Inoltre, al programmatore viene rilasciata una documentazione che descrive le varie system call principali. Ad esempio, in C++ richiama la system write.call.
Per chiamare una system call è opportuno passargli dei parametri che possono essere contenuti all’interno dei registri; in un blocco in memoria, passando l’indirizzo del blocco come parametro oppure i parametri sono passati attraverso lo stack. Le chiamate possono essere per il controllo dei processi, gestione dei file o dei dispositivi, gestione delle informazioni, comunicazione e protezione.
Comunicazione tra i processi
I processi possono comunicare mediante lo scambio di messaggi (creazione e chiusura di una connessione), invio e ricezione oppure mediante memoria condivisa accedendo ad aree di memoria di altri processi. I processi vengono protetti poiché esistono permessi di accesso alle risorse per cui vi sono delle politiche affinché i permessi vengano assegnati in maniera corretta. L’utente solitamente non è mai al corrente delle chiamate di sistema che vengono effettuate all’interno del sistema ma tiene a mente solo dell’esecuzione delle applicazioni (ogni applicazione utilizza sempre le stesse system call).
Progettazione di un sistema operativo
Per poter progettare un SO è opportuno seguire delle linee guida: bisogna tenere a mente dell’hardware ed il tipo di sistema a disposizione; gli utenti pretendono che un Sistema sia facile, veloce, affidabile e sicuro; il sistema deve essere facile da progettare, da manutenere, affidabile e sicuro. È opportuno basarsi su due principi, come farlo (meccanismi) cosa deve essere fatto (criteri). Questa separazione è molto importante soprattutto nel futuro nel caso in cui si vogliano cambiare i criteri.
Per poter tenere a bada lo stato di vita di un processo all’interno della CPU viene utilizzato un timer (meccanismo) che viene impostato in maniera opportuna (criteri) per poter tenere un determinato tempo un processo in esecuzione sulla CPU, facendolo morire quando il tempo supera quello del timer.
Strutture semplici e stratificate
I primi SO (struttura semplice) avevano come unico scopo quello di fornire quante più funzioni possibili nel modo più semplice e diretto cercando di mantenere la complessità di sistema al minimo; molte operazioni venivano effettuate accedendo direttamente all’hardware senza l’utilizzo delle system call e questo era fonte potenziale di errori. UNIX era diviso in programmi di sistema e kernel, che forniva i servizi per il file system, la gestione della memoria ecc.
Il sistema operativo (metodo stratificato) viene diviso in strati dove quello più basso è l’hardware e quello più alto è l’interfaccia utente e attraverso la modularità ogni strato può utilizzare le funzioni o i servizi degli strati inferiori.
Microkernel e modularità
Per quanto riguarda l’architettura a microkernel, molte funzioni sono tirate fuori dal kernel ed eseguite direttamente in modalità utente. La comunicazione avviene attraverso il message passing. Questa architettura comporta vantaggi e svantaggi di cui: estendibile, maggiore affidabilità e sicurezza; lo svantaggio principale sono i cali di prestazioni indotti dal sovraccarico della comunicazione.
Ora viene utilizzata un’architettura che consiste nel separare i vari moduli che comunicano con una interfaccia nota e ogni modulo è caricato dal kernel quando è necessario.
Macchine virtuali
Una macchina virtuale ha come scopo quello di trattare l’hardware ed il SO come se fosse tutto hardware fornendo una interfaccia identica all’hardware sottostante e inoltre il SO crea uno o più hardware virtuali su cui è possibile installare un SO fornendo la completa protezione delle risorse, isolando ogni macchina virtuale dalle altre (non c’è diretta condivisione delle risorse).
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.