Appunti di Tecnologie e progettazione di Sistemi Informatici e di Telecomuniazioni terzo anno quarto anno Rappresentazione delle informazioni Modello di un processo di comunicazione La comunicazione può essere definita come il procedimento per trasmettere e ricevere messaggi. Gli elementi della comunicazione sono quindi la sorgente fonte del messaggio, il destinatario che riceve il messaggio ed il canale, cioè il mezzo attraverso cui il messaggio passa dalla sorgente al destinatario. messaggio Il processo di comunicazione può essere sincrono, se la sorgente ed il destinatario utilizzano lo stesso clock per la trasmissione, asincrona se sorgente e destinatario non hanno un orologio comune per cui i messaggi sono preceduti da caratteri di controllo che servono alla sincronizzazione del trasmettitore e del ricevitore. La comunicazione può essere : Unicast: c’è solo un destinatario Multicast: i destinatari sono un gruppo specifico ( televisione a pagamento ) Broadcast : i destinatari sono tutti ( televisione ) Per messaggio intendiamo il fatto o la notizia, che ha un significato oggettivo per informazione ciò che ha un valore soggettivo. Per meglio comprendere un processo di comunicazione, possiamo usufruire del modello di Shannon: Sorgente Fonte del messaggio CS Codifica di sorgente : si sceglie un codice e si codifica il messaggio, dalla parte del destinatario vi è la DS ( decodifica di sorgente ) che opera il processo inverso. CC Codifica di canale : Il messaggio viene inviato con ridondanza, ovvero, vengono aggiunti dei bit al messaggio, per consentire al destinatario di rilevare ed eventualmente correggere errori, dalla parte del destinatario vi è la DC ( decodifica di canale ) che opera il processo inverso. CL Codifica di linea Il messaggio per essere inviato deve passare da una forma simbolica ad una forma di segnale elettrico per adattarlo al tipo di canale, dalla parte del destinatario vi è la DL ( decodifica di linea ) che opera il processo inverso. TX Trasmettitore Il trasmettitore manderà il messaggio sul canale . Canale Il messaggio arrivato sul canale potrebbe incontrare del noise, cioè rumore che può alterare il significato del messaggio . RX Ricevitore : Il messaggio arriva al ricevitore. DL Decodifica di linea Il messaggio è ritrasformato da segnale elettronico a messaggio in simboli. DC Decodifica del canale Viene controllata la ridondanza inviata nel messaggio, per verificare l’esistenza di eventuali errori. DS Decodifica del messaggio Il messaggio viene decodificato. 2 Destinatario Riceve il messaggio . Tipologia dell’informazione Le principali categorie di informazioni che tratta un’elabotatore sono numeri e operazioni su di essi; dati alfanumerici immagini filmati suoni Con il termine di multimedialità si intende la confluenza di più mezzi di comunicazione ( suoni immagini, filmati, testo ) su uno stesso strumento informativo ( ipertesto ) I protocolli Il protocollo è l’insieme delle regole che determinano le modalità con cui il destinatario si scambiano dati. il mittente e Trasmissione parallela, seriale sincrona e seriale asincrona Abbiamo una trasmissione parallela se i bit che rappresentano i caratteri del messaggio da trasmettere vengono inviati contemporaneamente su più linee. Poiché si usa una linea per bit non è possibile usare per la trasmissione la rete telefonica esistente. La rete telefonica, infatti, è stata realizzata per trasmettere la voce su un solo filo per cui possiamo trasmettere un bit per volta con una modalità di trasmissione seriale. La trasmissione parallela è utilizzata per collegamenti di breve distanza, anche se a parità di condizioni fornisce una maggiore velocità ( nell’ esempio in figura 8 volte ). Rumore In un sistema di comunicazione insieme al segnale utile è presente anche un segnale completamente privo di informazione e che influisce sulla corretta ricezione del messaggio. Parleremo di rumore generato dagli stessi apparati elettronici impiegati per la trasmissione; è sempre presente ; disturbo del tutto casuale determinato da vari fattori quali onde elettromagnetiche provenienti da eventi naturali ( ad esempio i fulmini ) o artificiali quali accensioni di motori, interferenze fra circuiti ecc. ecc.. 3 Digitale o Binario Analogico e digitale In natura tutte le grandezze fisiche seguono un andamento continuo e la loro rappresentazione grafica spesso, è funzione continua del tempo. Con segnale analogico si indica la rappresentazone di una grandezza fisica tramite una grandezza analoga che la desecrive completamente. La temperatura, la pressione sono grandezze fisiche analogiche e vengono rappresentate graficamente in funzione del tempo. La rappresentazione numerica di una grandezza analogica è quasi sempre , istante per istante, rappresentata da un numero reale di precisione infinita ( dopo la virgola infinite cifre ). Di un seganle analogico ( quindi che può assumere valori infiniti ) è possibile effettuare misurazioni in particolari istanti di tempo ( campionamento ) del segnale. In questo modo si passa da un insieme infinito di valori ad un insieme discreto. Se misuriamo il segnale precedente a intervalli di tempo prefissati e manteniamo il valore letto per tutto l’intervallo di tempo fino alla lettura successiva, otteninamo un segnale ad onda quadra ottenendo così la digitalizzazione del segnale cioè abbiamo trasformato un segnale analogico in uno digitale. 4 Perché un PC possa elaborare dati analogici è necessario prima un convertitore analogico / digitale quindi l’il personal computer per elaborare il segnale proveniente dal converitore ed infine un convertitore digitale / analogico per ottenere nuovamente in segnale analogico elaborato. I vantaggi dell’elaborazione digitale rispetto a quella analogica sono: i dati digitali possono essere elaborati da un computer; e’ possibile integrare diverse fonti ad esempio musica, parole, immagini in un unico supporto come ad esempio una chiavetta USB; attraverso internet si possono condividere le informazioni.; il sistema digitale e semplice, poco costoso e insensibile ai disturbi. Sistemi di numerazione posizionali Un sistema di numerazione è l’insieme dei simboli e delle regole che permettono di esprimere i numeri. Le cifre sono i simboli usati in un sistema di numerazione. Si dice base di un sistema di numerazione il numero di simboli che si usano per scrivere i numeri. Un sistema di numerazione si dice posizionale se il valore di una cifra dipende dalla posizione che essa occupa nella scrittura del numero. Nel numero decimale 111 è evidente che l’ uno più a destra ha un “peso” minore rispetto a quello centrale e ancor di più rispetto a quello più a sinistra. Infatti il numero si può rappresentare secondo la scrittura polinomiale per cui ogni cifra a partire da destra viene moltiplicata per la base elevata ad una potenza che rappresenta la posizione della cifra cominciando a contare da 0. 111 = 1 * 102 + 1 * 101 * 1 * 100 Sistema decimale Nel sistema di numerazione decimale si usano i dieci simboli: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Esempio. 7408 = 7 * 10 + 4 * 10 + 0 * 10 + 8 * 10 Un qualsiasi numero reale può essere scritto nella forma polinomiale Esempio 408,73 = 4 *10 + 0 * 10 + 8 * 10 + 7 * 10-1 + 3 * 10-2 3 2 2 1 1 0 0 Sistema binario Nel sistema di numerazione binario si usano due simboli, solitamente indicati con 0 e 1. Conversione di un numero intero da base 2 a 10. Per trasformare un numero dalla base 2 alla base 10 si scrive il numero nella forma polinomiale e si calcola il valore dell’espressione ottenuta considerando tutti i numeri come scritti in base 10. Esempio 1001 = 1*23 + 0*22 + 0*21 + 1*20 = 1*8 + 0*4 + 0*2 + 1*1 = 8 + 1 = 9 Più rapidamente basta porre i pesi sopra ogni cifra a 1 e poi sommarli direttamente. Ad esempio (2) 64 16 2 1 1 0 1 0 0 1 12 = 64 + 16 + 2 + 1 = 83 5 Conversione di un numero intero da base 10 a 2. Per trasformare un numero da base 10 a base 2 si eseguono le divisioni intere successive del numero dato per 2, e dei quozienti mano a mano ottenuti sempre dividendo per 2, fino ad avere come quoziente 0: il numero scritto nella base 2 si ottiene riportando i resti delle divisioni presi in senso inverso ( dall’ultimo resto al primo ). Esempio. Convertire in base 2 il numero 55. quoziente resto 55 : 2 1 ^ 27 : 2 1 | 13 : 2 1 | 6 :2 0 | 3 :2 1 | 1 :2 1 | 0 Quindi 55 = 110111(2) Conversione di un numero reale da base 10 a 2. Per convertire un numero reale dobbiamo applicare due algoritmi: il primo per la parte intera in secondo per la parte frazionaria. Per la parte intera l’algoritmo è quello delle divisioni successive visto nel paragrafo precedente, per la parte frazionaria è quello delle moltiplicazioni successive che possiamo enunciare così: 1. si moltiplica per 2 la parte frazionaria; 2. dal risultato si toglie la parte intera e si ritorna al punto 1. L’algoritmo ha termine quando la parte frazionaria diventa 0 o si è raggiunto il numero di cifre desiderato. Esempio Convertire in base 2 il numero 0,6572 frazione 0,6572 0,3144 0,6288 0,2576 0,5152 * * * * * 2 2 2 2 2 prodotto parte intera 1,3144 0,6288 1,2576 0,5152 1,0394 1 0 1 0 1 Il numero decimale 0,6572 = 0 , 101012 ottenuto prendendo le cifre della colonna parte intera. Addizione nel sistema binario. Si può applicare lo stesso algoritmo dell’addizione nel sistema decimale, occorre tenere presente che: 0+0 0 0+1 1 1+0 1 1+1 10 0 con riporto di 1 1 1100+ 6 110= -------------1 0 0 1 02 Sottrazione nel sistema binario. Si può applicare lo stesso algoritmo della sottrazione nel sistema decimale, occorre tenere presente che : 0-0 0 1-1 0 1-0 1 occorre ‘prendere 0-1 in prestito’ una unità dalla cifra di ordine superiore Quindi 111102− 10012=101012 Moltiplicazione nel sistema binario. Si può applicare lo stesso algoritmo che si usa per il sistema decimale, tenendo presente che : 0*0 0 0*1 0 1*0 0 1*1 1 Esempio. 1112* 1012 = 1000112 Divisione nel sistema binario. Si può eseguire in modo analogo alla divisione nel sistema decimale. Esempio. 110102 : 1012 =112 con resto 1 Il sistema binario nell’informatica L’unità di misura “naturale” dell’informazione per l’elaboratore è il BIT , ovvero una cifra o una variabile binaria. Poiché con un solo bit è possibile rappresentare solo due elementi ( associandoli ai due stati 0 e 1 ) dell’alfabeto esterno, per rappresentare un numero maggiore di informazioni si devono necessariamente impiegare codici binari costruiti con sequenze (ordinate) di bit. Il termine ordinato 7 indica che, se si scambiano i valori (0 e 1) nella sequenza, si ottiene nella decodifica una informazione diversa. Una sequenza di n bit, con n numero intero, può assumere 2n configurazioni diverse e quindi rappresentare altrettante informazioni. Ad esempio con 8 bit potremo avere 28 = 256 diverse configurazioni e quindi potremo rappresentare tutti i numeri da 0 a 255. 0 1 2 3 . . . 254 255 00000000 00000001 00000010 00000011 ……. ……. ……. 11111110 11111111 Essendo il singolo bit un’unità troppo piccola per rappresentare l’informazione, si introducono i concetti illustrati nella tabella seguente: semibyte o nibble sequenza ordinata di 4 bit. Byte o ottetto (octet) sequenza ordinata di 8 bit. parola ( word ) sequenza ordinata di 2, 4 o 8 Byte a seconda del tipo di CPU e di memoria centrale del calcolatore. Nelle applicazioni, l’unità di misura bit è rappresentata con la lettera b , mentre il byte con B . Con un semibyte, ad esempio, si possono rappresentare 24 = 16 simboli elementari diversi dell’alfabeto. Attualmente, i sistemi di elaborazione devono gestire enormi quantità di informazioni codificate in binario. La memoria utilizzata per codificare una pagina di testo è di qualche migliaio di byte, quella usata per una immagine può raggiungere il milione di byte, mentre un lungo filmato può richiedere miliardi di byte per essere memorizzato. Si avverte la necessità, come nel sistema metrico decimale, di utilizzare dei simboli per rappresentare i multipli delle grandezze elementari; nella terminologia informatica sono stati quindi adottati gli stessi simboli del sistema decimale, ma visto che la misurazione della memoria ha come sua base principale il 2, il loro significato è leggermente diverso. La tabella che segue riassume i simboli e i valori dei multipli più usati: multipl sigla valore o Kilo k 210 = 1024 Mega M 220 = 10242 = 1024 k Giga G 230 = 10243 = 1024 M Tera T 240 = 10244 = 1024 G 8 Nel campo informatico, per motivi esclusivamente pratici, si preferisce rappresentare sia i numeri negativi, sia i numeri decimali utilizzando esclusivamente 0 e 1, senza cioè usare i simboli +, - né la virgola per i numeri decimali. Sistema esadecimale Nel sistema esadecimale, abbreviato con esa o hex, si utilizzano i seguenti 16 simboli: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F Che corrispondono in decimale 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 Conversione da sistema esadecimale a decimale. Si scrive il numero in forma polinomiale e si eseguono le operazioni, il numero ottenuto è in forma decimale. Esempio. 2 1 0 AF316 = A*16 + F*16 + 3*16 = 10*256 + 15*16 + 3*1 = 2560 + 240 + 3 = 2803 Conversione da decimale a esadecimale. Si divide il numero per 16 ripetutamente fino a ottenere quoziente 0, quindi si riportano i resti in ordine inverso. Esempio. Convertire 1204 nel sistema esadecimale quoziente resto 1204 : 16 = 75 4 = 416; 75 : 16 = 4 11= B16; 4 : 16 = 0 4 = 416; 0 poiché il quoziente è 0 si interrompe la serie di division1. Quindi 1204 = 4B416 Conversione di un numero intero dalla base 2 alla base 16 L’algoritmo è : dividere il numero binario in gruppi di 4 cifre partendo da destra verso sinistra sostituire ad ogni gruppo la corrispondente cifra esadecimale Esempio. Convertire 10001111102 nel sistema esadecimale 10 0011 11102 2 3 14 = E16 Quindi 10001111102 = 23E16 Conversione di un numero intero dalla base 16 alla base 2 Basta sostituire ad ogni cifra esadecimale il corrispondente valore binario a 4 bit Esempio. Convertire 3FD16 in base 2 3 F D 0011 1111 1101 Quindi 3FD16 = 0011 1111 1101 Immagini, suoni e filmati Queste informazioni sono di natura continua e devono essere trasformate in binario. Questa trasformazione comporta una certa perdita di dati e a seconda della tecnica usata otteniamo risulati più o meno fedeli all’originale. Rappresentazione delle immagini in binario Esistono diverse tecniche usate per la memorizzazione e l’elaborazione di un’immagine. 9 Immagini bianco nero senza grigi Cominciamo col considerare un’immagine in bianco e nero, senza alcuna ombreggiatura o livello di chiaroscuro. Suddividiamo l’immagine tramite una griglia formata da righe orizzontali e verticali a distanza costante a formare quindi quadratini tutti uguali. Ogni quadratino prende il nome di PIXEL (picture element): immagine = griglia di pixel ad ogni pixel viene fatto corrispondere : 0 per la codifica del bianco quando questo colore è predominante nel pixel; 1 per la codifica del nero se questo colore è predominante nel pixel. Per convenzione la griglia di pixel viene ordinata dal basso verso l’alto e da sinistra verso destra. Con questa convenzione otteniamo una rappresentazione della figura di esempio di questo tipo: il che significa che l’immagine di partenza viene rappresentata dalla stringa binaria ottenuta leggendo la tabella per righe partendo dalla prima. Se riconvertiamo la stringa dell’ mmagine sostituendo a ogni 1 un pixel nero e a ogni 0 un pixel bianco otteniamo: L’immagine di partenza è “appossimata”. Quindi come già accennato la digitalizzazioner comporta una perdita di qualità dell’immagine di partenza . Per migliorare la qualità esistono tecniche per “ingannare l’occhio” e limitare l’effetto a scalini della visualizzazione a pixel. 10 La rappresentazione sarà più fedele aumentando il numero di pixel. Un’immagine digitale può essere caratterizzata mediante due dimensioni: Risoluzione: il numero di pixel che la costituiscono l’immagine espressi in termini di larghezza per altezza Profondità: il numero di bit che vengono usati per rappresentare un singolo pixel dell’immagine, legata al numero di colori rappresentabili. Nella figura seguente lo stesso cerchi con risoluzioni diverse Immagini bianco nero con chiaroscuro Per codificare diversi livelli di grigio con una rappresentazione binaria devo far corrispondere a ogni livello di grigio una certa sequenza di bit Ad esempio: con 4 bit posso rappresentare 24 = 16 livelli di grigio con 8 bit posso rappresentare 28 = 256 livelli di grigio quindi nel primo caso per “descrivere” un pixel abbiamo bisogno di 4 bit, nel secondo di 8. Considerando l’immagine del cerchio di risoluzione 58 x 56 = 3248 pixel, la grandezza dell’immagine sarà 3248 x4 =12992 bit / 8 = 1624 Byte nel caso di profondità 4, 3248 nel caso di profondità 8. Immagini a colori Per il colore si ha un sistema analogo alla immagine bianco nero con scala di grigi Si individuare un certo numero di sfumature di colore differenti Si codifica ogni sfumatura di colore mediante un’opportuna sequenza di bit Le sfumature di colore vengono individuate attraverso l’uso del sistema RGB (Red, Green, Blu) secondo cui qualsiasi colore può essere rappresentato componendo Rosso Verde e Blu (colori primari) Invece che di partire da tanti colori e di rappresentarne diverse sfumature, possiamo rappresentare molte sfumature a partire dai 3 colori primari, tutti i possibili colori ottenuti dalla loro combinazione. La codifica RGB prevede che : A ogni pixel associo una certa combinazione dei 3 gradazioni dei colori primari La gradazione per ogni colore primario è rappresentata mediante un certo numero di bit Esempio: – con 8 bit per colore primario ottengo 256 possibili gradazioni per colore primario 11 Quindi 256 × 256 × 256 = 16.777.216 colori diversi ( tutte le possibili combinazioni di tutte le possibili gradazioni di Red, Green e Blu ) – La codifica di 1 pixel richiede in questo caso 3 byte, quindi avremo una profondità dell’immagine a 24 bit R 0 1 2 3 ….. …. 253 254 255 G 0 1 2 3 ….. …. 253 254 255 B 0 1 2 3 ….. …. 253 254 255 Considerando la prima colonna lo 0 rappresenta il colore primario Rosso 1,2,3…., 255 le varie sfumature del rosso. Così per le colonne del green e del blu. Questo tipo di codifica dei pixel viene detta codifica bitmap ed il numero di bit utilizzati per codificare ogni pixel è detta profondità del colore. Ottimizzazione della dimensione dell’ immagine tramite compressione Se ad esempio abbiamo un’immagine con risoluzione di 640 x 480 pixel e profondita 24 bit la sua dimensione sarà : 640 x 480 x 24 = 7.372.800 bit = 921.600 Byte La codifica bitmap è estremamente costosa in termini di occupazione di spazio di memoria . Maggiore è la qualità, maggiore sarà lo spazio occupato. Quindi utilizzando una simile codifica trasmettere in rete l’immagine sarebbe oneroso in termini di tempo. In sintesi maggiore qualità dell’immagine -> maggiore spazio occupato -> maggiore tempo di trasmissione Esempio Un’immagine bitmap con risoluzione 400 ×400 e profondità 24 ha dimensione di circa 480 KB. Ci metterebbe circa 70 secondi a essere visualizzata per intero in una pagina web, data una connessione a Internet via modem telefonico, che consente una velocità massima di scaricamento dei dati di 56kb per secondo La soluzione a questo inconveniente è ottimizzarla cioè ridurne le dimensioni ( comprimere l’immagine ) quanto più possibile mantenendo la qualità dell’immagine a livelli accettabili Vi sono due metodologie di compressione : lossless : senza perdita di informazione; lossy : con perdita di informazione ed è il più utilizzato. Il primo metodo si basa di riconoscere nella rappresentazione binaria dell’immagine di sequenze di bit che si ripetono con maggiore frequenza e nella loro sostituzione con sequenze più corte, in modo da rispormiare spazio. Questo tipo di codifica viene utilizzata per i formati GIF , PNG e TIFF ed è utilizzato dai programmi di compressione WinZIP e WinRAR. In particolare il formato GIF viene utilizzato per le applicazioni Internet in quanto offre buona qualità dell’immagine occupando poco spazio consentendo rapide visualizzazioni di pagine Web. Un file GIF può contenere più immagini ed uno sfondo, che il browser può visualiuzzare come animazioni ( GIF animate ). I limiti del sistema GIF sono Pochi colori : 256 12 Stampa scadente delle immagini. I metodi lossy si usano per i dati multimediali. Essi sfruttano i limiti delle capacità percettive dell’occhio umano. Ad esempio la retina dell’occhio è maggiormente insensibile alle variazioni di luminosità che a quelle di colore: questo permette di trascurare differenze piccole di sfumature di colore tra pixel vicini riducendo le sfumature e quindi la dimensione dell’immagine. Il formato JPEG usa questo metodo e consente di visualizzare immagini a 256 colori consentendo anche di definire la percentuale di compressione. Quindi maggiore compressione minore qualità dell’immagine. IL metodo lossy viene utilizzato anche per codificare immagini in movimento nei formati MPEG e WMV. Immagini vettoriali Un oggetto grafico bitmap è memorizzato semplicemente come una griglia di pixel a ciascuno dei quali è associato un colore. Una volta disegnata una linea, essa non è più una “linea” ma solo un insieme di pixel sullo schermo, pertanto non è più possibile modificarne le coordinate. Le manipolazioni che si possono fare su un oggetto bitmap ( ad esempio con Photoshop ) si basano sul concetto di modificare la posizione e il colore di un pixel. Esiste un altro tipo di approccio alla rappresentazione delle immagini ed è la grafica vettoriale, utilizzato principalmente quando le immagini sono geometriche ( linee, cerchi rettangoli..). In questo caso non si registrano le immagini ma il procedimento per costruirle ottenendo due vantaggi: minima occupazione di spazio; facile ridimensionamento delle figure. Ogni oggetto è codificato tramite un identificatore ed alcuni parametri che lo caratterizzano, ad esempio Circle 98 66 50 ( 98, 66 ) sono le coordinate del cerchio 50 è il raggio Polyline 0 48 88 152 88 48 è un poligono con le 3 coordinate deli vertici Sono esempi di formati per grafica vettoriali : ai (Adobe Illustrator) cdr (Corel Draw) svg (Scalable Vector Graphics) dwg (AutoCAD etc. - Grafica vettoriale) dxf (Drawing Interchange (eXchange)) dwf (Autodesk Design Review) fla (Macromedia Flash) lfp (Laser file plus) prt (Vari) Il processo di di visualizzazione di un’immagine vettoriale e detta rendering o rasterizzazione. Rappresentazione dei filmati I filmati o immagini in movimento vengono rappresentati da una serie di immagini fisse ( frame o fotogrammi ) visualizzati ad una frequenza ( frame rate ) tale da consentire all’occhio umano di ricostruire il movimento. Quindi come per le immagini fisse ogni frame deve essere convertito in digitale per poterlo memorizzare in digitale e quindi riconvertirlo in analogico per riprodurlo. La codifica comprende codifica dati audio codifica dati video loro sincronizzazione Sarebbe altamente inefficiente codificare completamente ogni frame e quindi per ovviare a questo in conveniente viene utilizzata ad esempio la codifica differenziale ( compressione ) che consiste 13 sostanzialmente nel codificare alcuni frame chiave interamente, altri solo nelle parti che differiscono da quelli adiacenti. Lo standard di compressione differenziale più conosciuto è l’ MPEG (Movie Picture Experts Group). I file in questo formato hanno estensione .mpg. Questo formato opera diverse compressioni compressione spaziale ( all’interno del singolo fotogramma ) compressione temporale (sfrutta componenti comuni fra fotogrammi successivi per effettuare compressioni su un’intera scena ) Esistono 3 diversi standard appartenenti a questo gruppo: MPEG-1, MPEG-2, M-PEG4 Altri formati video sono Quicktime (Apple), Avi (Microsoft) Real Video: molto usato in applicazioni di streaming video. A differenza del classico download dove è necessario attendere lo scaricamento completo di un file prima di poterlo vedere, lo streamming video consente di visualizzare un filmato mentre i frame vengono trasmessi continuamente l’uno dopo l’altro ( naturalmente con un piccolo ritardo ); poi vengono buttati via. Rappresentazione dei suoni Anche i suoni possono essere rappresentati in forma digitale Dal punto di vista fisico un suono è un'alterazione della pressione dell'aria che, quando rilevata dall'orecchio umano, viene trasformata in un particolare stimolo al cervello. La durata, l'intensità e la variazione nel tempo della pressione dell'aria sono le quantità fisiche che rendono un suono diverso da ogni altro. Un suono può essere rappresentato mediante un’onda che descrive la variazione della pressione dell'aria nel tempo. Sull'asse delle ascisse rappresentiamo il tempo e sull'asse delle ordinate la pressione corrispondente al suono stesso. La conversione di un segnale continuo in una successione di numeri viene eseguita con due successive operazioni elementari: 1. Campionamenti sull’onda: si misura il valore dell’onda a intervalli di tempo costante; 2. Quantizzazione e codifica: ogni campione viene quantizzato ossia convertito in un numero; la sequenza dei valori numerici ottenuta dai campioni può essere facilmente codificata in forma digitale. Quanto più frequentemente viene campionato il valore di intensità dell'onda, tanto più precisa sarà la rappresentazione dell'onda. 14 Osservazione: Un errore viene comunque introdotto quando si converte il valore analogico di un campione in un numero con un numero limitato di cifre , quindi la codifica digitale introduce sempre una perdita di qualità. Se ad esempio consideriamo un CD musicale si mescolano due registrazioni ( stereofonia= 2 sorgenti di audio); 44.100 campioni al secondo per ogni registrazione; 16 bit per memorizzare l’informazione di ogni campione. Quindi servono: 44.100 campioni × 16 bit × 2 = 1.411.200 bit per ogni secondo di registrazione. Osservazione Per calcolare lo spazio occupato da un file di testo, da un’immagine, da un file audio, la tecnica è sempre la stessa Si trova quanti bit occupa ogni unità elementare costituente il file (un carattere per il testo, un pixel per l’immagine, un campione per il file audio ) Si trova il numero di unità elementari che costituiscono il file (il numero di caratteri per il testo, il numero di pixel per l’immagine -sfruttando la risoluzione, il numero di campioni per il file audio sfruttando la frequenza di campionamento) Si moltiplicano queste due quantità Formati audio Un formato non compresso è il WAVE con estensione .wav. Un esempio di formato compresso è l’ MPEG3 (mp3) la cui estensione è .mp3. formato specializzato per la compressione e la trasmissione di file audio digitali. Le sue caratteristiche principali sono: garantisce una fedeltà molto alta ( praticamente indistinguibile dal suono non compresso ) efficienza e grande diffusione: la dimensione del file ottenuto è molto inferiore di quella originale ( anche un decimo o meno), permette di trasmettere file audio anche con connessioni molto lente. L’ MP3 viene utilizzato anche per lo streaming audio che prevede : compressione dell’audio da parte della sorgente audio ( per esempio una radio su web ) il dato audio compresso viene trasmesso a un destinatario ( per es. ad un PC ) il PC possiede il programma codec per la decodifica del dato e quindi è possibile sentire il suono man mano che viene trasmesso. Un requisito importante per lo streamming sia video che audio è una connessioni a larga banda. 15 Codici L’ alfabeto è l’insieme dei simboli con i quali si costruiscono le parole La parola è l’oggetto formato da una sequenza di uno o più simboli appartenenti ad un alfabeto. Si dice alfabeto sorgente T = ( x1, x2, x3 ….,xn ) l’insieme dei valori da rappresentare mentre l’ alfabeto in codice E= ( e1, e2, e3…, ek ) è l’insieme dei simboli che servono a rappresentare le parole costruite con l’alfabeto sorgente. Una parola ( o stringa ) di lunghezza l costruita con l’alfabeto E è definito codifica o trasformazione. L’applicazione che associa ad ogni parola di T una parola di E di lunghezza l viene detta codice. Per costruire un codice bisogna prima definire il numero dei simboli dell’alfabeto la lunghezza delle parole. l Se abbiamo un alfabeto di n simboli e lunghezza di parola l avremo n parole diverse tra loro. 8 Ad esempio n = 2 l = 8 avremo 2 = 256 parole o stringhe diverse fra loro. Ogni codice ha una sua lunghezza minima di parola cioè il numero minimo di simboli che devono avere tutte le parole per non codificare in un modo ambiguo le stringhe costruite con l’alfabeto sorgente; lunghezza della parola = lunghezza minima avremo un codice efficiente, lunghezza della parola > lunghezza minima avremo un codice ridondante I codici possono codificare solo numeri ed in questo caso avremo codici efficienti numerici e codici che possono codificare simboli di vario genere ( lettere, cifre, segni di interpunzione ecc.. ecc.. ) detti codici efficienti alfanumerici. Codici efficienti numerici Esempi di codici efficienti numerici sono: • Il codice BCD con S = 2 e L = 4 quindi potremo codificare 24 = 16 parole diverse • Codice Eccesso 3 con S = 2 e L = 4 Il codice BCD è un codice efficiente e ponderato, efficiente perché per codificare le 10 cifre decimali utilizza 4 bit ossia una lunghezza uguale a quella minima; ponderato perché ogni bit ha un valore in base alla sua posizione, per questo motivo il codice viene anche chiamato codice 8421 cioè il valore decimale delle potenze di 2 partendo dal bit più significativo a quello meno significativo. Tabella del BCD Decimale BCD 0 0000 1 0001 2 0010 3 0011 4 0100 5 0101 16 6 0110 7 0111 8 1000 9 1001 Il codice eccesso 3 è un codice numerico efficiente che codifica le cifre decimali su 4 bit. Ed è un codice non ponderato, ma autocomplementante, cioè per ottenere il complemento a 9 di una cifra basta invertire i bit corrispondenti. Il suo nome deriva dal fatto che ogni codifica è ottenuta dalla corrispondente configurazione binaria sommata a 3. Tabella codice eccesso 3 decimale eccesso 3 0 0011 1 0100 2 0101 3 0110 4 0111 5 1000 6 1001 7 1010 8 1011 9 1100 Da notare che per i due codici esaminati delle 16 possibili parole se ne utilizzano 10 soltanto, cioè il numero delle cifre del sistema numerico decimale. Codici efficienti alfanumerici Quando in un processo di comunicazione l’oggetto è un testo costruito non solo da cifre ma anche da caratteri alfabetici e caratteri speciali, occorre utilizzare i codici alfanumerici, ovviamente questi codici hanno lunghezza di parola maggiore di 4. Esempi di codici efficienti alfanumerici sono: codice S = numero simboli L= lunghezza parola Codice ASCII 2 7o8 Codice EBCDIC Codice ISO-8859 2 2 8 8 17 numero parole 7 8 2 =128 o 2 = 256 8 2 = 256 8 2 = 256 Codice Unicode 2 16 16 2 = 65536 Il codice ASCII Fra i codici alfanumerici è molto usato e può rappresentare un vasto numero di caratteri. Il codice ASCII nella versione standard è un codice a 7 bit con il quale si possono codificare 128 simboli (oppure se lo si vuole estendere ad 8 bit può codificare 256 simboli) diversi. Esso è formato da due diverse categorie: i caratteri riproducibili (cioè se digitiamo sulla tastiera le parole si vedono i suoi effetti) ed i non riproducibili (cioè se premiano sulla tastiera, per esempio, "invio" non si vedrà nulla tranne il movimento del cursore nella riga successiva). Il codice EBCDIC Un altro codice a 8 bit utilizzato è il codice EBCDIC sviluppato da IBM per i propri sistemi di grandi dimensioni;poiché è utilizzato solo dai sistemi IBM,questo codice rende incompatibili le macchine IBM con sistemi appartenenti ad altri produttori. Ogni byte comprende due parti(semibyte o nibble) di quattro bit: Il semibyte più significativo è detto Zonatura Il semibyte meno significativo è detto Numerico I caratteri alfanumerici contigui sono raggruppati in blocchi: Quattro blocchi principali(le prime 4 zone) da 0000 0000 a 0011 1111 che sono riservati hai caratteri di controllo I blocchi da 0100 0000 a 1011 1111 sono per le lettere minuscole I blocchi da 1100 0000 a 1111 1111 sono utilizzate per le lettere maiuscole e per le cifre numeriche;mentre i per i numeri i bit di zonatura sono 1111. Il codice ISO 8859 è una famiglia di 14 diversi codici ognuno dei quali ha un insieme di 256 caratteri. Il set dei caratteri comprende gli alfabeti di quasi tutte le lingue del mondo; infatti lui identifica con ISO 8859-n (per n sta il numero che scegliamo per la lingua[per ogni lingua sta un numero]). Il codice Unicode è un sistema per scambiare , elaborare e visualizzare testo scritto nelle diverse lingue nel mondo moderno;questo codice a confronto del resto include 34168 caratteri supportate da 24 lingue diverse, tra le principali nel mondo. Codici ridondanti Lo schema del modello di comunicazione comprende la codifica di sorgente, che consiste nel codificare il messaggio in modo tale da permettere a chi lo riceve di stabilire se il messaggio è corretto o errato. Per fare questo si usano i codici ridondanti, cioè codici che hanno una lunghezza di parola superiore alla lunghezza minima. Quindi se servono n bit per codificare un messaggio in modo efficiente, si deve utilizzare un codice con lunghezze di parole m > n + k i k bit aggiuntivi sono detti bit di controllo. La lunghezza delle parole del codice diventa m = n + k dove n è la lunghezza della parola e k i bit aggiuntivi. Si definisce ridondanza R il valore ottenuto dalle seguente formula: m R = n+k = k = 1 + n n 18 n Pertanto più grande è k e più tempo si impiega a trasmettere il messaggio. I k bit di controllo non sono disposti in modo causale, ma secondo uno schema preciso che permetta al destinatario di controllare la correttezza del messaggio ricevuto. Peso: il peso di una parola è il numero di bit 1 presenti nella parola stessa. Distanza tra due parole: Numero di posizioni in cui le parole hanno bit con valore diverso. Per esempio, se P1 = 10001 e P2 = 10110, le due parole hanno distanza = 3. Molteplicità dell’errore: Distanza tra la parola trasmessa e la parola ricevuta. Per esempio, si ha errore di molteplicità 2 quando ci sono 2 bit sbagliati nella parola ricevuta. Distanza minima di hamming di un codice: La distanza minima di hamming è la minima distanza tra una qualsiasi coppia di parole di un codice ( si indica con h ). Si può affermare che i codici efficienti hanno h = 1 e quelli ridondanti h > 1. Più elevato è h , maggiore è la probabilità di rivelare una parola errata, perché il codice è in grado di rilevare parole errate con molteplicità dell’errore fino a h-1. Un codice in grado di rilevare l’errore non è detto che sia anche in grado di correggerlo: per questo motivo i codici ridondanti si distinguono in codici rilevatori e codici correttori di errori. I codici rilevatori richiedono meno bit di controllo rispetto ai codici correttori: quindi nel primo caso la comunicazione è più efficiente rispetto al secondo caso. In un processo di comunicazione se la comunicazione è bidirezionale (full Duplex) si utilizzano codici rilevatori e l’errore si risolve con la ritrasmissione del messaggio; se, invece, la trasmissione è unidirezionale (Simplex) si utilizzano codici correttori visto che il ricevente è impossibilitato a trasmettere. Nella teoria dei codici si possono utilizzare i seguenti teoremi: Teorema 1 Condizione necessaria e sufficiente affinché un codice sia in grado di rilevare errori di molteplicità K è che la distanza minima di Hamming sia almeno K + 1. Teorema 2 Condizione necessaria e sufficiente affinché un codice sia in grado di correggere errori di molteplicità K e che la distanza minima di Hamming sia almeno 2K + 1. Codici rilevatori di errori Nel seguito sono presentati tre tipi di codici rilevatori di errori. I primi due sono i più utilizzati nella trasmissione seriale, che può essere asincrona,come nelle porte seriali del personal computer COM1 e COM2, oppure sincrona, come nelle schede di rete o nei collegamenti in internet. Codice a controllo di parità ( partity check ) Questo tipo di codice è costituito aggiungendo ad ogni parola un bit di controllo, come bit più significativo, in modo da rendere pari ( oppure dispari ) il peso di ogni parola formata da m = n + 1 Bit. Le caratteristiche di questo codice sono: Ridondanza: R = m/n = n+1/n = 1+1/n. Distanza minima di Hamming: h = 2 Molteplicità dell’errore dispari, cioè il codice è in grado di rilevare un numero di bit errati di valore dispari. Il controllo di peso pari e dispari presenta dal punto di vista matematico la stessa robustezza agli errori. Dal punto di vista pratico è invece consigliabile adottare il controllo di peso dispari in modo che la configurazione costituita da tutti 0 (assenza di collegamento) sia considerata errore, situazione non rilevabile utilizzando il codice a controllo di peso pari. 19 Codice ciclico di ridondanza o controllo polinomiale I codici cilici di ridondanza CRC, detti anche codici a controllo polinomiale,si basano sul fatto che i bit che formano il messaggio possono essere considerati come coefficienti di un polinomio completo di grado n-1 in x; n rappresenta il numero dei bit del messaggio. Indichiamo questo polinomio con T(x). Si consideri poi un polinomio G(x), detto polinomio generatore, di grado inferiore al polinomio T(x) e avente il termine noto uguale a 1. Dalla parte della sorgente si divide il polinomio T(x) per G(x) e si calcola il resto, che indichiamo con R(x).Il trasmettitore invia il messaggio concatenato al resto R(x) cioè M(x) = T(x):R(x). Dalla parte del ricevente si deve fare l’operazione inversa: dal messaggio ricevuto M(x)’ = T’(x):R’(x) si prende la parte T’(x) e la si divide per il polinomio G(x), calcolando il resto R’’(x); se la trasmissione è corretta, R’’(x) deve essere uguale a R’(x), altrimenti il messaggio deve essere considerato errato. Checksum Checksum, tradotto letteralmente significa somma di controllo. È una sequenza di bit che viene utilizzata per verificare l'integrità di un dato o di un messaggio che può subire alterazioni. Ad esempio un algoritmo potrebbe essere : Suddivido il messaggio in blocchi di lunghezza fissa Applico lo XOR ai bit di ciascun blocco ( controllo trasversale ) ottenendo T bit Applico lo XOR ai bit di stessa posizione nei i blocchi (controllo longitudinale) ottenendo B bit. Invio insieme al messaggio M , T e B cioè M:T:B Il ricevitore calcolerà T’ e B’ se T = T’ e B = B’ allora il messaggio ricevuto è corretto altrimenti potrò rilevare e in alcuni casi correggere gli errori. Ad esempio: controllo trasversale 1 1 0 0 0 1 1 1 1 1 0 1 1 1 1 0 1 0 0 1 1 1 0 1 0 0 1 1 1 0 1 0 1 0 1 0 0 0 0 1 1 0 1 0 1 1 1 1 1 0 1 1 1 0 1 0 0 1 0 0 0 1 controllo longitudinale Il messaggio originale di 48 bit è stato suddiviso in blocchi di 8 bit. E’ stato applicato lo XOR ai bit di ciascuna riga ( blocco ). Lo Xor è stato applicato ai bit di ogni colonna ( bit di uguale posizione in ciascun blocco ) i bit così calcolati sono inviati al destinatario insieme al messaggio. Nota Ricordiamo la tavola della verità dello xor a b a xor b 0 0 0 0 1 1 1 0 1 1 1 0 Osserviamo che calcolare lo xor su un gruppo di bit equivale a calcolarne il bit di parità pari. Codici autocorrettori 20 Quando la trasmissione è di tipo Simplex, cioè il destinatario non è in grado di comunicare al mittente che il messaggio è arrivato in modo coretto, occorre utilizzare i codici correttori, codici in grado non soltanto di rilevare l’errore,ma anche di individuare la posizione del bit errato. Il codice è costituito con il controllo longitudinale(ed orizzontale) e trasversale (o verticale) del peso, pari o dispari, contenuto nel codice dei caratteri da trasmettere. Per esempio,supponiamo di dover inviare un blocco di 4 caratteri, ognuno dei quali è formato da 4 bit, come illustrato nella tabella seguente: C1 0 0 1 1 C2 1 0 1 1 C3 1 1 1 0 C4 0 0 0 1 Applicando il controllo di parità dispari sia in verticale che in orizzontale, il blocco da trasmettere diventa il seguente: C1 0 0 1 1 1 C2 1 0 1 1 0 C3 1 1 1 0 0 C4 0 0 0 1 0 1 0 0 0 0 Si osservi che il controllo longitudinale è applicato anche ai bit di controllo trasversale. Supponiamo ora che durante la trasmissione il bit più significativo del carattere C2 venga alternato passando da 1 a 0. Il ricevente ottiene la seguente tabella: C1 0 0 1 1 1 C2 0 0 1 1 1 C3 1 1 1 0 0 C4 0 0 0 1 0 0 0 0 0 0 Il ricevente, facendo lo stesso tipo di controlli, rileva un errore nella riga 2 e nella colonna 1: questo significa che il bit errato si trova all’intersezione della riga e della colonna che non soddisfano il controllo. Pertanto il bit ricevuto come 0 viene corretto a 1. Codici a lunghezza variabile Una sorgente si dice discreta quando i simboli che può emettere sono in numero finito. Le proprietà che caratterizzano i simboli sono: • La probabilità di emissione, • La quantità di informazioni da essi trasporta. La probabilità di emissione indica la probabilità che un simbolo venga emesso tra gli N elementi dell’alfabeto. Se i simboli hanno tutti la stessa probabilità di emissione, questa vale P = 1/N 21 Se le probabilità di emissione non è uguale per tutti i simboli, occorre calcolarla in modo empirico come: numero di volte che il simbolo si viene emesso P(si) = ----------------------------------------------------------numero totale dei simboli emessi. Misura dell’ informazione Per informazione si intende tutto ciò che serve a togliere incertezza. Se la sorgente ha un alfabeto di 2 simboli, l’emissione di un simbolo fornisce una maggiore informazione rispetto alla sorgente precedente: in questo caso è come se una persona rispondesse “si” o “no” alle domande; la sua risposta toglie incertezza. La quantità di informazione trasporta da un simbolo è data dalla seguente formula: l(S(i))=log1/p(S(i)) applicando la proprietà dei logaritmi, per la quale il logaritmo di un rapporto è uguale alla differenza dei logaritmi, si può scrivere: l(S(i))=log1-logP(S(i))=0-logP(S(i))=-logP(S(i)) Se i logaritmi sono in base 2, il risultato è espresso in bit. Possiamo definire il bit come l’unità elementare di informazioni. Se una sorgente emette simboli tutti con la stessa probabilità, l’informazione associata ad un simbolo è data da: I=1/log(2)p Poiché p = 1/N, allora I=log(2)1/1/N=log(2)N Si può notare che più grande è N, maggiore è l’informazione associata ad ogni simbolo che viene emesso. Nel caso minimo in cui la sorgente emetta 2 simboli, l’informazione vale log(2)=1 bit. Si possono fare anche le seguenti considerazioni: L’ informazione associata a un simbolo si misura in bit. Il bit rappresenta l’informazione associata a un simbolo emesso da una sorgente con un alfabeto di 2 simboli indipendenti ed equiprobabili. Una sorgente che emette simboli equiprobabili si dice priva di memoria o a memoria zero. Maggiore è la probabilità di emissione di un simbolo e minore è il suo contenuto informativo rispetto ad un altro simbolo che ha probabilità di emissione minore. Lunghezza media di un codice Se una sorgente emette S(1),S(2); …S(n) simboli indipendenti, con probabilità p(1),p(2), …,P(n),codificati con parole lunghe l(1),l(2), …, l(n) bit, la lunghezza media del codice è data dalla seguente formula: L = p(1)*l(1) + p(2)*l(2) +…+ P(n)*l(n) Entropia Si definisce entropia l’ informazione media di una sorgente S che emette simboli indipendenti in sequenza. L’ informazione media della sorgente è : H(S) = ∑ P(S(i)) * l(S(i)) = ∑ –P(S(i)) * log P(S(i)) 22 Essa si misura in bit ed indica il grado di incertezza di ricevere un simbolo tra gli n dell’alfabeto della sorgente. La massima entropia si ha quando i simboli sono equiprobabili: applicando la formula in questo caso si ottiene H(S) = logN. Codice di Huffman I codici a lunghezza variabile si usano quando si conosce la probabilità di emissione dei simboli e questi non hanno la steassa probabilità. In questo caso, per aumentare l’ efficienza della trasmissione, si assegnano meno bit ai simboli più probabili e viceversa più bit a i simboli meno probabili. Il primo codice a lunghezza variabile, che utilizza la diversa probabilità di emissione dei simboli, è il codice morse, utilizzato in radiotelegrafia e che utilizza due simboli, il punto e la linea, per codificare i simboli dell’alfabeto e delle cifre numeriche. Nell’ uso dei codici a lunghezza variabile occorre risolvere il problema della condizione del prefisso: cioè nessuna parola del codice deve essere prefisso ( parte iniziale ) di una parola più lunga del codice, perché si potrebbe avere difficoltà in fase di decodifica. Per esempio, se la sorgente ha quattro simboli codificati con x1 = 1 , x2 = 10 , x3 = 11 E trasmette la sequenza x3 x2, la stringa del messaggio diventa 1110 In ricezione potrebbe essere interpretata come 1 1 10 Cioè x1 x1 x2 diversa da quella trasmessa. Il codice di huffman risolve il problema attraverso una struttura ad albero, in cui i simboli sono le foglie,utilizzando il seguente algoritmo. Algoritmo di generazione del codice Potremo suddividere l’algoritmo di generazione del codice in 3 fasi: Prima fase 1. I simboli vengono ordinati in senso decrescente di probabilità; 2. I due simboli con minore probabilità vengono “fusi” in un nuovo simbolo con probabilità somma delle 2 probabilità dei due simboli componenti; 3. Si ritorna ad 1) fino a che la tabella ha un numero di simboli diverso da 1; Se l’unico simbolo rimasto ha probabilità 1 allora potremo passare alla fase 2. Seconda fase ( costruzione dell’albero ) A questo punto si costruisce l’ albero di Huffman che ha come radice il simbolo finale e come sotto alberi i simboli che lo hanno generato. Il processo è ricorsivo e termina quando tutte le foglie sono simboli originari. Si assegna 1 al sottoalbero di destra e 0 al sottoalbero di sinistra. Terza fase ( generazione del codice ) Per codificare i simboli basta attraversare l ‘ albero dalla radice al simbolo e registrare i bit che man mano si incontrano. Esempio: Prima fase Simbolo P(S) Simbolo P(S) Simbolo P(S) Simbolo P(S) S1 1/2 S1 1/2 S1 1/2 S1 1/2 S2 1/4 S2 1/4 S2 1/4 C 1/2 23 Simbolo P(S) D 1 S3 1/8 A 1/8 S4 1/16 S3 1/8 S5 1/16 B 1/4 Attenzione prima di creare una nuova tabella riordinare in modo decrescente di probabilità. Seconda fase D | -----------------------0| |1 S1 C -----------------------0| |1 S2 B -----------------------0| |1 A S3 -----------------------0| |1 S4 S5 Terza fase Simbolo Codice S1 0 S2 10 S3 111 S4 1100 S5 1111 Codici riflessi I codici riflessi sono usati nei convertitori di segnali analogico/digitali e hanno le parole ordinate secondo i numeri naturali in modo che ogni parola P abbia distanza 1 dalla parola precedente, P-1, e dalla successiva P+1. I codici riflessi non sono ponderati e,poiché oltre a quantizzare una grandezza analogica si deve anche elaborarla,occorre appoggiarsi ad un codice ponderato come i codici binari puri. Codice Gray 24 Il codice gray è un codice riflesso che ha la proprietà di avere semplici algoritmi di trasformazione da codice binario a codice gray e viceversa:per questo motivo è praticamente l’ unico codice riflesso utilizzato. La conversione di un numero non viene cifra per cifra ma nel suo complesso. I codici di gray non sono utilizzati nelle trasmissioni proprio perché, avendo distanza minima di hamming uguale a 1, non possono utilizzare tecniche di rilevazione o correzione degli errori. Per convertire una parola binaria a una parola del codice di gray si applica il seguente algoritmo: 1)Procedendo da sinistra verso destra, per ricavare il bit della parola di gray di posto i si esegue l’operazione di OR esclusivo tra il bit di posto i e il bit i-1 della parola in binario. 2)Il primo bit della parola di gray si ottiene facendo l’ OR esclusivo tra 0 e il bit di posto 1 della parola binaria. Esempio Convertire in codice di Gray i numeri 55, 56, 57 che hanno la seguente codifica binaria: 55= 110111 , 56=111000 , 57=111001 w n 1 2 3 4 5 6 pb 55 1 1 0 1 1 1 pg 55 1 0 1 1 0 0 pb 56 1 1 1 0 0 0 pg 56 1 0 0 1 0 0 pb 57 1 1 1 0 0 1 pg 57 1 0 0 1 0 1 La prima colonna indica la parola :PB per la parola binaria,Pg per la parola di Gray. La seconda parola indica il valore decimale del numero. Le altre colonne contengono i bit della codifica. Ricordando che l’OR esclusivo vale 1 se i due bit sono diversi e zero se sono uguali, nel caso della conversione dl numero 55: il primo bit di Gray vale 1 perché il primo bit della parola binaria è 1 e bisogna farne l’OR esclusivo con lo zero; il bit 2 di Gray vale 0 perché il bit 1 ed il bit 2 della parola binaria sono entrambi 1; il bit 3 di Gray vale 1 perché il bit 3 della parola binaria è 0 mentre quello di posto 2 è 1 e cosi via. Il procedimento inverso, per convertire una parola dal codice di Gray al codice binario, applica il seguente algoritmo: si esamina la parola di Gray da sinistra verso destra : i bit della parola binaria sono uguali a quelli di Gray fino al primo bit di valore 1 che si incontra. Dal bit successivo: se il bit di Gray è 0, il corrispondente bit binario è uguale al bit che immediatamente lo precede; se il bit di Gray è 1, il corrispondente bit binario è l’opposto del bit che immediatamente lo precede. Come esempio, consideriamo i numeri precedenti in codice di Gray e li riportiamo in binario. w n 1 2 3 4 5 6 pg 55 1 0 1 1 0 0 pb 55 1 1 0 1 1 1 pg 56 1 0 0 1 0 0 pb 56 1 1 1 0 0 0 pg 57 1 0 0 1 0 1 pb 57 1 1 1 0 0 1 Per ottenere la parola binaria del numero 55 a partire dalla corrispondente parola di Gray si opera in questo modo: il primo bit della parola binaria è 1 perché il primo bit di Gray è 1 ; il bit 2 della parola binaria è 1 perché il bit 2 di Gray è 0, quindi il bit 2 della parola binaria è uguale al precedente che è 1. il bit 3 della parola binaria è 0 perché il terzo bit di Gray è 1 e quindi il terzo bit binario deve essere l’opposto del suo secondo bit, e cosi via. Codifica dei numeri Nel calcolatore la memoria è costituita da un numero finito di celle mentre l’insieme dei valori da rappresentare è infinito quindi è possibile solo una rappresentazione parziale dell’informazione cioè non tutti i valori possono essere rappresentati. Supponendo di avere a disposizione n bit con i quali vogliamo rappresentare i seguenti dati: un numero naturale ( 0, 1, 2, ...) un numero relativo (...,-3,-2,-1, 0, +1, +2, +3, ... ) un numero razionale ( 0.0018, 2345.06, 12.0, ... ) Rappresentazione dei numeri naturali Un numero naturale può essere rappresentato dalla sua notazione in binario. Esempi : con n = 8 rappresentare in memoria 10 il risultato sarà 0000 1010 cioè ai 4 bit necessari alla rappresentazione di 10 verranno aggiunti 4 bit a 0 a sinistra 54 verrà rappresentato 0011 0110 anche in questo caso sono stati aggiunti 2 bit a 0. n Quanti numeri possiamo rappresentare con n bit in base due ? 2 4 Ad es n = 4 2 = 16 potremo rappresentare 16 combinazioni quindi i naturali da 0 a 15. Considerando l’esempio precedente se sommiamo a 11112 + 1 = 0000 perché il quinto bit che viene generato dalla somma viene perso in quanto i bit a disposizione sono solo 4 ( overflow ). n Quindi in generale 2 - 1 + 1 = 0 questa proprietà viene detta circolarità della rappresentazione. Rappresentazione dei numeri relativi Sono 4 le modalità di rappresentazione dei numeri binari relativi: • modulo e segno • complemento a 1 • complemento a 2 • eccesso P Rappresentazione modulo e segno In questo formato il bit più significativo rappresenta il segno 0 = segno + I restanti bit rappresentano il modulo esempio: con sequenze di quattro bit ( n = 4 ) 1 = segno - Notiamo che lo 0 viene codificato due volte 0000 e 1000 cioè abbiamo uno 0 postivo e uno negativo. Questa rappresentazione pone difficoltà nell’esecuzione delle operazioni aritmetiche, perché il segno va trattato in maniera differente dagli altri bit. Rappresentazione con il complemento a 1 Ricordando che il complemento a 1 si ottiene invertendo il valore dei bit che costituiscono il numero ovvero facendone il complemento dei bit. esempio: 1010 il complemento a 1 sarà 0101 La rappresentazione in memoria di un numero relativo con il metodo del complemento a 1 ( CP1) avendo a disposizione n bit si effettua nel seguente modo: • i numeri positivi si rappresentano come i primi 2n-1 numeri naturali • numeri negativi si rappresentano con il complemento a 1 del corrispondente intero positivo esempi: CP1(5) -> 5 = 0101 CP1(-5) -> complemento ( 0101) = 1010 esempio : con sequenze di quattro bit ( n = 4 ) Osserviamo che : • i numeri positivi iniziano con zero i negativi con 1 • l’intervallo di rappresentazione con n bit è ( -2n-1 + 1, 2n-1 – 1 ) • abbiamo una doppia rappresentazione dello zero Rappresentazione con il complemento a 2 Ricordiamo che il complemento a 2 di un numero si ottiene • facendo il complemento a 1 del numero • al risultato si somma 1 Oppure partendo da destra si lasciano invariate tutte le cifre fino al primo uno ( incluso ) e poi si invertono tutti i seguenti. La rappresentazione in memoria di un numero relativo con il metodo del complemento a 1 ( CP2) avendo a disposizione n bit si effettua nel seguente modo: • i numeri positivi si rappresentano si rappresentano come i primi 2n-1 numeri naturali; • numeri negativi si rappresentano con il complemento a 2 del corrispondente intero positivo. esempio 5 = 0101 -5 = 1010 + 1 = 1011 Osserviamo che • La prima cifra continua a rappresentare il segno • Il combiamento di segno i un numero di ottiene con il complemento a 2 el numero esempio 5 = 0101 -5 = 1011 (ottenuto complementando a due il precedente) 5 = 0101 (ottenuto complementando a due il precedente • Anche in questo caso vi è circolarità, sempre con 4 bit Se 1111 + 0000 1 -1 = 0000 Esempi di somme in complemento a 2 Esempi di sottrazioni in complemento a 2 1111 la somma di due numeri relativi di n cifre si esegue come nel caso dei numeri naturali • l’eventuale riporto sulla cifra n+1 viene ignorato • se il risultato eccede 2n-1 si ottiene il numero negativo che non corrisponde al risultato desiderato • se il risultato è minore di -2n-1 si ottiene un numero positivo che non corrisponde al risultato desiderato la sottrazione tra due numeri relativi di n cifre si esegue complementando a due il secondo numero ed eseguendo la somma Rappresentazione con Eccesso 2n - 1 Come al solito n rappresenta il numero di bit a disposizione per la codifica. I numeri in eccesso 2n – 1 si ottengono • sommando 2n – 1 ( detto bias ) al numero da codificare • si codifica in binario il numero ottenuto al passo precedente esempio Supponiamo che n = 5 quindi 2n – 1 = 2 5-1 = 24 = 16 Il numero da codificare è 6 quindi 6 + 16 = 22 in binario 101102 Rappresentazione dei numeri reali in virgola mobile Una delle rappresentazioni più utilizzate per i reali è quella in virgola mobile, anche detta floating point. Un qualunque numero razionale può essere riscritto in modo da evidenziare una mantissa ed un esponente esempio • 273.023 = .273023 × 103 ( .273023 è la mantissa, 3 è l’esponente ) • 1001001.0101 = .10010010101 ×2+7 Rappresentare un numero reale in virgola mobile consiste nel memorizzare mantissa, esponente e i segni di mantissa ed esponente del numero espresso in base 2. segno segno mantissa esponente esponenente mantissa Lo standard IEEE 754 prevede che il numero reale venga rappresentato nel seguente modo S1.xxxxxxx * 2esponente S = segno della mantissa x = cifra binaria ( 0 o 1 ) Questa normalizzazione prevede che la virgola venga spostata alla destra del primo 1 ( quello più significativo, di “peso” maggiore ) che si incontra nel numero reale da rappresentare. esempi: 101.1112 normalizzato 1.011112 x 22 0.0010102 normalizzato 1.0102 x 2-3 Visto che a destra del punto vi è sempre 1 potremo nella memorizzazione sottointendere la sua presenza, “guadagnando” così 1 bit. Questa codifica viene detta bit hidden. In memoria avremo una rappresentazione segno esponenente mantissa mantissa L’esponente viene memorizzato secondo la codifica Eccesso P studiata per numeri relativi. La normativa IEEE 754 sono previsti 3 formati diversi di codifica in virgola mobile Singola precisione : 32 bit esponente codificato in eccesso 127 ( 2 8-1 – 1 ) Doppia precisione : 64 bit esponente codificato in eccesso 1023 ( 2 11-1 – 1 ) Precisione estesa : 80 bit esponente codificato in eccesso 16383 ( 2 15-1 – 1 ) 1bit 8 bit segno esponenente mantissa 1 bit segno mantissa 1 bit segno mantissa 23 bit mantissa 11 bit 52 bit esponenente mantissa 15 bit 64 bit esponenente mantissa esempio di precision singola • Codifichiamo -43,125 osserviamo che il numero è negativo quindi il bit del segno è 1 • Convertiamo il numero in binario 101011 . 0012 • Normalizziamo spostando la , di 5 posizioni a sinistra 1 . 010110012 x 25 • Codifichiamo l’esponente in eccesso 127 al quale dobbiamo sommare 5 che è il valore ottenuto dalla normalizzazione. 5 + 127 = 132 = 100001002 avremo quindi 1 bit 1 8 bit 10000100 23 bit 01011001000000000000000 NOTA: La notazione hidden bit non viene utilizzata per la precisione estesa : in questo caso si preferisce la “velocità” di esecuzione dei calcoli ( non si deve aggiungere il bit a 1 prima di effettuare calcoli ), alla maggiore capacità di memorizzazione del numero ottenuta togliendo il bit a 1 alla sinistra del punto decimale. ARCHITETTURA DI UN SISTEMA A MICROPROCESSORE Introduzione Agli inizi degli anni '60 comparvero sul mercato i primi circuiti integrati digitali. Servendosi di tali dispositivi divenne più semplice la progettazione e la realizzazione di sistemi logici. Utilizzando la tecnica denominata a logica cablata, i sistemi venivano progettati utilizzando i circuiti integrati digitali offerti dal mercato ( porte logiche, registri, contatori, ecc.) in funzione dell'applicazione specifica. Inoltre, aumentando sempre più il numero di componenti integrati su un unico chip di silicio, ovvero le funzioni logiche che il singolo circuito integrato era in grado di svolgere, divennero sempre più complesse e sofisticate le funzioni disponibili. Tuttavia il costo del dispositivo integrato aumentava con la sua complessità, e solo una sua larga diffusione permetteva l'abbattimento del costo di produzione, cosa non sempre possibile in quanto esso spesso era rivolto ad utilizzazioni specifiche. Nacque pertanto ben presto la necessità di disporre di un unico dispositivo che permettesse di essere utilizzato in applicazioni diverse con poche modifiche. Sulla base di queste motivazioni venne realizzato agli inizi degli anni '70 un circuito integrato cui fu dato il nome di microprocessore (C.P.U. = Central Process Unit). Tale dispositivo aveva bisogno di essere programmato, cioè era necessario fornirgli una successione ordinata di istruzioni sul modo di operare. Il microprocessore può quindi essere considerato un dispositivo adatto a svolgere funzioni diverse, modificando solo in modo minimo la parte circuitale ( hardware ) ad esso connessa, ma variando di volta in volta, in base all'applicazione richiesta, il programma di gestione ( software ). I sistemi realizzati con l'applicazione di una CPU sono detti a logica programmabile. Il microprocessore può essere utilizzato in una vastissima gamma di applicazioni che vanno dai sistemi di controllo, alla strumentazione di misura, dalle apparecchiature domestiche quali lavastoviglie e televisori a quelle elettromedicali, senza dimenticare una delle più diffuse applicazioni, ovvero il Personal Computer. Architettura di un sistema a microprocessore Un microprocessore in genere, pur contenendo al suo interno tutti i circuiti di calcolo e di controllo, per poter operare correttamente ha bisogno di essere collegato con altri dispositivi in base alle applicazioni per cui il processore è impiegato (da solo non è utilizzabile). Al microprocessore debbono essere collegati sia dei moduli di memoria che i dispositivi per l’ingresso e l'uscita dei dati. E dunque necessario parlare di sistema a microprocessore e non semplicemente di microprocessore. Nella figura è riportato un semplice schema di sistema con memorie e dispositivi di I/O. In esso è evidenziata la funzione di collegamento tra i vari componenti svolta dai bus dati, bus indirizzi e bus di controllo. Il sistema è costituito da una CPU da memorie RAM e ROM da uno o più dispositivi d'ingresso e d'uscita. Lo schema può servire per comprendere come il microprocessore possa scambiare informazioni con la memoria e con i dispositivi di I/O (Input / Output). Mediante il BUS INDIRIZZI vengono identificate le locazioni di memoria dove vengono memorizzati i dati nelle operazioni di scrittura o da cui la CPU riceve i dati (o i codici delle istruzioni, cioè il programma) nelle operazioni di lettura. Inoltre, sempre per mezzo del bus indirizzi il microprocessore identifica i dispositivi d'ingresso, ad esempio la tastiera di un computer, da cui riceve i dati nello operazioni di lettura da periferica o quelli d'uscita, ad esempio il monitor di un computer, a cui invia i dati nelle operazioni di scrittura su periferica. Il bus dati è utilizzato per il trasferimento (lettura o scrittura) dei dati dalla CPU con le locazioni di memoria o con i dispositivi di ingresso / uscita (detti anche periferici o dispositivi di I/O). Il bus controlli, oltre a funzioni specifiche che non saranno qui esplicitate, è utilizzato dalla CPU per segnalare alla memoria e ai dispositivi di I/O la direzione del flusso dei dati e il tipo di dispositivo coinvolto nel trasferimento (memoria o I/O). L’architettura a BUS presenta diversi vantaggi il principale dei quali è dato dalla modularità del sistema; è infatti particolarmente semplice ampliare il sistema con l’aggiunta di nuovi componenti collegandoli ai BUS. Tale vantaggio risulta evidente pensando alle schede madri dei Personal Computer, dove attraverso gli slot di espansione è possibile ampliare le prestazioni e le caratteristiche del sistema attraverso nuove schede (es: modem, acquisizione dati, porte parallela) o nuovi chip di memoria. I diversi “oggetti” sono collegati ai bus con una modalità che consenta di evitare conflitti e/o indecisioni nella comunicazione che avviene sempre tra due soli componenti del sistema e quasi sempre uno dei due è il microprocessore. BUS DATI (bidirezionale) Bus controllo Lo schema di figura (dove per semplicità si sono rappresentate solo 4 linee per bus) mette in evidenza i circuiti di decodifica che, generando dei segnali di chip select, permettono di selezionare i diversi dispositivi. I segnali CS permettono di togliere il componente dallo stato di alta impedenza (che corrisponde al virtuale scollegamento del componente dal bus dati) garantendo la corretta comunicazione solo tra due componenti. Altri segnali particolarmente importanti per la comunicazione provengono dal BUS CONTROLLI e sono i segnali di READ e WRITE, pilotati dalla CPU, e specificano la direzione dei dati nel trasferimento. Elementi caratteristici di una CPU La CPU costituisce il cuore e l’elemento più importante del sistema; in esso sono rappresentate le funzioni logiche, di controllo e aritmetiche. La CPU è in grado di prelevare le istruzioni del programma da svolgere contenute nella memoria (fase di fetch) e di interpretarle ed eseguirle (fase di execute). Tale istruzioni sono evidentemente in forma binaria. Grazie ai programmi compilatori è possibile scrivere programmi per Personal Computer utilizzando linguaggi particolarmente semplici ed intuitivi (linguaggi ad alto livello) che vengono poi tradotti nell’unico linguaggio interpretato ed eseguito dalla CPU che viene denominato linguaggio macchina. Le principali caratteristiche di una CPU che ne identificano le peculiarità e la potenza possono essere cosi sintetizzate: Set istruzioni, cioè l’insieme delle istruzioni che la CPU è in grado di svolgere; Velocità di funzionamento ed esecuzione delle istruzioni; si identificano tali caratteristiche attraverso il clock di sistema (frequenza di funzionamento passata negli anni da qualche MHz a centina di Mhz) e il MIPS (milioni di istruzioni eseguite in un secondo). Architettura interna, insieme dei registri di lavoro, coprocessore matematico per svolgere le operazioni logico aritmetiche di una certa complessità, memoria cache per il reperimento veloce delle istruzioni I registri sono memorie di pochi bit ( 8, 16, 32 o 64 ) contenuti fisicamente nella CPU che vengono utilizzati come memoria di transito per appoggiare provvisoriamente i dati ( indirizi, dati o istruzioni ). Essi consentono un funzionamento più efficiente della CPU, evitando l’accesso continuo alla memoria esterna che dovrebbe avvenire tramite i bus e ciò porterebbe ad un impiego maggiore di tempo. L'Unità di Controllo. è l'organo o unità che gestisce, controlla e presiede l'esecuzione di tutte le operazioni di elaborazione per il particolare programma da eseguire ovvero comanda tutte le altre parti del processore attraverso il pilotaggio dei componenti stessi (ALU ecc..) impartendo a questi comandi di input e facendo da supervisore; rappresenta la parte a logica sequenziale della macchina a stati generale che, a sua volta, rappresenta la logica elettronica generale del processore stesso. Ad essa spetta, ad esempio, l'interpretazione dell'istruzione che si trova di volta in volta nel registro IR; abilitare alla lettura ed alla scrittura due registri tra i quali deve avvenire uno scambio di informazione. L'unità di controllo scandisce i passi o stati di un'istruzione: 1. fetch: preleva l’ istruzione dalla memoria e determina il tipo di istruzione, i suoi argomenti 2. execute: esegue l’istruzione, memorizza i risultati, e torna a 1. Tutte le fasi del ciclo del processore avvengono attraverso l'invio ai vari componenti di un insieme di impulsi di controllo, in una sequenza temporale ben precisa. Più precisamente, ad ogni colpo di clock le linee di controllo assumono un particolare stato; il susseguirsi dei diversi stati contribuisce all'esecuzione completa di un'istruzione. Per questo motivo si può dire che una singola istruzione in linguaggio macchina viene eseguita attraverso la opportuna composizione di più micro-operazioni. ALU., l'unità logico-aritmetica, è l'organo deputato allo svolgimento di operazioni aritmetiche ( somma, sottrazione, moltiplicazione, divisione, cambio di segno ); operazioni logiche ( or, and, xor, not ); confronti; operazioni di scorrimenti e rotazione a sinistra e a destra di un dato;. Essa preleva gli operandi tipicamente dai registri generali, così come nei registri generali depone i risultati dei calcoli. In seguito ad un calcolo l'ALU ha anche il compito di impostare alcuni flag in modo da tenere traccia di determinati eventi (es. riporto di una somma). La Memoria. Contiene un numero generalmente molto elevato di celle nelle quali vengono memorizzati i dati e le istruzioni di un programma. Ogni cella è caratterizzata da un indirizzo ( intero positivo ) specificando il quale è possibile leggere o scrivere nella cella stessa. Il tempo impiegato per accedere ad una cella di memoria è costante e superiore a quello impiegato per l'accesso ad uno dei registri del processore. È per questo motivo che, per quanto possibile, si tenta di utilizzare i registri interni per effettuare le operazioni, limitando gli accessi in memoria allo stretto necessario. Pur contenendo la memoria un numero molto elevato di celle, in ciascun istante temporale solo uno di questi è abilitato a partecipare ad operazioni di lettura o scrittura: quello il cui indirizzo è contenuto nel registro MAR. Per sopperire alla lentezza della memoria RAM è stata introdotta anche la memoria cache. Internal Bus. È un canale di comunicazione principale condiviso dai vari componenti ed attraverso il quale essi possono dialogare scambiandosi informazioni quali comandi di input, output ecc.. In questo contesto, il dialogo consiste nello scambio di dati binari tra registri secondo una modalità parallela. Ciò significa che un certo numero di bit viene contemporaneamente trasferito attraverso il bus da un registro mittente ad un registro destinatario. Durante un'operazione di trasferimento, i due registri implicati nella comunicazione si trovano in uno stato di lettura (destinatario) e scrittura (mittente) in modo tale da poter acquisire il dato presente sul bus e da potercelo scrivere, rispettivamente. Tutti gli altri registri sono in uno stato di “riposo” nel quale non possono né leggere i dati che circolano sul bus né influenzare lo stato del bus con i dati che contengono. Il numero di bit contemporaneamente trasferiti indica il parallelismo del bus ed è pari al numero di bit contenuti in un singolo registro. Esso caratterizza anche il parallelismo interno del processore. Il Data Bus e il registro MDR. Il Data Bus è un bus che collega la memoria con il registro MDR ( Memory Data Register ). Esso serve a trasferire dati in entrambi i sensi, sempre secondo una modalità parallela. Tutti i dati e le istruzioni che dalla memoria devono essere elaborati nel processore, transitano inoltre attraverso il registro MDR e solo successivamente da questo raggiungono gli opportuni registri per l'elaborazione vera e propria. Analogamente, tutti i risultati (output) di un'elaborazione che devono essere immagazzinati in memoria transitano prima per il registro MDR e solo successivamente da esso raggiungono l'esatta posizione (cella) di memoria. L'Address Bus e il registro MAR. Durante un accesso alla memoria, sia in fase di lettura che in fase di scrittura, il registro MAR ( Memory Address Register ) contiene l'indirizzo della cella di memoria a cui si deve accedere. Questo indirizzo, trasferito all'organo memoria attraverso l'Address Bus, abilita alla comunicazione una sola tra tutte le locazioni di memoria (celle) disponibili. Il registro PC. (Program Counter). Contiene l’indirizzo di memoria in cui è contenuta la prossima istruzione da eseguire. Esso viene interrogato tipicamente all'inizio di ogni fase di fatch ed immediatamente dopo viene aggiornato alla posizione di memoria “seguente” preparandolo così per il prelievo dell'istruzione successiva. Può accadere comunque che l'istruzione prelevata rientri nella categoria delle istruzioni di salto: in questo caso si procede ad un ulteriore aggiornamento del PC durante la fase di execute dell'istruzione. Da questo deriva che lo scopo di un'istruzione di salto (condizionato) è esclusivamente quello di alterare (eventualmente) il valore del PC. Spesso il registro PC è chiamato anche IP (Instruction Pointer). Il registro IR ( Instruction Register ). Questo registro ha il compito di accogliere dalla memoria (attraverso il MDR), durante una fase di fetch, l'istruzione da eseguire, quella cioè puntata dal PC. Una volta in questo registro, l'istruzione deve essere interpretata dall'unità di controllo per procedere alla eventuale fase di preparazione degli operandi ed alla fase di esecuzione. Il registro di stato o dei flag è un registro che memorizza una serie di bit detti flag indicativi dello stato corrente del processore. Ad esempio: ZF = Zero flag (o flag zero). Indica se il risultato di un'operazione matematica o logica è zero. CF = Carry flag (o flag di riporto). Indica se il risultato di un'operazione produce una risposta non contenibile nei bit usati per il calcolo. SF = Sign flag (flag di segno). Indica se il risultato di un'operazione matematica è negativo. OF = Overflow flag. Indica se il risultato di un'operazione è in ovwrflow, secondo la rappresentazione in complemento a 2. È simile al carry flag, ma viene impiegato nelle operazioni in cui è presente il segno degli operandi. IF = Interrupt enable flag (o flag di abilitazione dell'interruzione). Gli interrupt possono essere abilitati ponendo a 1 questo flag, se 0 allora sono disabilitati. PF = Parity flag (o flag di parità). Indica se il numero di bit del risultato è pari o dispari. I registri generali I registri generali non hanno un preciso ruolo come gli altri, e da ciò scaturisce il loro nome. Sono utilizzati per contenere i dati in transito per un'elaborazione: gli addendi di un'addizione che l'ALU sta per effettuare, il risultato di un calcolo che l'ALU ha effettuato, un indirizzo di memoria in cui si trova un dato che dovrà essere acceduto in seguito, ecc. Un numero elevato di tali registri conferisce maggiore flessibilità nella programmazione, ma complica la struttura del processore dal punto di vista architetturale. Modalità di esecuzione di un’istruzione Dopo aver esaminato le caratteristiche degli elementi che compongono un generico sistema a microprocessore vediamo come essi siano coinvolti nell’esecuzione di una istruzione. Una generica istruzione è formata: Il codice operativo rappresenta l’azione che la CPU deve eseguire sugli operandi ( registri o dati ). Ad esempio 0110 mov ax, 30 0111 0112 inc ax 3E 30 3C ; prima istruzione ; seconda istruzione Il codice operativo della prima istruzione è 3E mentre il campo dati è il numero 30 e nell’ insieme l’istruzione è lunga 2 byte. La seconda istruzione è lunga 1 byte . Nel complesso la prima istruzione pone nel registro a 16 bit AX il dato 30, la seconda lo incrementa di 1. Dopo l’esecuzione della seconda istruzione in AX avremo 31. Fase di fetch della prima istruzione La CPU deve prelevare la prima istruzione il cui indirizzo 0110 è nel registro PC quindi: 1. Il contenuto di PC 0110 viene posto sul bus indirizzi 2. Il contenuto della cella 0110 viene posto sul bus dati; 3. L’istruzione trasferita nel registro IR viene decodificata, permettendo alla logica di controllo di generare i segnali necessari a eseguire effettivamente l’istruzione; 4. Il contenuto di PC viene incrementato di 1 ( il PC “punta” al dato 30 ) Fase di execute della prima istruzione 1. Il contenuto del PC (0111) viene posto sul bus indirizzi, “puntando così alla cella che contiene l’operando 30; 2.Il contenuto della cella 0111 viene posto sul bus dati; 3.il dato dal bus dati viene trasferito in AX Fase di fetch della seconda istruzione 1. Il contenuto di PC (0112) viene posto sul bus indirizzi; 2.Il contenuto della cella 0112 viene posto sul bus dati; 3. L’istruzione trasferita nel registro IR viene decodificata, permettendo alla logica di controllo di generare i segnali necessari a eseguire effettivamente l’istruzione; 4.Il contenuto di PC viene incrementato di 1 ( quindi punta alla terza istruzione); Fase di execute della seconda istruzione 1. Dal bus dati 30 viene trasferito in AX 2. La ALU incrementa di 1 il contenuto del registro AX Classificazione memorie Le memorie sono organizzate in byte ( 8 bit ) ognuno dotato di indirizzo rappresentato da un numero intero positivo. Per poter leggere o scrivere ( accedere ) ad un byte è necessario specificare prima l’indirizzo del byte a cui si vuole accedere. A volte si parla di parola o word costituite da 2 byte. La capacità della memoria è il numero massimo di byte che essa può contenere. Il tempo di lettura è l’intervallo di tempo che intercorre fra l’inizio dell’indirizzamento del byte che si vuole leggere a quando il dato risulta disponibile. Il tempo di scrittura è l’intervallo di tempo che intercorre fra l’inizio dell’indirizzamento del byte che si vuole scrivere a quando il dato viene scritto in memoria. Il tempo di accesso è una media fra il tempo di scrittura e quello di lettura. Le memorie possono essere volatili e non volatili: le prime perdono i dati contenuti in mancanza dell’alimentazione le seconde no. Un’ altra possibile classificazione è: ROM ( read only memory ) vengono scritte quando sono fabbricate e non possono essere più cancellate. PROM ( programmable ROM ) possono essere scritte dall’ utente tramite dei programmatori appositi e da quel momemnto diventano ROM. EPROM ( erasable PROM ) programmabili e cancellabili più volte. EEPROM ( electrically erasable programmable ROM ) è un tipo di memoria non volatile cancellabile elettricamente RAM ( random access memory ) sono memorie volatili. Gestione dei dispositivi di input / output Un sistema a microprocessore deve eseguire programmi più o meno complessi, trasferire dati alle periferiche esterne e ricevere da queste informazioni. Durante l'esecuzione dei programmi vi è anche un flusso continuo di dati dalla CPU alle memorie e viceversa. Il video, la tastiera, la stampante, il plotter, il drive ecc., sono gli esempi più noti di dispositivi periferici. In molti casi il trasferimento dei dati dalla CPU alle periferiche e alle memorie deve rispettare procedure ben precise e codificate. Le tecniche di scambio dei dati tra il sistema e le periferiche possono essere classificate in: polling, interruzione e DMA L’utilizzazione di una tecnica piuttosto che un'altra richiede una attenta analisi delle procedure software e dei dispositivi hardware al fine di valutare quale sia la tecnica che aumenta l’efficienza del sistema a microprocessore e che meglio si adatti alle specifiche di progetto. Polling ( interrogazione ciclica ) È la tecnica più semplice per lo scambio di informazioni tra microprocessore e periferiche perché è essenzialmente software. Prevede che la CPU sotto il controllo di un apposito programma, controlli in ciclicamente lo stato delle periferiche ad essa collegate. Se il flag è attivo (generalmente a livello logico basso), la CPU esegue un programma di servizio al termine del quale verifica lo stato del flag della seconda periferica e così via. Naturalmente la CPU passa immediatamente a controllare il flag della seconda periferica se quella interpellata non ha bisogno dell'attenzione della CPU. Questa tecnica di gestione delle periferiche presenta alcuni problemi: la CPU è impegnata continuamente a verificare i flag; non si ha la possibilità di definire una gerarchia se due richieste arrivano contemporaneamente; se una periferica appena interrogata ha bisogno di essere servita dalla CPU deve aspettare, prima di essere nuovamente interrogata, perchè la CPU deve verificare lo stato di tutte le rimanenti periferiche, e soddisfare le eventuali richieste di servizio da esse avanzate. Ed è anche possibile che i dati inviati dalla periferica vengano, nel frattempo, persi. Interruzione ( Interrupt ) Per superare le limitazioni della tecnica del polling, la gestione dello scambio di informazioni tra CPU e periferiche è spesso affidata alla tecnica dell'interruzione, la quale prevede che sia la periferica, a differenza di quanto avviene nel polling, ad avanzare la richiesta di servizio alla CPU. In tal modo la periferica può interrompere la CPU in qualsiasi momento, in relazione alle esigenze specifiche, senza dover attendere i tempi dell’interrogazione ciclica. Tutte le CPU sono provviste, a seconda della complessità della loro architettura di uno o più ingressi predisposti per ricevere il segnale di richiesta di interruzione da parte della periferica. Il flow chat che segue descrive le varie fasi che seguono la richiesta di interruzione. L’ interruzione non viene servita immediatamente ma solo alla fine della fase di esecuzione dell’istruzione in corso, prima quindi che inizi la fase di fetch dell’istruzione successiva. 1. viene interrogato l’ IF nel registro dei flag per verificare se sono abilitate le interruzioni. 2. viene salvato il contesto ( registri, PC, PSW ). 3. viene riconosciuta l’interruzione, cioè quale periferica l’ha inviata e quindi viene prelevato in un’opportuna zona di memoria l’indirizzo della ISR ( interrupt service routine ) associata al dispositivo. 4. viene eseguita la ISR associata al dispositivo. 5. viene quindi ripristinato il contesto, cioè vengono ricaricati i registri salvati in precedenza. Fra i registi vi è il PC ( program couter ) che sappiamo contenere l’indirizzo dell’istruzione successiva, di conseguenza viene ripresa l’esecuzione del programma originario, all’istruzione seguente a quella durante la cui esecuzione è arrivata l’interruzione. Il problema dell’interruzione è che oltre il tempo necessario alla gestione dell’I/O abbiamo anche i tempi relativi al salvataggio e al ripristino del contesto. Questi tempi sono piccoli ma in presenza di molte interruzioni con grosse quantità di dati possono “pesare” notevolmente. DMA (Direct Memory Access – accesso diretto in memoria) Nella tecnica DMA la periferica richiede alla CPU il controllo dei BUS per gestire in modo autonomo, senza cioè l’intervento del microprocessore, il trasferimento dei dati con le memorie. In pratica nella tecnica DMA un dispositivo di supporto, detto DMA controller, regola il flusso dei dati dalla periferica alla memoria e viceversa. La tecnica DMA è utilizzata quando bisogna gestire i trasferimenti di blocchi di dati ad alta velocità perché, a differenza di quelli gestiti da programma, non sono necessarie operazioni di lettura e scrittura. Microcontrollori e PLC Con il termine microcontrollore si intende comunemente un sistema a microprocessore integrato su un unico chip, che comprende, oltre alla CPU, una memoria di programma, solitamente EPROM o EEPROM, una memoria RAM, generalmente di dimensioni ridotte, per i risultati intermedi dell'elaborazione e per lo stack e periferici di I/O vari (porte seriali ,contatori, timer, convertitori analogico digitali ecc.); per quanto appena esposto questi circuiti integrati vengono anche detti microcomputer single chip. Con queste caratteristiche, dovrebbe essere evidente che i microcontrollori sono stati concepiti soprattutto per applicazioni industriali di controllo, in cui il programma di gestione, una volta messo a punto non ha più necessità di essere modificato (o di esserlo raramente). Sono le applicazioni che gli americani chiamano “embedded”, cioè incorporate in prodotti e apparati finiti, che possono andare dagli elettrodomestici intelligenti, ai sistemi di comunicazione o sicurezza, alla strumentazione, all'automazione in campo automobilistico, ecc In questo senso si vogliono qui ricordare ad alcuni microcontrollori particolarmente diffusi quali la famiglia PIC (il 16C84 costituisce importante elemento delle PlayStation), la famiglia ST o le famiglie prodotte da Motorola, Siemens, Atmel e da diversi altri importanti produttori che hanno progettato e realizzati microcontrollori dedicati a loro applicazioni. I microcontroller PIC (acronimo per Programmable. Integrated Controller) si distaccano dalla struttura di un microprocessore classico, essenzialmente perché sono delle CPU RISC ( Reduced Instruction Set Computing, elaborazione con insieme di istruzioni ridotto) basate su una struttura del tipo Harward (dall'Università dove è stata sviluppata), che si distingue dalla macchina di Von Neuman classica per avere memoria programma e memoria dati (e relativi bus) separati. La filosofia RISC consiste sostanzialmente nel prevedere poche e semplici istruzioni, tutte della stessa lunghezza e (possibilmente) tutte richiedenti lo stesso numero di cicli macchina sta per il fetch che per l'esecuzione. Questa caratteristica unita alla separazione fisica dei canali lungo cui fluiscono istruzioni e dati, permette di ottenere una sovrapposizione (Pipelining) delle fasi di Fetch di un'istruzione con quella di esecuzione della precedente “senza buchi" e quindi in modo molto più efficiente di quello che, per esempio, si ha in termini di velocità complessiva dell'elaborazione nei microprocessori della Famiglia 8086, dove istruzioni diverso hanno tempi diversi sia di fetch che di esecuzione. Da un punto di vista delle applicazioni di potenza in impianti industriali e civili hanno avuto larga diffusione i PLC, Controllori Logici Programmabili, che al pari dei microcontrollori hanno la caratteristica della semplice riprogrammabilità per ottenere le più svariate funzioni attraverso le linee (in genere a 24 (v) ed in numero variabile da modello a modello) di segnali I/O. SISTEMI OPERATIVI Introduzione Un Sistema Operativo ( SO ) è un insieme di programmi che gestiscono le risorse sia hardware che software del sistema di elaborazione rendendone più semplice l’uso (supporto per l’utente) ed ottimizzano l’utilizzazione delle stesse (gestore di risorse). In particolare il SO è • Un sistema per gestire l’interazione utente-computer; • Un’interfaccia fra le applicazioni e le risorse del computer • Un sistema per gestire e condividere le risorse; • Un sistema per ottimizzare l’uso delle risorse; • Un fornitore di servizi allo sviluppo del software e all’amministrazione del sistema. L’utente interagisce con il SO attraverso un’interfaccia denominata shell. La shell può essere "a linea di comando", tipo MS-DOS, o "grafica" ( GUI -Graphics User Interface ), tipo Windows. Il SO vero e proprio è formato dal nucleo o kernel che gestisce le funzioni principali e gli altri moduli del SO stesso. In base alle modalità di funzionamento un SO può essere: SO Mono Tasking, Mono Utente ( un solo programma, un solo utente ), tipo MS-DOS SO Multi Tasking, Mono Utente (più programmi, un solo utente), multiprogrammazione, tipo Windows. Evoluzione dei sistemi Multi Tasking sono i sistemi Multi Threading (ogni processo è costituito da uno o più thread che possono essere eseguiti contemporaneamente). Windows appartiene a questa categoria di sistemi operativi. SO Multi Tasking, Multi Utente ( più programmi, più utenti) , multiprogrammazione, tipo Unix/ Linux. SO in Tempo Reale (Real Time), per il controllo dei processi industriali, non interagiscono con l’utente, ma con i dispositivi che controllano il processo SO a Macchine Virtuali, che permettono di simulare hardware e/o sistema operativo diverso (computer virtuale), ad esempio la finestra DOS di Windows SO di Rete per la gestione delle LAN (reti locali), ad esempio Windows Server 2003 Esamineranno alcune caratteristiche di un generico SO in multiprogrammazione e multiutente. Perché sia possibile la multiprogrammazione occorre che la CPU sia svincolata dall’esecuzione delle operazioni di I/O ( input / output ), delle quali si occuperanno appositi processori di I/O ( canali, DMA ) che lavorano in parallelismo effettivo con la CPU. La CPU (processore centrale) può così occuparsi esclusivamente dell’avanzamento dei processi. Funzioni e struttura di un sistema operativo Un SO nell’eseguire i suoi compiti si prefigge obiettivi di convenienza, efficienza e versatilità. Il SO deve inoltre ottimizzare l’uso delle risorse e interfacciare SO con le applicazioni. Le principali risorse del sistema operativo sono: Processore ( CPU ) Memoria centrale Dispositivi di I/O Informazioni Per gestire le risorse dispone di strutture dati e programmi per Tenere conto dello stato delle risorse, ad esempio se c’è memoria libera per collocare un programma; Avere una politica per operare scelte nell’allocazione delle risorse, ad esempio quale programma mandare in esecuzione fra quelli in memoria; Avere metodi per allocare e togliere le risorse. Un SO è quindi un sistema software complesso; di seguito è mostrata una sua rappresentazione del SO collegata al suo compito di gestore di risorse. L’idea alla base della strutturazione di un SO a livelli o strati è la seguente Ogni livello assolve a compiti specifici utilizzando i servizi offerti dallo strato inferiore e offrendo servizi al livello superiore; Ogni livello è visto come una macchina virtuale in grado di fornire determinati servizi; Ogni livello opera sulla macchina virtuale del livello inferiore e costruisce una macchina virtuale più potente al livello superiore. Processi e risorse Un processo è l’insieme delle azioni compiute dal processore per eseguire un programma. Si può semplificare dicendo che "un processo è un programma in esecuzione". Per meglio definire un processo analizziamone la differenza con il programma. Un programma è costituito dalle istruzioni scritte dal programmatore e tradotte in linguaggio macchina e viene visto come un’insieme di bit presenti sul disco rigido che non cambia nel tempo e non modifica lo stato della macchina. Un processo invece modifica lo stato della memoria e dei registri. In generale potremo dire che: Un processo è un’entità dinamica il cui stato è determinato dai valori assunti dal registro Program Counter, dai registri generali, dallo stato della memoria, dallo stack e dall’ SR; Un processo ha una traccia d’esecuzione, che può essere definita come la sequenza degli stati assunti dal processore durante l’avanzamento dei processi. Ad un processo sono associati il codice, l’area dati, i registri e le altre risorse ad esso assegnate. La risorsa è un oggetto hardware e software che serve per far avanzare un processo dallo stato di new a quello di terminated. È compito del SO, oltre all’avanzamento dei processi, la gestione delle risorse. Le risorse possono essere consumabili ad esempio i messaggi scambiati tra processi; riusabili quelle che vengono date in uso ai processi e controllate dal gestore di risorse del SO. Ci sono risorse che possono essere usate da un solo processo alla volta e sono dette seriali ( CPU, stampante,...). Spesso può essere utile costringere il processo a rilasciare la risorsa ( prerilascio ). Lo scheduling è l’algoritmo di gestione della risorsa, che consente la scelta di assegnare o togliere una risorsa al processo che l’ha richiesta. Stati di un processo Un processo, dopo la sua creazione ed attivazione, può trovarsi in diversi stati di avanzamento: Attesa di caricamento ( new ) è stata richiesta l’esecuzione del programma memorizzato su disco, attende di essere caricato in memoria centrale; più processi in attesa di caricamento. Pronto ( ready ) il processo è in memoria centrale ed attende che gli venga assegnata la CPU; più processi in stato di pronto Esecuzione ( running ) la CPU esegue il processo; un solo processo in esecuzione Waiting ( in Attesa ) il processo ha richiesto una operazione di I/O e ne attende il completamento; più processi in attesa; spesso le operazioni di I/O vengono eseguite dai processori di I/O in parallelismo effettivo con la CPU Terminazione ( terminated ) il processo ha terminato l’esecuzione può rilasciare le risorse utilizzate. Se non consideriamo la freccia tratteggiata, lo schema illustra un sistema operativo in multiprogrammazione "a semplice richiesta di I/O": un processo lascia lo stato di running per richiesta di I/O o per terminazione. Ciò, in alcune situazioni, può causare il blocco dell’intero sistema (se, ad esempio, si esegue un ciclo infinito per errore del programmatore all’interno del quale non sono presenti istruzioni di richiesta di I/O). Per ovviare a questo inconveniente occorre "costringere" il processo al prerilascio della CPU. Quando un processo passa da Pronto ad Esecuzione ( gli viene data la CPU ) si assegna ad esso un "quanto di tempo" ( time slice ) di qualche decina di millisecondi scaduto il quale, se ancora in esecuzione, il processo viene costretto a rilasciare la CPU. Un sistema di questo tipo ( tutti i moderni sistemi operativi in multiprogrammazione ) è un sistema time-sharing ( a suddivisione di tempo). Windows è un SO time-sharing, monoutente, multithreading. Linux è un SO time-sharing, multiutente, multitasking. Si definisce grado di multiprogrammazione la somma del numero di processi in stato di ready, wait e run. Descrittore del processo Ogni processo ha associato un proprio descrittore di processo ( o PCB. – Process Control Block) che viene creato in fase di "creazione" del processo e mantenuto in memoria centrale, in un’area riservata al sistema operativo, per tutta la durata del processo. Tale descrittore contiene: PID ( Process Identification) , un numero progressivo che identifica il processo, assegnato dal SO al momento della creazione del processo; PPID ( Parent Process Identification ) cioè il PID del processo padre cioè il processo che l’ha generato; puntatore alla lista dei processi figli ; stato del processo ; priorità; limiti della memoria centrale utilizzata; puntatore alla lista delle risorse in uso; copia del contesto del processo ( PC, IR, MAR, MDR, registri generali, …). Quando un processo rilascia la CPU nel suo PCB viene salvato il contenuto dei registri della CPU. Quando a un processo viene data la CPU la copia dei registri contenuta nel suo PCB viene copiata nei registri fisici della CPU. Ciò consente al processo di riprendere l’esecuzione dal punto in cui era stata sospesa, dall’istruzione puntata dal PC al momento della sospensione. Per Creare un processo occorre assegnare un’area di memoria centrale, copiare il programma in tale area, creare il suo PCB, porre il processo in stato di "pronto". Per Terminare un processo occorre liberare la memoria centrale allocata, rilasciare tutte le risorse da esso utilizzate, aggiornare il PCB del processo che l’ha creato. Scheduling della CPU Esistono diverse tecniche di scheduling della CPU ( scheduling a basso livello ), cioè politiche di assegnamento della CPU a uno dei processi in stato di Ready. Lo scheduling della CPU viene effettuata dal modulo del sistema operativo denominato Dispatcher. L’obiettivo dello scheduling della CPU è la massimizzazione dell’utilizzo della CPU, questo si ottiene assegnando al processore processi che sono pronti per eseguire istruzioni. Lo scheduler a breve termine seleziona uno tra i processi in memoria pronti per essere eseguiti e lo assegna alla CPU. Lo scheduler interviene quando un processo: 1. passa dallo stato di running a quello di waiting 2. passa dallo stato di running a quello di ready 3. passa dallo stato di waiting a quello di ready 4. Termina Qualunque sia la strategia di scheduling essa si deve proporre di ottimizzare differenti parametri: 1. massimizzare la percentuale di utilizzo della CPU; 2. massimizzare il throughput, cioe il numero di processi terminati nell’unità di tempo; 3. minimizzare il sovraccarico del processore nell’eseguire processi di sistema e non utente; 4. minimizzare il tempo che passa dall’ inizio di un processo alla sua fine (tempo di turnround) ; 5. prevenire starvation situazione in cui un processo non viene mai eseguito; 6. minimizzare il tempo di risposta dei processi interattivi, tempo impiegato a fornire il primo output. Gli algoritmi di schedulazione possono essere classificati secondo due categorie: Schedulazione senza prerilascio cioè un processo in stato di running deve necessariamente proseguire fino allo stato di terminato. Schedulazione con prerilascio cioè un processo in stato di running, dopo un certo tempo assegnatogli ( time slice o quanto di tempo ) passa in stato di ready, ovviamente se non intervine prima una richiesta dio I/O che lo porterebbe in stato di waiting. L’esigenza che accomuna tutti gli algoritmi di scheduling è che siano veloci per minimizzare il sovraccarico della CPU. Schedulazione First-Come First-Served ( FCFS ) Algoritmo senza prerilascio che prevede che il processo arrivato per primo debba essere eseguito per primo. Le conseguenze di un algoritmo come questo che tiene in conto solo l’ordine di arrivo sono: Viene penalizzato un processo breve che arriva dopo una serie di processi lunghi; Vengono favoriti i processi che fanno largo uso della CPU ( CPU bound ) rispetto a quelli in cui prevale l’I/O ( I/O bound ). veloce Schedulazione Shortest Job First ( SJF ) Algoritmo senza prerilascio che prevede che la coda dei processi ready sia ordinata per valori crescenti di durata. In questo modo il primo ad essere eseguito è il processo di durata minore fra quelli in attesa. I processi brevi sopravanzano quelli lunghi con il rischio per quest’ultimi di starvation; Il principio di favorire i processi brevi è anche alla base dell’idea di privilegiare i processi I/O bound rispetto a quelli CPU bound, permettendo anche agli altri di essere serviti. Infatti un processo I/O bound può esser visto come una serie di brevi processi che rilasciano la CPU per fare I/O; SJF è il migliore per ridurre i tempi di attesa ma è di difficile utilizzo data la difficoltà di misurarne la durata, infatti viene usato nella schedulazione dei processi batch. Schedulazione a priorità La priorità è un parametro che indica l’importanza o l’urgenza che viene assegnata ad un determinato processo. Questo algoritmo con prerilascio prevede che il primo processo ad essere eseguito è quello a priorità più alta. In sostanza differisce dai precedenti oltre che per il prerilascio, per il fatto che la coda dei processi ready è ordinata in base alla priorità. Si ha anche un prerilascio quando arriva nella coda di ready un processo a priorità più alta. Schedulazione Round Robin E’ un algoritmo di schedulazione FCFS con prerilascio. Schedulazione Multilevel Feedback I processi "pronti" vengono inseriti in N code. La prima ha priorità massima e time slice minimo, l’ultima ha priorità minima e time slice massimo (da coda 1 a coda N la priorità diminuisce e lo slice aumenta). I processi della coda i-esima vengono eseguiti quando le code precedenti sono vuote. Un processo che lascia la CPU per fine time slice viene inserito nella coda seguente a quella dalla quale era stato prelevato ( diminuisce la priorità ed aumenta il time slice ). Un processo che entra in "ready" per fine I/O o per caricamento viene inserito nella prima coda, quella a priorità maggiore. Questo meccanismo favorisce i processi con alto tasso di I/O, cioè i processi interattivi. Overhead di sistema L’assegnazione della CPU a un nuovo processo comporta il salvataggio contesto del vecchio processo, la scelta del processo da avanzare e il caricamento del contesto di questo nuovo processo. Il tempo di cambio di contesto ( context-switch time ) è l’overhead di sistema. Gli algoritmi di scheduling debbono essere efficienti, ma non eccessivamente complessi, così da minimizzare il consumo di CPU da parte dell’algoritmo di scheduling stesso (e minimizzare l’overhead di sistema). Questo vale per tutti i processi del sistema operativo. Interruzioni Il passaggio dei processi da uno stato all’altro, durante il loro avanzamento, è causato dal verificarsi di eventi. A parte la richiesta di I/O, che viene fatta dal processo in esecuzione, gli altri eventi sono esterni alla CPU. Per segnalare tali eventi è previsto il meccanismo delle interruzioni (o interrupt). Una interruzione può essere • un segnale hardware che viene inviato dai vari dispositivi e periferiche alla CPU; • software con la quale il processo richiede al sistema operativo una operazione di I/O. Quando arriva una interruzione alla CPU si ha l’automatica sospensione del processo in esecuzione sulla CPU (con il salvataggio del suo PCB) e l’attivazione di una routine di gestione interruzioni (caricata in memoria centrale, nell’area riservata al sistema operativo, all’accensione del computer). Tale routine, in base al tipo di interruzione arrivata, prende i provvedimenti del caso. Ad esempio, se l’interruzione segnala la fine di un I/O individuerà il processore di I/O che l’ha inviata ed il processo per il quale è stata inviata cioè il processo che ha terminato l’I/O -, lo metterà nello stato di pronto e riprenderà l’esecuzione del processo che aveva temporaneamente sospeso; se l’interruzione segnala la fine del time-slice metterà il processo sospeso in pronti ed attiverà lo scheduling per scegliere il prossimo processo da avanzare; etc. Il flow chat che segue descrive le varie fasi che seguono la richiesta di interruzione. L’ interruzione non viene servita immediatamente ma solo alla fine della fase di esecuzione dell’istruzione in corso, prima quindi che inizi la fase di fetch dell’istruzione successiva. 1. viene interrogato l’ IF nel registro dei flag per verificare se sono abilitate le interruzioni. 2. viene salvato il contesto ( registri, PC, PSW ). 3. viene riconosciuta l’interruzione, cioè quale periferica l’ha inviata e quindi viene prelevato in un’opportuna zona di memoria l’indirizzo della ISR ( interrupt service routine ) associata al dispositivo. 4. viene eseguita la ISR associata al dispositivo. 5. viene ripristinato il contesto, cioè vengono ricaricati i registri salvati in precedenza. Fra registi vi è il PC (program couter) che sappiamo contenere l’indirizzo dell’istruzione successiva, di conseguenza viene ripresa l’esecuzione del programma originario, all’istruzione seguente a quella durante la cui esecuzione è arrivata l’interruzione. Il problema dell’interruzione è che oltre il tempo necessario alla gestione dell’I/O abbiamo anche i tempi relativi al salvataggio e al ripristino del contesto. Questi tempi sono piccoli ma in presenza di molte interruzioni con grosse quantità di dati possono “pesare” notevolmente. Gestione delle Risorse Una risorsa, sia essa hardware ( memoria, disco, cpu,…) o software ( file, PCB,…), è una qualche cosa necessaria al processo per avanzare verso lo stato di terminato. Una risorsa può essere assegnata: in modo statico per tutta la durata del processo; in modo dinamico viene assegnata, usata e rilasciata dopo l’utilizzo da parte del processo; se il processo può essere costretto anche a rilasciare la risorsa, allora la risorsa è detta risorsa prerilasciabile come la CPU. Il nucleo (o Kernel) del sistema operativo si occupa, della gestione della CPU, scheduling della CPU e dell’avanzamento dei processi. La gestione delle altre risorse è effettuata da appositi gestori delle risorse quali il gestore della memoria centrale, il gestore delle periferiche, e il gestore delle informazioni (o File System). Due processi si dicono disgiunti se sono completamente indipendenti l’uno dall’altro, interagenti se accedono a risorse comuni. L’interazione fra processi può essere : • diretta ( cooperazione ) due processi per avanzare si scambiano messaggi; • indiretta ( competizione ) due processi “competono” per l’accesso ad una stessa risorsa. Mutua esclusione In genere una risorsa riusabile e non prerilasciabile può essere assegnata ad un solo processo per volta, in mutua esclusione cioè se un processo P ha la risorsa R, nessun altro può utilizzarla finché P non la rilascia, garantendo un tempo di attesa finito ed evitando situazioni di stallo ( o deadlock ). La mutua esclusione va garantita, pena il verificarsi di errori non controllabili. Competizione tra processi ( interazione indiretta ) Primitive Lock e UnLock (attesa attiva) Per garantire la mutua esclusione ad una risorsa si associa a questa un semaforo binario che può valere • 0 "semaforo verde", cioè risorsa libera • 1 "semaforo rosso", risorsa occupata. La richiesta della risorsa viene fatta con la primitiva Lock che verifica il semaforo: se il semaforo è a 0 lo pone a 1 ed assegna la risorsa al processo, altrimenti resta in attesa che il semaforo diventi 0 ( verificandolo ad intervalli regolari ). Questo meccanismo, che prevede il test ripetuto del semaforo ( attesa attiva ) garantisce la mutua esclusione. Il processo P che utilizza la risorsa sarà : ………… Lock ( s ); ……… // questa è la porzione di codice in cui viene usata la risorsa R ……… // a cui è associato il semaforo s ( detta sezione critica ) UnLock ( s ); Questo sistema ha due gravi inconvenienti: 1. attesa attiva : la Lock() impegna inutilmente il processore a scapito di altri processi; 2. rinvio indefinito: un processo può attendere indefinitamente il semaforo verde anche se questo evento si è verificato infinite volte. Infatti se il semaforo diventa verde prima che venga riesaminato ed un altro processo chiede la risorsa, la ottiene, rimettendo il semaforo a rosso; quando si va a testare il semaforo lo si trova ancora rosso, ma nel frattempo la risorsa è stata data ad un alto processo; in teoria questo potrebbe verificarsi per un tempo indefinito. Primitive wait e signal ( attesa passiva ) Con questo meccanismo a ciasuna risorsa viene associata • semaforo s • Q(s) una coda destinata a contenere i PID dei processi che l’hanno richiesta. La richiesta della risorsa viene fatta con la primitiva wait che testa il semaforo: se il semaforo è a 0 lo pone a 1 ed assegna la risorsa al processo, altrimenti pone il PID del processo nella coda di attesa e pone in wait il processo. Il rilascio della risorsa viene fatto con la primitiva signal che controlla se la coda di attesa è vuota: se si pone il semaforo a verde, altrimenti lo lascia a rosso ed assegna la risorsa al primo processo della coda. Questo meccanismo di attesa passiva garantisce sia la mutua esclusione che l’attesa finita. Cooperazione tra processi ( interazione diretta ) Soluzione tramite la tecnica del produttore-consumatore Un processo produttore genera dati e li mette in un buffer , da dove li preleva il processo consumatore. Osserviamo che : il produttore deve accertarsi che il dato nel buffer sia stato prelevato, prima di inserirne un altro; il consumatore deve accertarsi che il produttore abbia inserito un nuovo dato prima di prelevarlo In sintesi i processi devono essere "sincronizzati", cioè la "produzione" di un messaggio da parte del processo trasmittente deve avere sempre lo stesso tempo del "consumo" da parte del processo ricevente. La sincronizzazione viene fatta tramite le primitive P e V e altri due semafori "prelevato" inizializzato a verde, utilizzato dal consumatore per segnalare lo svuotamento del buffer; "depositato" inizializzato a rosso, utilizzato dal produttore per segnalare la presenza nel buffer di nuovi dati. Il produttore, dopo aver depositato un dato nel buffer, non può scrivere nel buffer sino a quando il consumatore non ha prelevato il dato e, analogamente, il consumatore non può usare più volte il dato inserito nel buffer. Per queste ragioni l’accesso al buffer avviene in mutua esclusione. Stallo (deadlock) Lo stallo si ha quando due o più processi che posseggono risorse e, per avanzare necessitano di ulteriori risorse possedute da altri processi che non le possono rilasciare. L’avere più programmi che competono per l’uso delle risorse può causare situazioni di stallo (deadlock), situazioni nelle quali i processi non possono più avanzare. Le condizioni per le quali si può verificare lo stallo sono: • necessità di garantire la mutua esclusione; • attesa di risorse aggiuntive (i processi richiedono le risorse una alla volta e non tutte insieme); • non prerilascio delle risorse ottenute; • verificarsi di una situazione di "attesa circolare". Ad esempio il processoP1 ha in uso la risorsa R1che rilascerà solo dopoaver ottenuto la risorsaR2; P2 ha in uso R2 cherilascerà solo dopo averottenuto R1: STALLO!Per ovviare allo stallo ci sono alcune diverse tecniche: la Prevenzione dello stallo ( deadlock prevention ) consiste nell’invalidare una o più condizioni di stallo (ad esempio si obbligano i processi a chiedere le risorse tutte insieme); in genere si previene l’attesa circolare con l’allocazione gerarchica delle risorse la Fuga dallo stallo ( deadlock avoidance ) permette di rifiutare la richiesta di risorsa che causa (o potrebbe causare) stallo; l’algoritmo più noto di questa strategia è l’algoritmo del banchiere il Riconoscimento e recupero dello stallo ( deadlock detection and recovery ) non previene lo stallo, ma riconosce quando esso avviene (ad esempio esaminando periodicamente il grafo di allocazione delle risorse) e lo elimina (ad esempio forzando uno dei processi a terminare). Gestione della memoria Ogni processo, per avanzare, necessita che il relativo programma risieda in memoria centrale ( immagine del processo ). Tale programma, in linguaggio macchina, è costituito da istruzioni e dati. Lo spazio fisico è l’insieme degli indirizzi accessibili fisicamente e costituisce la memoria fisica;lo spazio logico è uno spazio astratto di indirizzi che vengono assegnati al momento della traduzione del programma e costituisce la memoria logica. Il compilatore al momento della traduzione del programma assegna agli identificatori e alle istruzioni degli indirizzi logici come se il programma debba esser caricato a partire dalla locazione 0, ma durante l’esecuzione tali indirizzi debbono essere fisici ( l’indirizzo fisico della prima istruzione sarà l’indirizzo della locazione di memoria a partire dalla quale è stato caricato il programma ). Occorre quindi trasformare gli indirizzi logici in indirizzi fisici ( o assoluti ). Questa "traduzione" è detta Mapping o Rilocazione degli indirizzi. Ci sono diverse tecniche di gestione della memoria. Contigua singola La memoria è divisa in due parti, una riservata al sistema operativo ed una al programma utente. Il programma utente sarà sempre caricato a partire dalla stessa locazione fisica. In questo caso non c’è possibilità di multiprogrammazione, ma solo di sistemi operativi uniprogrammati. La rilocazione è assoluta, cioè gli indirizzi vengono trasformati da logici a fisici al momento della compilazione del programma. Partizioni Fisse La memoria è divisa, al momento dell’installazione del SO, in un certo numero di partizioni fisse ( non necessariamente uguali ) ciascuna delle quali potrà contenere un processo. Il SO, per la gestione, si avvarrà di una tabella delle partizioni di tante righe quante sono le partizioni e per ogni partizione l’ indirizzo iniziale, indirizzo finale (o lunghezza), PID del programma che la occupa ( 0 se libera ). La rilocazione è statica, gli indirizzi vengono trasformati da logici a fisici al momento del caricamento del programma in memoria, dal caricatore ( loader ), che trasformerà gli indirizzi sommando all’indirizzo logico l’indirizzo iniziale della partizione dove viene caricato il processo. Il grado di multiprogrammazione è minore o uguale al numero di partizioni. Due sono i principali problemi di questa organizzazione della memoria: • uso inefficiente della memoria a causa della frammentazione interna alle partizioni. La dimensione dei processi deve essere minore delle partizioni e lo spazio inutilizzato in ogni partizione forma un insieme di frammenti di memoria inutilizzabili; • impossibilità di eseguire programmi più grandi della partizione di dimensioni maggiore. tabella delle partizioni Indirizzo iniziale della partizione dimensione PID processo 25 35 65 125 135 10 30 60 10 40 0 4 5 0 0 Partizioni Variabili Le partizioni vengono create dinamicamente, in base alle richieste. Inizialmente si hanno due partizioni, una del SO ed una costituita dal resto della memoria libera. La creazione di una nuova partizione divide la partizione libera in due. E così via. Quando un programma termina si libera la sua partizione (e se quella precedente e/o seguente sono libere si accorpano in un’unica partizione libera). Dopo un certo tempo si può avere il problema della frammentazione esterna ( tanti frammenti di partizioni libere, ciascuno dei quali troppo piccolo per contenere un altro processo ). Il SO, per la gestione, si avvarrà di una tabella simile a quella delle partizioni fisse, ma con un numero variabile di righe. tabella delle partizioni partizione locazione iniziale dimensione PID processo 1 2 3 ----n 25 35 65 ----- 10 30 60 ----- 0 4 5 ----- La scelta della partizione libera da utilizzare può essere fatta con diverse strategie: • first fit viene scelta la prima partizione sufficiente a contenere il programma; favorisce il formarsi di una grande partizione libera verso il fondo della memoria; • best fit viene scelta la più piccola partizione sufficiente a contenere il programma, così da non frammentare inutilmente le grosse partizioni; • worst fit viene scelta la più grande partizione che può contenere il programma. La rilocazione è generalmente dinamica ma può anche essere statica. La rilocazione dinamica consiste nel caricare il programma in memoria senza alcuna trasformazione degli indirizzi, che vengono trasformati da logici a fisici al momento dell’esecuzione da una apposita unità hardware chiamata MMU ( Memory Management Unit ). L’indirizzo iniziale della partizione viene posto in uno speciale registro base e durante l’esecuzione l’indirizzo fisico viene calcolato come Col partizionamento variabile si crea ancora frammentazione, ma in questo caso possiamo superarla lanciando un processo di compattazione della memoria che sposta i programmi in modo da creare un’ unico spazio libero in coda a tutte le partizioni. Questa operazione però è abbastanza "costosa" in termini di overhead di sistema e non può essere utilizzata in sistemi interattivi. Segmentazione Le tecniche di gestione memoria fin qui illustrate prevedono che ogni programma sia logicamente lineare e venga caricato in un’area contigua di memoria. Con la segmentazione questo vincolo cade: un processo viene suddiviso in più segmenti ( ad esempio dal compilatore o su indicazione del programmatore ) ciascuno dei quali potrà essere successivamente caricato in una diversa partizione. In ogni segmento gli indirizzi logici partono da 0; il campo indirizzo delle istruzioni è formato numero del segmento [ 1 byte ] e displacment ( indirizzo all’interno del segmento ) [2 byte]. La memoria centrale viene gestita a partizioni variabili, con l’ausilio della tabella delle partizioni, contenente, per ogni partizione, indirizzo iniziale, indirizzo finale (o dimensione ), PID e numero segmento del segmento del programma che la occupa ( 0 se libera ). tabella delle partizioni partizione 1 2 3 ----n indirizzo iniziale 10 30 60 ----- Indirizzo finale 30 60 80 ----- PID processo numero segmento 5 0 5 ----- 1 2 La rilocazione è dinamica e per risolvere gli indirizzi, per ogni programma si usa una tabella dei segmenti (di tante righe quanti sono i segmenti del programma e avente, per ogni segmento, l’indirizzo della partizione nella quale è stato caricato il relativo segmento del programma) il cui indirizzo è contenuto nel registro base. Paginazione La segmentazione ridimensiona il problema della frammentazione, ma non lo elimina. La tecnica della paginazione è simile a quella della segmentazione, con la differenza che le pagine sono a lunghezza fissa. Il processo viene, dal compilatore, suddiviso in pagine a lunghezza fissa. La memoria centrale è suddivisa in frame o cornici di pagine della stessa dimensione delle pagine. Le pagine di un programma possono essere caricate ovunque in memoria. Con la paginazione non esiste più il problema della frammentazione e la memoria viene sfruttata al 100% (un po’ di "spreco" ci potrà essere sull’ultima pagina di ciascun programma). Il campo indirizzi delle istruzioni, come per la segmentazione, è formato è formato da numero pagina e displacment (indirizzo all’interno della pagina). La memoria centrale viene gestita con l’ausilio di una tabella di occupazione pagine, (di tante righe quante sono i frame ) contenente, per ogni riga, l’indicazione di chi occupa il frame corrispondente [PID e numero pagina del processo che la occupa] o 0 se libera. La rilocazione è dinamica e per risolvere gli indirizzi, per ogni programma si usa una tabella delle pagine, ( di tante righe quante sono le pagine del programma e avente, per ogni riga, il numero di frame nel quale è stata caricata la relativa pagina del programma) il cui indirizzo è contenuto nel registro base. Memoria Virtuale paginata o Paginazione Dinamica Con le precedenti tecniche di gestione memoria i processi debbono essere caricati completamente in memoria. La memoria virtuale, invece, consente che un processo sia parzialmente presente in memoria centrale e che le sue parti siano caricate e scaricate da disco quando serve. Ad ogni processo è assegnata un’area di memoria centrale (che lo contiene parzialmente) ed un’area su disco area di swap che contiene la sua immagine, istruzioni e dati, e che viene aggiornata durante l’esecuzione ( il contenuto dell’area di swap del processo è diverso da quello del file contenete le istruzioni del programma, che non viene modificato ). I processi, oltre alla memoria centrale, usano anche l’area di swap su disco, usano cioè una Memoria Virtuale più grande della memoria fisica. Il loro spazio indirizzabile ( indirizzi logici ) può essere maggiore dello spazio fisico della memoria centrale e possono essere eseguiti contemporaneamente tanti processi dei quali è presente in memoria solo una piccola parte. A fronte di questo indubbio vantaggio, occorre pagare un prezzo: lo swapping ( carico e scarico ) di parti di processo da/su area di swap rallenta l’esecuzione del processo ( il tempo di accesso alla RAM è dell’ordine dei nanosecondi, mentre quello al disco è dell’ordine dei millisecondi ). I processi permanenti del SO non vengono mai gestiti dinamicamente. L’inutilità di portare in memoria l’intero processo è dovuta al fatto che: • è probabile che non tutto il codice verrà effettivamente eseguito ( si pensi ad esempio alla parte then o else di un if, al while subito falso ecc..ecc.. ). • il principio di località temporale ci dice che è molto probabile che , in un dato intervallo di tempo, un processo tende ad accedere agli stessi indirizzi di memoria e quindi solo le pagine che li contiene può risiedere in memoria centrale. • il principio di località spaziale che ci dice che se un programma accede ad una cella di memoria probabilmente entro breve tempo accederà a celle vicine. Avere in memoria solo una parte del processo è importante per due ragioni: • aumenta il grado di multiprogrammazione del sistema cioè il numero di processi in stato di running + ready + wait; • è possibile eseguire processi di dimensioni maggiori dell’intera memoria fisica. Questa tecnica permette di eseguire un programma anche se non tutte le sue pagine sono presenti in memoria; le altre pagine restano nell’area di swap e vengono caricate all’occorrenza. Quando il programma fa riferimento ad una pagina non presente in memoria ( page fault ) il sistema provvede a caricarla ( page swapping ) e il processo viene bloccato finché la pagina non è stata caricata. La nuova pagina sarà caricata su un frame libero se presente o su una occupata; in quest’ultimo caso potrebbe prima essere necessario salvare la pagina da sovrascrivere su disco (se, ad esempio, questa ha subito modifiche dopo l’ultimo caricamento). La rilocazione è dinamica e la memoria centrale viene gestita con l’ausilio di due tabelle: • tabella di occupazione dei frame, di tante righe quante sono i frame contenente, per ogni riga, l’indicazione di chi occupa il frame corrispondente [ PID e numero pagina del processo che la occupa ] o 0 se libera, un bit di variazione che dice se la pagina è stata modificata e un bit di riferimento che dice se la pagina è stata riferita. PID 2130 0 7321 • Numero pagina bit variazione 3 0 1 1 bit riferimento 1 0 tabella delle pagine di un processo, ( di tante righe quante sono le pagine del processo e avente, per ogni riga, il numero del frame nel quale è stata caricata la relativa pagina del programma oppure 0 se non presente in memoria e l’indirizzo del blocco sull’area di swap del disco che contiene l’immagine della pagina ) il cui indirizzo è contenuto nel registro base. frame 123 0 21 Indirizzo blocco area swap 4 7 8 Nell’esempio abbiamo la tabella del processo P1 costituito da 3 pagine la prima e la terza sono in memoria rispettivamente nei frame 123 e 21, la seconda non è in memoria. Il flow chart che segue sintetizza l’evento page fault e le azioni conseguenti. Per riferirsi ad un disco rigido si usano le terne. Abbiamo visto che quando il programma fa riferimento ad una pagina non presente in memoria avvene il cosiddetto page fault, cioè un’interruzione per mancanza di pagina. A questo punto possiamo attuare due diversi algoritmi per caricare la pagina mancante, che prendono il nome di strategia di prelevamento. Paginazione a richiesta che porta in memoria la sola pagina richiesta, cioè quella che contiene l’indirizzo richiesto. Prepaginazione che cerca di prevedere le esigenze del processo, portando in memoria un certo numero di pagine per volta, per esempio quelle vicine alla pagina che ha causato il page fault. Alla base di questa strategia vi è un principio detto principio di località spaziale che afferma che un programma che accede ad una parola di memoria accederà probabilmente , entro breve termina a celle di memoria vicine. Strategie di sostituzione Riguarda la selezione della pagina da rimuovere della memoria centrale per far posto ad una buona pagina da caricare. Le strategie di sostituzione possono seguire: • Strategie di sostituzioni locali viene liberato un frame fra quelli occupati dal processo che ha generato il page fault; • Strategie di sostituzioni globali viene liberato un frame fra quelli occupati, senza tener conto del processo. Vediamo quali sono i principali algoritmi di sostituzione. Algoritmo FIFO La pagina da eliminare è quella che è stata più a lungo in memoria Algoritmo LRU La pagina da eliminare è quella che è non è stata usata da maggior tempo Algoritmo della seconda opportunità E’ una variante del FIFO che evita di eliminate una pagina dalla memoria solo perché è stata caricata da molto tempo. L’algoritmo fa uso del bit di riferimento che dice se la pagina è stata referenziata ( letta e/o scritta ). L’algoritmo esamina la coda FIFO , ma prima di rimuovere la pagina che si trova in cima alla coda controlla il valore del bit di riferimento : se = 0 ( pagina non referenziata) la pagina viene rimossa se = 1 ( pagina referenziata ) la pagina viene messa in fondo alla coda con il valore riportato a 0 Gestione delle periferiche Occorre assegnare i dispositivi ai processi secondo un’opportuna politica di scheduling, controllare l’esecuzione delle operazioni sui dispositivi e fornire un’interfaccia uniforme agli stessi. Le operazioni di I/O vengono svolte da processori specializzati processori di I/O che interagiscono con il sistema attraverso buffer di comunicazione e segnali di interruzione. Le periferiche sono viste in modo astratto indipendentemente dalla loro struttura hardware. Tale astrazione viene realizzata dai driver, dei programmi che 'pilotano' la periferica e che fanno da interfaccia tra la periferica e i programmi che vi possono accedere richiamando il driver relativo. Il disco rigido Il disco fisso è un componente fondamentale del computer poiché stabilisce i tempi di accesso alle applicazioni e quindi la capacità di risposta del sistema operativo. Il principio di funzionamento degli hard-disk si è andato via via perfezionando, ma il principio base è rimasto sempre lo stesso. Una testina volante fissata ad un dispositivo molto aerodinamico simile ad un’ala effettua le operazioni di scrittura e lettura; l’hard-disk gira velocemente creando un microvortice in grado di far allontanare con lo spostamento d’aria la testina dal disco di qualche micron (1 micron= 1/1000 di millimetro ). In esso vengono fisicamente archiviati i dati preposti al funzionamento del sistema operativo ed i dati utente; Le informazioni sono memorizzate su di esso sotto forma di files. Un file è semplicemente una sequenza di bytes. I bytes possono rappresentare un codice ASCII per un carattere di un file testo oppure possono essere delle istruzioni per un particolare software oppure ancora un colore di un pixel in un immagine. Non importa quello che contiene, un file è semplicemente una sequenza di bytes. Quando un programma lo richiede, l'hard disk lo trova e byte per byte lo invia alla CPU per la successiva elaborazione. Se apriamo un hard-disk ci accorgiamo che è costituito da una pila di uno o più piatti ( i veri e propri dischi ) ricoperti da uno strato di supporto magnetico simile ad una microscopica asfaltatura, su cui vengono registrati i dati. I piatti ruotano assieme su un perno, chiamato asse. I piatti degli hard disk vengono mantenuti in costante rotazione, ciò fa si che i dati in esso registrati siano immediatamente disponibili, letti e scritti alla maggior velocità possibile. Normalmente i piatti di un disco fisso sono in alluminio ( da cui il nome hard ) su cui è applicato un materiale magnetico, ed è lavorato con macchine di precisione per ottenere tolleranze molto basse, misurata in frazioni di micron. Il disco fisso tradizionale è un meccanismo combinato in parte elettronico e in parte meccanico. Da un punto di vista elettrico esso svolge la funzione di trasformare gli impulsi di dati digitali elettronici in campi magnetici permanenti; analogamente ad altri dispositivi di registrazione magnetici, floppy disk, registratori audio e video, anche il disco fisso raggiunge il suo fine usando un elettromagnete, la testina di lettura/scrittura per allineare le polarità delle particelle magnetiche sugli hard disk stessi. Il resto dell’elettronica presente ha il compito di controllare e ottimizzare la parte meccanica del drive aiutandola a organizzare adeguatamente la memorizzazione magnetica e a localizzare le informazioni immagazzinate sui piatti del disco. I piatti sono suddivisi in zone chiamate tracce, queste a loro volta sono suddivise in settori ( record fisici o blocchi ). L'insieme di tracce che si trova alla stessa distanza dal centro su tutte le facce di tutti i piatti viene chiamato cilindro. Si è soliti individuare ciascuna traccia con un numero a partire dalla traccia zero sita sul bordo esterno. Per individuare un informazione sul disco è necessario conoscere il cilindro in cui l’ informazione stata memorizzata, nell’ambito del cilindro su quale faccia ed infine individuata la faccia in quale settore. In sintesi per individuare l’informazione abbiamo bisogno della terna < cilindro, faccia, settore > o CFS La suddivisione CHS è logica e non fisica e viene impostata una volta per tutte in fabbrica, quando si esegue la formattazione a basso livello, differente dalla formattazione ad alto livello, fatta generalmente dall’utente, che prepara il disco ad essere utilizzato secondo le richieste del file system adottato dal sistema operativo. Le tracce sono degli anelli (ideali) presenti su ogni piatto e sono identificati da un numero intero che parte da zero. Indipendentemente dal tipo di supporto magnetico, le testine di lettura e scrittura devono arrestare il proprio moto laterale sul disco ogni volta che leggono o scrivono dati. Mentre le testine sono ferme, i piatti del disco girano sotto di esse e, ogni volta che un piatto completa un giro, le testine descrivono una circonferenza completa sulla sua superficie. Questa circonferenza viene chiamata traccia. I settori sono “fette” di tracce, rappresentano la minima quantità di memorizzazione di dati che supporta il disco. La dimensione del settore viene calcolata in base al numero di piatti presenti nel disco fisso e alla capacità di esso, solitamente avremo un settore di 512B o 1024B o 2048B. Le testine vengono pilotate da un motorino che fa ruotare anche i dischi ad una velocità di rotazione sempre costante. Queste testine, sulla base delle indicazioni fornite dall'elettronica di controllo, si muovono avanti e indietro fino a raggiungere la zona esatta nella quale devono essere lette o scritte le informazioni. Queste in realtà non toccano il disco ma galleggiano su un cuscino d'aria sfiorando la superficie. Infatti, la testina tocca il disco in una zona chiamata “landing zone” (che non contiene dati) solo quando il computer è spento. Le testine sono un componente molto delicato del disco fisso: se queste sbattono violentemente contro i piatti possono rimanere permanentemente danneggiate (ecco perché è molto rischioso urtare violentemente un computer in funzione). La polvere o le particelle di sporcizia presenti nell’aria possono danneggiare la testina; se questa tocca e danneggia il supporto magnetico si può arrivare ad una situazione (crash) che non solo distrugge la capacità di memorizzazione del supporto nell’area colpita, ma può liberare particelle di materiale magnetico che a loro volta danneggiano altre zone del disco in un perverso meccanismo a catena. Il numero di giri ( rpm ) che il disco effettua in un minuto è indicato con queste sigle: 5400 rpm, 7200 rpm, 10000 rpm, 15000 rpm ecc.., cioè 5400 rotazioni per ninuto , 7200 giri al minuto e così via. Più alto è questo numero, maggiore sarà l'accesso ai dati di lettura e scrittura. In ogni caso, un disco a 10.000 rpm ha bisogno di essere ben ventilato in quanto viene prodotto molto calore. Il tempo di accesso ai dati indica il tempo necessario affinché il disco possa accedere alle informazioni.. Le caratteristiche principali di un disco rigido sono: • la capacità • la velocità di trasferimento • il tempo di accesso La capacità è la massima quantità di byte che possono essere memorizzati sul disco rigido. In genere la capacità viene espressa in espressa in gigabyte ( GB = 1000.000.000 Byte ). La capacità può essere aumentata sia aumentando il numero di piatti che incrementando la densità con cui le informazioni vengono memorizzate sui piatti che compongono l'hard disk. La velocità di trasferimento è la quantità di dati fornita dal disco rigido in un determinato tempo (in genere si prende 1 secondo come riferimento). Usare dischi che ruotino più velocemente o incrementare la densità di memorizzazione porta ad un miglioramento diretto della velocità di trasferimento. Il tempo di accesso è il tempo medio necessario perché un dato, residente in un punto casuale del disco, possa essere reperito. Il tempo di accesso impiegato dipende dalla • velocità della testina a spostarsi sul cilindro dove risiede il dato • dalla velocità di rotazione del disco; maggiore è la velocità e più breve è il tempo impiegato dal dato a ripassare sotto la testina nel caso questa non fosse arrivata in tempo sul dato, durante la rotazione precedente ( latenza rotazionale ). Il tempo di accesso medio tipico per un disco rigido da 7200 rpm ( rotazioni per minuto ) è di circa 9 millisecondi. Per uno da 15.000 rpm è inferiore a 4 ms. IL tempo di accesso medio si può calcolare come : TAccesso = TS TS TSEL TL TT + TSEL + TL + TT dove tempo di seek cioè il tempo necessario a spostare la testina sul cilindro richiesto; tempo di selezione della faccia ( trascurabile ); tempo di latenza cioè il tempo necessario a che il settore richiesto passi sotto la testina; tempo di trasferimento di un settore dalla superfice del disco al buffer del disco. Fra questi quattro tempi quello che incide maggiormente sul tempo di accesso è TS ( è nell’ordine di qualche ms ) per cui è necessario minimizzare lo spostamento del braccio attraverso efficaci algoritmi di schedulazione del braccio del disco. Essi sono: FIFO ( First In First Out ) Le diverse richieste sono servite secondo l’ordine di arrivo. L’algoritmo risulta • lento • equo SSTF ( Shortest Service Time First ) Vengono servite prima le richieste che generano il minor spostamento della testina. L’algoritmo risulta • efficente • non equo SCAN Si ha la scansione del braccio prima in una direzione e poi nella direzione opposta, durante la scansione vengono servite le richieste in arrivo ( come ascensore ). Il senso di scansione si inverte quando non vi sono più richieste da soddisfare nella direzione del movimento. L’algoritmo risulta • meno efficiente rispetto al SSTF; • più equo anche se favorisce le richieste vicine ai cilindri più esterni e interni. C.SCAN ( Circular SCAN ) Variante dello SCAN il braccio serve sempre le richieste in una sola direzione. Questo algoritmo è • Più equa rispetto allo SCAN N.SCAN Variante dello SCAN che divide le richieste che via via arrivani in più code. Mentre una coda è servita, le nuove richieste sono inserite nelle altre code. Le diverse code sono servite ciclicamente. • Efficiente come lo SCAN • Più equo rispetto allo SCAN Virtualizzazione delle periferiche ( SPOOL ) I dispositivi virtuali sono dispositivi fisicamente "non esistenti" simulati con l’ausilio di altri dispositivi fisici, come la Memoria Virtuale precedentemente esaminata. Altro esempio di virtualizzazione delle periferiche è lo SPOOL (Symultaneous Peripheral Operations On Line), una tecnica che consente la gestione multipla di periferiche seriali (che possono essere concesse ad un processo alla volta) quali la stampante. Le stampanti virtuali vengono simulate con l’ausilio del disco e dell’unica stampante di sistema presente. Ad ogni processo che usa la stampante viene associato, su disco, un file di spool nel quale vengono memorizzate le informazioni da stampare. Quando il processo dichiara che la stampa è terminata (o quando il processo termina), il relativo file di spool viene chiuso ed inviato alla stampante. Se la stampante è libera esso viene stampato, altrimenti la sua richiesta di stampa viene accodata nella coda di stampa ad attendere che la stampante abbia stampato i file di spool le cui richieste di stampa sono in coda prima di essa. SPOOL -virtualizzazione della stampante di sistema La Gestione delle Informazioni La gestione delle informazioni ha un ruolo importante nell’architettura di un SO, perché le informazioni sono la risorsa fondamentale di ogni applicazione. Il file è l’oggetto della gestione delle informazioni. Le informazioni sono memorizzate in file e i programmi applicativi acquisiscono l’input dai file e restituiscono le informazioni sotto forma di file. A differenza della memoria centrale , i file sono oggetti che esistono indipendentemente dalle applicazioni che li usano e sono collocati in unità periferiche, come strutture permanenti di memorizzazione delle informazioni. La parte del SO che gestisce le informazioni si chiama file system ( sistema di archivi ). Esso ha il compito di gestire i file registrati su disco e, di conseguenza, le operazioni di accesso ai file si traducono in operazioni di input / output con il disco. I file Un file ( termine inglese per "archivio" ) è un contenitore di informazione in forma digitale. Le informazioni codificate al suo interno sono leggibili solo da software. Tecnicamente, i dati codificati in un file sono organizzati come una sequenza di byte, immagazzinati come un unico elemento su un disco rigido, all'interno del file system esistente su quel disco rigido. Se dal punto di vista logico cioè dell'utente, un file è solitamente visto come un singolo elemento, da un punto di vista strettamente fisico il file è un insieme di blocchi ( settori o record fisici ). Uno dei compiti del sistema operativo è nascondere alle applicazioni la reale suddivisione fisica del file in blocchi e occuparsi di gestire la scrittura e il recupero delle informazioni. L’utente non deve preoccuparsi dell’allocazione fisica dei blocchi, tracce o settori, ma solo di associare ad un file un nome e conoscere le caratteristiche che ne definiscono la struttura e le modalità di accesso. Il file è una collezione di record logici, cioè di informazioni logicamente omogenee, che descrivono istanze di un’entità. Ogni record è composto da un insieme di campi che contengono i valori assunti dalle proprietà che descrivono l’entità. Nell’esempio che segue l’entità è studente, ogni riga della tabella descrive un’istanza ( cioè uno studente ) ed ogni campo è una proprietà dello studente. 225 345 657 123 341 151 100 campo codice Bianchi Rossi Magnati Negri Bruni Viola Gialli campo cognome Adolfo Francesco Nicola Annalisa Giuliano Pietro Alessandra campo nome ………. ………. ………. ………. ………. ………. ………. campo ………. ………. ………. ………. ………. ………. ………. campo 1^ 2^ 3^ 4^ 5^ 6^ 7^ record logico record logico record logico record logico record logico record logico record logico Il campo da un punto di vista logico è l’unità elementare di informazione ed è composto da un certo numero di byte. Le sue caratteristiche essenziali sono • Dimensione espressa in Byte; • Tipo carattere, numero, stringa, data ecc.. ecc… In un settore o record fisico possono essere registrati più record logici il rapporto dimensione del record fisico ----------------------------------dimensione del record logico prende il nome di fattore di bloccaggio. La dimensione del campo può esser fissa Rossi Giuseppe 30 Byte 20 Byte E’ evidente in questo caso che vi è un grande spreco di spazio. Roma 30 byte Nel caso il campo sia di dimensione variabile dovremo in qualche modo indicare la dimensione del campo stesso 1 5 Verdi 8 Giuseppe 6 Milano 9 o comunque delimitarne la fine attraverso un delimitatore di campo ( nel nostro caso # ) Verdi# Giuseppe# Milano# I file hanno una serie di attributi • Nome • Identificatore un numero che distingue il file nel file system • Tipo necessaria ad associare l’applicazione che deve manipolarlo • Locazione puntatore alla locazione del file sul disco • Dimensione • Protezione identifica può leggerlo, scriverlo ed eseguirlo • Data di creazione • Data ultima modifica • Identificazione dell’utente PID del proprietario del file e su di essi si possono fare una serie di operazioni: • creazione • apertura • chiusura • lettura • scrittura • cancellazione • posizionamento in un file • troncamento di un file ( svuotamento ) Alcuni sistemi operativi provvedono a fornire meccanismi per bloccare file già aperti, in modo da impedire che altri processi tentino di aprirli contemporaneamente. La tipologia di un file viene usata per ottenere informazioni sulla sua struttura. Le operazioni di apertura e chiusura di un file vengono definite nel modo seguente. Apertura: trasferisco gli attributi del file in memoria centrale. Nel PCB del processo che opera sul file c’è una struttura che si chiama tabella dei file aperti, che mi permette di avere accessi rapidi al processo. Anche una parte del file può essere portato sulla memoria centrale ( perché può essere molto grande ); nel caso dell’accesso sequenziale porterò un blocco a sinistra e a destra dal record logico ‘n’ da cui parto. Chiusura: mi sbarazzo delle informazioni del file messe in memoria e cancello dalla tabella dei file aperti del PCB relativo al processo che lo stava utilizzando il puntatore al file. Metodi di accesso. Per metodo di accesso si intendono le modalità con le quali è possibile accedere al record di un file. Per accedere ad un file abbiamo essenzialmente un punto di vista logico ( ovvero di come il file si presenta agli utenti ) e un punto di vista fisico ( ciò che è nella realtà ) che sono diversi tra loro ma sono strettamente correlati. Dal punto di vista logico un file è organizzato come un’insieme di elemento, detti record logici, che tutti insieme formano il file stesso. Il sistema operativo consente di scorrere i vari record logici che compongono il file operando essenzialmente su di essi tramite due tipi di accessi. Accesso Sequenziale: per accedere ad un particolare record N, devo scandire gli N – 1 record logici che lo precedono. Accesso Diretto: si può accedere direttamente al record logico desiderato senza scorrere tutti i record uno alla volta . File System Logico In genere il file system prevede una struttura gerarchica ad albero che parte dalla radice principale ( C: ) e ha i file organizzati in directory ( indici o cartelle ) gerarchiche, ciascuna delle quali può contenere altri file e altre directory. La directory stessa è un file speciale costituito da un record descrittivo per ogni file o directory in essa contenuti. Il FS fornisce le operazioni di gestione creazione, cancellazione, spostamento di file e directory. Si occupa dell’apertura dei file, della loro protezione, della condivisione di file e directory gestendo gli accessi contemporanei per evitare incongruenze, delle organizzazioni dei file e dei metodi di accesso (sequenziale, diretto, …), del recovery ( ad esempio "salva/ ripristina configurazione di sistema" di Windows), etc.. Le informazioni relative ai file contenute nella directory sono gli attributi visti prima: Nome, identificatore, tipo, locazione, protezione, data di creazione, data ultima modifica, identificazione, dell’utente. Le directory vanno organizzate in modo che: • sia garantita una buona efficienza (ovvero localizzare velocemente un file). • due utenti possano avere lo stesso nome per file diversi. • lo stesso file possa avere differenti nomi per diversi utenti. • che siano possibilmente raggruppati per tipo ( programmi, documenti, video,musica, etc..). Essenzialmente vi sono cinque tipi di struttura di directory. • Single-Level. Tutti i file sono contenuti in un’unica directory. Presenta gli svantaggi che i file, essendo nella stessa directory, devono avere nomi unici e che è pesante gestire tutti i file in un unico luogo. • Two-Level. Ogni utente ha una sua directory personale chiamata ‘user directory’. Di tutte queste directory tiene traccia la ‘master directory’. Questo sistema risolve il problema dell’unicità dei nomi dei file fra utenti, ma non consente a più utenti di cooperare tra loro. • Tree-Structure. Possono essere create sottodirectory. Per ogni file compaiono i cosiddetti path name assoluti (iniziano dalla radice fino al file) e path name relativi (da una sottodirectory al file). Non è possibile condividere files. • • Grafo Aciclico. Utile per condividere directory e files; la condivisione può essere applicata tramite links (puntatori a un altro file contenete il percorso assoluto o relativo) o tramite copie duplicate. Bisogna però gestire i problemi derivanti dai dati condivisi e soprattutto verificare che non vi siano cicli nel grafo. Grafo Generico. Assicurarsi che non via siano cicli. Come un file va aperto prima di essere usato, un file system va montato ( mounted ) prima di essere disponibile per i processi sul sistema; un mounted-point è il luogo dove un file system deve essere attaccato ( tipicamente è un directory vuota ). Con le strutture di directory ad albero è semplice assegnare ai file nomi significativi rispettando la richiesta di dare nomi differenti ad ogni file. Infatti il nome del file è ottenuto , a partire dalla directory radice, concatenando tutti i nome delle directory che si incontrano sino alla directory che contiene il file. Ad esempio, facendo riferimento alla figura precedente: C:\Documenti\pippo Questo è il path assoluto ( percorso ) del file pippo assoluto perché parte dalla radice. C:\Windows\System\pippo è differente dal pippo dell’esempio precedente infatti il pathname è diverso Se sono nella directory Windows posso riferirmi a pippo con il patname relativo alla cartella in cui mi trovo cioè Windows: \System\pippo Protezione. Quando le informazioni sono salvate sul disco, dobbiamo proteggerle dai danneggiamenti e dagli accessi indesiderati. Dai danneggiamenti ci si tutela con le copie di backup; dagli accessi indesiderati, invece, creando tipologie di accessi dipendenti dall’identità dell’utente. Per ogni file avemo i diritti di accesso per il proprietario ( user ), il gruppo di appartenenza del proprietario ( group ) e per gli altri ( other ) . Ad esempio nei sistemi Unix/Linux per ogni file avremo: user group other | | | ------ ------ ----- -rwx r-x --x -rwx rwx rwx pippo documento Dove r = read ( lettura ) w = write ( scrittura ) x = execute ( esecuzione ). Quindi il file pippo potrà essere scritto, letto e eseguito dal proprietario, letto e eseguito dal gruppo, solo eseguito dagli altri. Sul file documento tutti potranno fare scrittura, lettura ed esecuzione. Struttura e organizzazione dei file Un file dal punto di vista fisico può essere descritto come un insieme di blocchi o record fisici o settori. Il file system a questo livello ha il compito di tenere traccia dello stato dei blocchi sapendo quali sono i liberi e quali occupati, e di allocare quote di disco ai file tramite metodi detti metodi di allocazione dello spazio su disco. I principali metodi sono descritti di seguito. Allocazione Contigua. I blocchi che costituiscono il file sono allocati contiguamente sul disco. Vantaggi: • è semplice c’è solo bisogno del blocco iniziale e del numero di blocchi occupato dal file; • supporta il metodo di accesso diretto. Svantaggi: • soggetto al problema della frammentazione esterna del disco rigido; • i files non possono crescere di dimensione perché si rischia di collidere con altri files bisogna necessariamente allocare nuovamente spazio su disco. Allocazione Linkata. I blocchi che compongono il file sono organizzati a lista linkata; questi blocchi possono essere dislocati ovunque sul disco fisso. Ogni blocco è suddiviso in due parti, informazione e puntatore al blocco successivo. Vantaggi: • • è un metodo semplice, per applicarlo c’è bisogno solo del puntatore iniziale alla lista • non è soggetto al problema della frammentazione esterna. Svantaggi: viene utilizzato dello spazio in più per memorizzare un puntatore in ogni blocco. • non supporta il metodo di accesso diretto ( per accedere al blocco i-esimo devo necessariamente partire dall’inizio della lista e scandire i blocchi che lo precedono ). • poco affidabile, si pensi ad esempio a che potrebbe succedere se un puntatore venisse perso o danneggiato. Allocazione Indicizzata. E’ simile all’organizzazione linkata solo che in questo caso tutti i puntatori ai blocchi che compongono il file sono portati all’interno di un unico blocco che chiameremo blocco indice. I puntatori sono memorizzati in sequenza, dal primo all’ultimo blocco. Se il blocco indice raggiunge dimensioni troppo grandi è possibile fare un blocco indice di blocchi indice. Vantaggi: • supporta il metodo di accesso diretto quindi trovare un blocco è immediato; • non è soggetto al problema della frammentazione esterna. Svantaggi: • non è semplice da realizzare ( c’è bisogno del blocco indice) • spreco di spazio, specialmente se si hanno tanti file di piccole dimensioni. Allocazione con mappa dei blocchi dei file ( FAT - File Allocation Table ) Utilizza le idee alla base dell’allocazione linkata e indicizzata. Infatti i blocchi che compongono un file sono riconosciuti da un elenco di puntatori, come nel caso delle liste di blocchi collegate, ma l’elenco dei puntatori è centralizzato, come nel caso dell’allocazione indicizzata. In particolare l’elenco dei puntatori è memorizzato in una struttura dati centralizzata datta FAT. La FAT è un vettore con tanti elementi quanto sono i blocchi del disco; gli elementi della fat sono collegati in liste che permettono di trovare tutti i blocchi che compongono un file. La FAT è memorizzata su disco ma per velocizzare le operazioni può essere caricata in memoria centrale. Allocazione dei file nei sistemi Unix / Linux Consiste nel tenere i primi 15 puntatori del blocco indice in una struttura di dati detta inode del file. I primi 12 di questi puntano a blocchi diretti, cioè contengono direttamente gli indirizzi di blocchi che contengono i dati del file. Quindi i dati per piccoli file non richiedono un blocco indice distinto. Gli altri 3 puntatori puntano a blocchi indiretti. Il primo puntatore di blocco indiretto è l’indirizzo di un blocco indiretto singolo, ovvero un blocco indice che non contiene dati ma indirizzi di blocchi che contengono dati. Infine abbiamo un puntatore a un blocco indiretto doppio e un puntatore a un blocco indiretto triplo. Gestione deli blocchi liberi Essenzialmente vi sono due modi per gestire lo spazio libero. Se ne tiene traccia tramite una free-space list implementata come un vettore di bit. bit[ i ] = 0 se il blocco è libero, bit[ i ] = 1 se il blocco è occupato . Questo metodo ha l’inconveniente che, per ragioni di efficienza, il vettore di bit va tenuto in memoria centrale e sappiamo che può essere molto grande. Un altro metodo di gestione dei blocchi liberi consiste nel collegarli tutti in una lista linkata, e quindi tenere un puntatore al primo di questi in una speciale locazione del disco e caricarlo nella memoria. Questo primo blocco contiene un puntatore al successivo blocco libero e così via. Protezione e Sicurezza In una macchina esistono tanti oggetti, processi, etc.., e il sistema operativo deve assicurare la loro protezione. Struttura dei domini di protezione. Diritti di accesso è una coppia che rappresenta <nome oggetto, {diritti}> Dominio: insieme di diritti di accesso. Un dominio può rappresentare ciò che un processo è in grado di fare. Accesso a Matrice Il sistema operativo deve fornire un meccanismo che permetta di specificare vari diritti su sti oggetti e vedere che siano rispettati. Il sistema operativo può memorizzare questa matrice in due modi: utente/file File 1 File 2 Printer Franco r Pino r, w p Nicola r, w, e r Svolgere attività sulla protezione e sicurezza comporta limiti all’efficienza del sistema, per esempio aggiungere controlli al tempo d’esecuzione. Lo spingere la sicurezza dipende molto dall’ambiente in cui ci troviamo. Tra la minacce fondamentali troviamo: • Lettura dei dati (data confidentiality) • Integrità dei dati (data modified) • System avaiability (denial of service): il sistema impiega tutto il suo tempo nel gestire continue richieste dell’esterno. Per esempio un sito web che non funziona più perché è costretto a gestire un numero elevatissimo di richieste dalla rete esterna. Autenticazione Come ci garantiamo che un’utente che vuole lavorare su una macchina sia accettato? Abbiamo il sistema basato su login ( username e password ), che però è facilmente attaccabile. 1) One time password. Per migliorare questo sistema si possono utilizzare password che vengono utilizzate solo per una sessione. L'utilità sta nel fatto che se un intruso se ne appropria non se ne fa niente perchè alla sessione dopo non viene più utilizzata. 2) Challenge-Response Authentication. Si tratta ancora di password che cambiano nel tempo. L’utente seleziona un algoritmo, per esempio x^2 Durante il login: • il server spedisce un numero intero i • l’utente risponde con i^2 Problema: l’algoritmo può essere indovinato. Soluzione: una funzione ‘f’ (pubblica) l’utente seleziona una chiave ‘k’ durante il login: • il server spedisce un numero intero i • l’utente risponde con f ( i, k ) Processi Un processo è l’insieme delle azioni compiute dal processore per eseguire un programma. Si può semplificare dicendo che "un processo è un programma in esecuzione". Per meglio definire un processo analizziamone la differenza con il programma. Un programma è costituito dalle istruzioni scritte dal programmatore e tradotte in linguaggio macchina e viene visto come un’insieme di bit presenti sul disco rigido che non cambia nel tempo e non modifica lo stato della macchina. Un processo invece modifica lo stato della memoria e dei registri. In generale potremo dire che: • Un processo è un’entità dinamica il cui stato è determinato dai valori assunti dal registro Program Counter, dai registri generali, dallo stato della memoria, dallo stack e dall’ SR; • Un processo ha una traccia d’esecuzione, che può essere definita come la sequenza degli stati assunti dal processore durante l’avanzamento dei processi. • Ad un processo sono associati il codice, l’area dati, i registri e le altre risorse ad esso assegnate. Stati di un processo Un processo, dopo la sua creazione ed attivazione, può trovarsi in diversi stati di avanzamento: Attesa di caricamento ( new ) è stata richiesta l’esecuzione del programma memorizzato su disco e attende di essere caricato in memoria centrale; vi possono essere più processi in attesa di caricamento. Pronto ( ready ) il processo è in memoria centrale ed attende che gli venga assegnata la CPU; vi possono essere più processi in stato di pronto. Esecuzione ( running ) la CPU esegue il processo; vi è un solo processo in esecuzione Waiting ( in Attesa ) il processo ha richiesto una operazione di I/O e ne attende il completamento; più processi in attesa; spesso le operazioni di I/O vengono eseguite dai processori di I/O in parallelismo effettivo con la CPU Terminazione ( terminated ) il processo ha terminato l’esecuzione può rilasciare le risorse utilizzate. Descrittore del processo Ogni processo ha associato un proprio descrittore di processo ( o PCB. – Process Control Block) che viene creato in fase di "creazione" del processo e mantenuto in memoria centrale, in un’area riservata al sistema operativo, per tutta la durata del processo. Tale descrittore contiene: • PID ( Process Identification) , un numero progressivo che identifica il processo, assegnato dal SO al momento della creazione del processo; • PPID ( Parent Process Identification ) cioè il PID del processo padre cioè il processo che l’ha generato; • puntatore alla lista dei processi figli ; • stato del processo ; • priorità; • limiti della memoria centrale utilizzata; • puntatore alla lista delle risorse in uso; • copia del contesto del processo ( PC, IR, MAR, MDR, registri generali, …). Quando un processo rilascia la CPU nel suo PCB viene salvato il contesto. Quando a un processo viene assegnata la CPU la copia dei registri contenuta nel suo PCB viene copiata nei registri fisici della CPU. Ciò consente al processo di riprendere l’esecuzione dal punto in cui era stata sospesa, dall’istruzione puntata dal PC al momento della sospensione. Per Creare un processo occorre assegnare un’area di memoria centrale, copiare il programma in tale area, creare il suo PCB, porre il processo in stato di "pronto". Per Terminare un processo occorre liberare la memoria centrale allocata, rilasciare tutte le risorse da esso utilizzate, aggiornare il PCB del processo che l’ha creato. Thread Come abbiamo visto un processo è generato da un programma in esecuzione. Un programma eseguito su un processore può dare origine a più processi. Ogni processo è descritto da un PCB ( Process Control Block ) che viene generato in fase di "creazione" del processo e mantenuto in memoria centrale. I programmi tradizionali sono pensati per essere eseguiti in sequenza. Un thread ( o processo leggero ) è una sequenza di istruzioni di un programma in esecuzione, cioè il flusso di esecuzione di un processo viene scomposto in più flussi concorrenti. Per esemplificare un theread è una parte di programma che può eseguire operazioni in modo autonomo rispetto al resto. Esempi di thread sono: - Correttore ortografico di word che esegue il controllo contemporaneamente alla nostra scrittura; - Un client FTP che scarica più file contemporamneamente; - I giocatori di un gioco 3D che combattono con noi. Il thread designa un percorso di esecuzione parallela ( execution path ) ed è la minima unità di esecuzione che un S.O. può gestire in modo indipendente. Ogni thread condivide con altri thread : • il codice eseguibile • area dei dati globali • area di memoria allocata dinamicamente • tabella dei descrittori dei file Ogni thread ha non condivide con altri thread : • dati propri • stack ( variabili locali ) • il contenuto del Program counter • il set di registri Questi ultimi indispensabili per permettere una execution path indipendente per ogni thread. Con i thread si ottiene un miglioramento delle prestazioni complessive del sistema, ma esso deve avere più di una CPU o come nei sistemi moderni CPU multicore. Bisogna fare attenzione a parallelizzare l’esecuzione, infatti se la mole dei dati da elaborare non è grande, vi è il concreto rischio che il tempo necessario al S.O. per creare e sincronizzare thread sia superiore a quello necessario all’elaborazione sequenziali dei dati. Elaborazione sequenziale e concorrente Con elaborazione sequenziale si intende l’esecuzione di un programma in cui le azioni eseguite sono totalemente ordinate. Con programmazione concorrente si indicano tecniche e strumenti impiegati per descrivere attività o processi che si intendono eseguire parallelamente in un sistema di calcolo ( processi paralleli ). Il parallelismo “vero” si ha soltanto nel caso di sistemei multipocessore o multicore. Nei sistemi monoprocessore il parallelismo è solo virtuale e il loro avanzamento parallelo dipende dal tempo che gli viene assegnato dalle politiche di schedulazione del S.O. Due processi si dicono concorrenti se la prima operazione di uno di essi ha inizio prima del completamento dell’ultima operazione dell’altro. La concorrenza Come abbiamo già detto l’elaborazione non sequenziale o elaborazione parallela o concorrente si può ottenere in due modi • Con elaboratori dotati di più CPU o multicore • Con sistemi con un'unica CPU in multiprogrammazione Per poter codificare programmi non sequenziali è necessari avere linguaggi non sequenziali dotati cioè di particolari istruzioni adatte a trattare la programmazione parallela. Un programma concorrente si basa sulla scomposizione sequenziale, cioè il programmatore deve individuare le attività parallelizzabili e descriverle in termini di sequenze di istruzioni di un linguaggio concorrente. L’ istruzione fork è una istruzione concorrente che serve a generare un processo ( figlio ) che viene eseguito in parallelo al processo che lo ha generato ( processo padre ). L’istruzione join viene eseguita quando il processo figlio è terminato e si sincronizza con il processo padre. L’istruzione cobegin e coend sono istruzioni che permettono di indicare il punto in cui N thread iniziano e finiscono contemporaneamente l’esecuzione. Risorse e condivisione Una risorsa, sia essa hardware ( memoria, disco, cpu,…) o software ( file, PCB,…), è una qualche cosa necessaria al processo per avanzare verso lo stato di terminato. Una risorsa può essere assegnata: • in modo statico per tutta la durata del processo; • in modo dinamico viene assegnata, usata e rilasciata dopo l’utilizzo da parte del processo; una risorsa è detta prerilasciabile se il S.O. può costringere il processo a rilasciarla. La CPU è un esempio di questo tipo di risorsa. • Il nucleo (o Kernel) del sistema operativo si occupa, della gestione della CPU, scheduling della CPU e dell’avanzamento dei processi. La gestione delle altre risorse è effettuata da appositi gestori delle risorse quali il gestore della memoria centrale, il gestore delle periferiche, e il gestore delle informazioni (o File System). Due processi si dicono disgiunti se sono completamente indipendenti l’uno dall’altro, interagenti se accedono a risorse comuni. L’interazione fra processi può essere : • cooperazione (diretta) due processi per avanzare si scambiano messaggi; • competizione (indiretta) due processi “competono” per l’accesso ad una stessa risorsa. Comunicazione fra processi Sappiamo che due processi possono interagire in cooperazione o concorrenza per utilizzare delle risorse che gli consentiranno di avanzare dallo stato di new a quello di terminated. A prescindere dall’hardware vi sono due modi per realizzare l’interazione • modello a memoria comune ( utilizza l’ambiente globale ) • modello a scambio di messaggi ( utilizza l’ambiente locale ) Il primo utilizzato in ambienti hardware in cui esiste un’unica memoria, il secondo in ambienti di calcolo distribuito. Nel modello a memoria comune vi possono essere problemi per l’accesso alla memoria da parte di due o più processi che ne potrebbero richiederne contemporaneamente l’attribuzione. Mutua esclusione In genere una risorsa riusabile e non prerilasciabile può essere assegnata ad un solo processo per volta, in mutua esclusione cioè se un processo P ha la risorsa R, nessun altro può utilizzarla finché P non la rilascia, garantendo un tempo di attesa finito ed evitando situazioni di stallo ( o deadlock ). La mutua esclusione va garantita, pena il verificarsi di errori non controllabili. Stallo ( deadlock ) Lo stallo si ha quando due o più processi che posseggono risorse e, per avanzare necessitano di ulteriori risorse possedute da altri processi che non le possono rilasciare. L’avere più programmi che competono per l’uso delle risorse può causare situazioni di stallo (deadlock), situazioni nelle quali i processi non possono più avanzare. Le condizioni per le quali si può verificare lo stallo sono: • necessità di garantire la mutua esclusione; • attesa di risorse aggiuntive ( i processi richiedono le risorse una alla volta e non tutte insieme ); • non prerilascio delle risorse ottenute; • verificarsi di una situazione di "attesa circolare". Ad esempio il processo P1 ha in uso la risorsa R1che rilascerà solo dopo aver ottenuto la risorsaR2; P2 ha in uso R2 che rilascerà solo dopo aver ottenuto R1: STALLO! Per ovviare allo stallo ci sono alcune diverse tecniche: la prevenzione dello stallo ( deadlock prevention ) consiste nell’invalidare una o più condizioni di stallo (ad esempio si obbligano i processi a chiedere le risorse tutte insieme); in genere si previene l’attesa circolare con l’allocazione gerarchica delle risorse. la Fuga dallo stallo ( deadlock avoidance ) permette di rifiutare la richiesta di risorsa che causa (o potrebbe causare ) stallo; il Riconoscimento e recupero dello stallo ( deadlock detection and recovery ) non previene lo stallo, ma riconosce quando esso avviene (ad esempio esaminando periodicamente il grafo di allocazione delle risorse ) e lo elimina ( ad esempio forzando uno dei processi a terminare ). Competizione tra processi ( interazione indiretta ) Primitive Lock e UnLock ( attesa attiva ) Per garantire la mutua esclusione ad una risorsa si associa un semaforo binario ( un bit ) che può valere • 1 "semaforo verde", cioè risorsa libera • 0 "semaforo rosso", risorsa occupata. La richiesta della risorsa viene fatta con la primitiva Lock che verifica il semaforo: se il semaforo è a 0 lo pone a 1 ed assegna la risorsa al processo, altrimenti resta in attesa che il semaforo diventi 0 ( verificandolo ad intervalli regolari ). Questo meccanismo, che prevede il test ripetuto del semaforo ( attesa attiva ) garantisce la mutua esclusione. Le primitive disable() e enable() servono rispettivamente a mascherare ( disabilitare ) e abilitare le interruzioni ( flag IF della PSW ) per evitare che il flusso dell’ esecuzione delle istruzioni delle primitive Lock() e Unlock() venga interrotto provocando così conseguenze imprevedibili. Nel processo P che utilizza la risorsa R a cui è associato il semaforo s avremo : ………… Lock ( s ); ……… // questa è la porzione di codice in cui viene usata la risorsa R ……… // a cui è associato il semaforo s ( detta sezione critica ) UnLock ( s ); Cioè la lock() dovrà precedere la sezone critica alla fine della quale dovremo inserire la chiamata alla funzione unlock() che provvererà al rilascio della risorsa cioè a mettere “a verde” il semaforo. Questo sistema ha due gravi inconvenienti: 1. attesa attiva : la Lock() impegna inutilmente il processore a scapito di altri processi; 2. rinvio indefinito: un processo può attendere indefinitamente il semaforo verde anche se questo evento si è verificato infinite volte. Infatti se il semaforo diventa verde prima che venga riesaminato ed un altro processo chiede la risorsa, la ottiene, rimettendo il semaforo a rosso; quando si va a testare il semaforo lo si trova ancora rosso, ma nel frattempo la risorsa è stata data ad un alto processo; in teoria questo potrebbe verificarsi per un tempo indefinito. Le funzioni primitive P() e V() ( attesa passiva ) Con questo meccanismo a ciasuna risorsa viene associata • semaforo s • Q(s) una coda destinata a contenere i PID dei processi che l’hanno richiesta. La richiesta della risorsa viene fatta con la primitiva P() che testa il semaforo: se il semaforo è a 1 ( verde ) lo pone a 0 ( rosso ) ed assegna la risorsa al processo, altrimenti pone il PID del processo nella coda di attesa e pone in attesat il processo. Il rilascio della risorsa viene fatto con la primitiva V() che controlla se la coda di attesa è vuota: se si pone il semaforo a verde, altrimenti lo lascia a rosso ed assegna la risorsa al primo processo della coda. Questo meccanismo di attesa passiva garantisce sia la mutua esclusione che l’attesa finita. Le primitive P() e V() sono conosciute anche come wait () e signal () . Cooperazione tra processi ( interazione diretta ) Nel modello ad ambiente locale un processo può accedere solo alla sua memoria ed essa non può essere modificata da altri processi. Non avendo memoria in comune l’unico strumento di comunicazione e sincronizzazione fra i processi è lo scambio di messaggi. In questo modello ogni processo può accedere esclusivamente alle risorse allocate nella propria memoria locale e ogni risorsa del sistema è accessibile a un solo processo ( server ) che può operare su di essa, eseguire le operazioni richieste dai processi applicativi ( processi client ) che ne hanno bisogno. Client e server comunicano tramite lo scambio di messaggi. Client e server comunicano tramite un mezzo trasmissivo che da un punto di vista logico viene detto canale. Le principali caratterisstiche del canale sono : • monodirezionale o bidirezionale • comunicazione sincrona o asincrona Nella comunicazione asincrona il mittente dopo l’invio del messaggio procede nell’esecuzione senza attendere una risposta. Nella comunicazione sincrona mittente e destinatario sevono essere ambedue pronti a trasmettere / ricevere il messaggio cioè vi deve essere un randez-vous ( incontro ) fra i due. Il randez-vous può essere : randez-vous semplice Il mittente invia un messaggio e si sospende in attesa che il destinatario si a pronto ad eseguire l’operazione richiesta randez-vous esteso Il destinatario una volta ricevuto il messaggio deve rispondere al mittente e quindi il mittente ricevuta la conferma, rimane in attesa che il destinatario esegua l’operazione richiesta Per la comunicazione asincrona esistono delle funzioni primitive che sono send() e receive() ma prima di utilizzarle va definito il canale con l’istruzione port <tipo> <identificatore>; ad esempio port int canale1; Per trasmettere send (<messaggio>) to <port>; ad esempio send ( 777 ) to canale1 Per ricevere receive (<variabile>) from <port> ad esempio receive ( dato ) from canale1 Il problema del produttore-consumatore Il problema del produttore-consumatore è un esempio di sincronizzazione tra processi. Il problema descrive due processi, uno produttore ed uno consumatore, che condividono un buffer comune, di dimensione fissata. Compito del produttore è generare dati e depositarli nel buffer in continuo. Contemporaneamente, il consumatore utilizzerà i dati prodotti, rimuovendoli di volta in volta dal buffer. Il problema è assicurare che il produttore non depositi nuovi dati se il buffer è pieno, e che il consumatore prenda dati se il buffer è vuoto. Sincronizzare vuol dire che la "produzione" di un messaggio da parte del processo trasmittente deve avere sempre lo stesso tempo del "consumo" da parte del processo ricevente. La sincronizzazione viene fatta tramite : • La funzione P ( semaforo ) che mette a rosso semaforo • La funzione V ( semaforo ) che mette a verde semaforo • Il semaforo prelevato inizializzato a verde, utilizzato dal consumatore per segnalare lo svuotamento del buffer; • Il semaforo depositato inizializzato a rosso, utilizzato dal produttore per segnalare la presenza nel buffer di nuovi dati. Il produttore, dopo aver depositato un dato nel buffer, non può scrivere nel buffer sino a quando il consumatore non ha prelevato il dato e, analogamente, il consumatore non può usare più volte il dato inserito nel buffer. Per queste ragioni l’accesso al buffer avviene in mutua esclusione. Esercitazioni Per poter “lavorare” con i thread con il Wxdev C++ dovremo installare la libreria Pthreads ( POSIX Threads ) costituita da 60 funzioni suddivise in tre classi : • • • Funzioni per gestire thread ( creare, distruggere, aspettare un thread ) Funzioni per la gestione della mutua esclusione ( Mutex ) : init, destroy, lock, unlock, . . . Condition variable: costrutti e funzioni per la comunicazione tra thread che condividono un mutex per aspettare o segnalare il verificarsi di eventi: init, destroy, wait, timed wait, . . . Pthreads: convenzioni • Nomi tipi: pthread_object_t • Nomi funzioni: pthread_object_action • Costanti/Macro: PTHREAD_PURPOSE Se le funzioni portano ha termine con successo il proprio compito generalmente ritornano il valore 0, 1 in caso contrario. Pthreads: installazione Se in Strumenti Package Manager del Wxdev C++ è presente l’icona : vuol dire che la libreria dei thread è installata per cui le istruzioni che seguono sono inutili. Scarica da http://oopthread.googlecode.com/files/pthreads_w32-2.8.0-1aj.DevPak La libreria per la gestione dei thread pacchettizzata per WxDev C++ . Se hai installato WxDev C++ fai direttamente Aprilo con .. come mostrato in figura Viene così richiamato direttamente il Package Manager del WxDev C++ Per verificare la corretta installazione in WxDev C++ Strumenti - Package Manager E viene visualizzata la finestra seguente : Controllare la presenza di Pthreads-w32. Dopo aver installato la libreria • Creare un nuovo progetto in Dev C++ • Scegliere dal menù Progetto Opzioni del Progetto Opzioni addizionali riga comando Aggiungi libreria selezionare C:/Program Files (x86)/Dev-Cpp/lib/libpthreadGC2.a OK NOTA Verificare in quale cartellla sono stati inseriti i tre Header: pthread.h sched.h semaphore.h ed eventualmente aggiungerne in Strumenti Opzioni di compilazione Cartelle Include C++ il path. Ora siamo pronti per la compilazione del progetto Identificatore di un thread Come un processo, anche un thread ha un proprio ID ( tid ), ma a differenza del pid , il tid ha senso solo all’interno del processo a cui il thread appartiene. pthread_t pthread_self ( void ); pthread_t pthread_equal ( pthread_t tid1, pthread_t tid2 ); // per conoscere il proprio tid // per confrontare due tid Creazione di un thread int pthread_create ( pthread_t *tid, pthread_attr_t *attr, void *(*funzione)(void *), void *arg ); // per creare un thread I parametri sono: tid : il tid del thread creato attr: per impostare gli attributi del thread ( NULL di default ) funzione: funzione eseguita dal thread creato arg: argomento di ingresso per funzione Una volta creato, un thread può creare altri thread; non c’è gerarchia o dipendenza implicita tra thread. Esempio: #include <pthread.h> #include <iostream> #include <windows.h> using namespace std; void * thread1( void *arg ) { // primo thread int i; cout << "x"; Sleep ( 500 ); // ritardo per rallentare l'esecuzione cout << "y"; Sleep ( 500 ); cout << "z"; Sleep ( 500 ); return NULL; } void * thread2( void *arg ) { int i; cout << "a"; Sleep ( 500 ); cout << "b"; Sleep ( 500 ); cout << "c"; Sleep ( 500 ); return NULL; } // secondo thread int main ( void ) { // programma principale che richiama i due thread pthread_t tID1; pthread_t tID2; while(1) // ciclo infinito che esegue in continuazione i due thread { pthread_create ( &tID1, NULL, &thread1, NULL ); // creazione del primo thread pthread_create ( &tID2, NULL, &thread2, NULL ); // creazione del secondo thread pthread_join ( tID1, NULL ); // attende il termine del 1^ thread pthread_join ( tID2, NULL ); // attende il termine del 2^ thread cout << "\n"; } return 0; } Terminazione di un thread Un thread può terminare perché: – L’intero processo viene terminato con una chiamata alle funzioni exit() o exec() – Il thread termina l’esecuzione della sua routine – Il thread chiama pthread_exit() La funzione int pthread_exit( void * rval_ptr ); viene usata per terminare thread e non causa la chiusura di file aperti dal thread che la chiama. Un thread può esser cancellato da un altro thread tramite pthread_cancel(). Sincronizzazione dei processi mediante semafori Come abbiamo già visto per evitare che due processi usino la stessa risorsa contemporaneamente è necessario implementare il meccanismo della mutua esclusione attraverso l’utilizzo : – di un semaforo ( variabile binaria ) associato alla risorsa – delle due funzioni lock() e unlock() ( meglio la P()e la V() ) le cui chiamate vengono inserite rispettivamente prima e dopo la sezione critica ( frazione di codice in cui viene utilizzata la risorsa a cui è assiciato il semaforo ) del codice. In sostanza dobbiamo fare in modo che le due sezioni critiche dei due processi non vengano eseguite contemporaneamente. Nella libreria pthread il semaforo è detto mutex e che naturalmente presenta due stati: aperto o verde o 1 ( unlocked ) o chiuso o rosso o 0 ( locked ) E’ evidente che solo un thread alla volta può possedere il mutex cioè ottiene il lock. Per definire un mutex utilizzeremo: pthread_mutex_t nomeSemaforo = PTHREAD_MUTEX_INITIALIZER; Per bloccare la sezione critica del processo che usa la risorsa a cui è associato il mutex, cioè la funzione che abbiamo chiamato P() dovremo usare: int pthread_mutex_lock ( pthread_mutex_t * nomeSemaforo ); int pthread_mutex_trylock ( pthread_mutex_t * nomeSemaforo ); La prima è bloccante la seconda no ( utile per evitare lo stallo ) Il parametro della funzione è il puntatore al mutex da bloccare Il valore di ritorno è un intero che se uguale a 0 indica il successo. La trylock() restituisce EBUSY se il mutex è occupato. Per mettere “a verde” il mutex utilizzeremo int pthread_mutex_unlock ( pthread_mutex_t * nomeSemaforo ); int pthread_mutex_destroy ( pthread_mutex_t * nomeSemaforo ); La prima funzione sbolocca il mutex soltanto, la seconda lo distrugge. Il parametro della funzione è il puntatore al mutex da sbloccare / distruggere Il valore di ritorno è un intero che se uguale a 0 indica il successo. Esempio Prendiamo in considerazione l’esempio precedente l’output si presentava così: xabyzc xaybzc cioè l’output del 1^ thread si mescolava con quello del secondo. L’uso di un semaforo ( mutex ) associato alla risorsa monitor, come mostrato di seguito, elimina l’inconveniente. ………. pthread_mutex_t monitor =PTHREAD_MUTEX_INITIALIZER; // definisco semaforo di nome monitor void * thread1( void *arg ) { // primo thread int i; pthread_mutex_lock ( &monitor ); // semaforo occupato quindi risorsa ad uso esclusivo del thread cout << "x"; cout << "y"; cout << "z"; Sleep ( 500 ); pthread_mutex_unlock ( &monitor ); // semaforo libero return NULL; } void * thread2( void *arg ) { // secondo thread int i; pthread_mutex_lock ( &monitor ); // semaforo occupato quindi risorsa ad uso esclusivo del thread cout << "a"; cout << "b"; cout << "c"; Sleep ( 500 ); pthread_mutex_unlock ( &monitor ); // semaforo libero return NULL; } ………… ………… Esempio del problema del produttore consumatore #include <pthread.h> #include <iostream> #include <windows.h> using namespace std; int m; pthread_mutex_t prelevato, depositato; void *produttore ( void * ) { int conta = 0; while ( conta < 10 ) { pthread_mutex_lock( &prelevato ); m = ++conta; cout << "Prodotto " << m << '\n'; pthread_mutex_unlock(&depositato); } pthread_exit(0); } // variabile condivisa // dichiaro semafori // P(prelevato) // produci un numero progressivo // V(depositato) per il consumatore void *consumatore ( void *) { int conta = 0; while ( conta < 10 ) { pthread_mutex_lock( &depositato ); // P(depositato) aspetta il dato conta = m; // consuma il numero progressivo cout << "Consumato: " << conta << '\n'; pthread_mutex_unlock( &prelevato ); // V(prelevato) per il produttore } pthread_exit(0); } int main() { pthread_t produci, consuma; //dichiaro le variabili relative ai thread pthread_mutex_init ( &prelevato, NULL ); // semaforo prelevato a verde pthread_mutex_init ( &depositato, 0 ); // semaforo depositato a rosso pthread_create ( &produci, NULL, produttore, NULL ); // creo il produttore pthread_create ( &consuma, NULL, consumatore,NULL ); // creo il consumatore pthread_join ( produci, NULL ); pthread_join ( consuma, NULL ); pthread_mutex_destroy ( &prelevato ); pthread_mutex_destroy ( &depositato ); system ( "PAUSE"); return 0;} // attesa del termine dei figli // distruzione dei semafori Ciclo di sviluppo di un progetto informatico Il progetto informatico La realizzazione del software è un' attività complessa. Per diminuire le probabilità che vengano commessi gli errori durante la realizzazione del software, sono state sudiate metodologie e modelli che hanno lo scopo di rappresentare, prevedere, programmare e gestire un progetto informatico. Questa tipologia di studi prendno il nome di ingegneria del software. Produrre oggi del software è complesso perchè: • Si è passati da elaborazioni batch a quelle on line; • I progetti sono indirizzati ad un più largo mercato più che ad un unico committente; • Il software da monoutente è passato a multiutente in ambienti concorrenti. • Le soluzioni software sono orientate ad un intera organizzazione più che a settori specifici. Se consideriamo la realizzazione di un edificio prima della realizzazione dello stesso vi è una fase precedente che comprende la realizzazione di modelli, progetti, disegni e calcoli, studio del terreno, vincoli derivanti dall'impatto ambientale o urbanistico, ecc.. ecc.. Solo dopo aver svolto e completato tutte queste attività sulla carta, si procede con il progetto vero e proprio: l'acquisto dei materiali, il coinvolgimento di macchinari, la selezione di personale qualificato nel numero e con le mansioni adatte. La costruzione procede in modo ordinato: prima lo scavo, poi le fondazioni, la struttura, gli interni e così di seguito fino alla consegna al cliente finale. Un progetto informatico è un processo che porta alla realizzazione di un manufatto informatico secondo le specifiche di un cliente. A questo processo concorrono uomini, oggetti, attività, piani e decisioni. Durante la gestione del progetto informatico si fa uso di modelli che servono per descrivere il problema. Un modello, in generale, è utilizzato per la descrizione di un sistema, cioè un insieme di elementi, interconnessi tra loro. Esempi di modelli sono: una legge della fisica, un plastico che rappresenta un edificio, lo schema di un circuito elettrico, un manichino, la mappa di una città. Attraverso un modello possiamo: • Definire un sistema in modo preciso e sintetico. • Ottenere una semplificazione del sistema, • Poter prevedere il comportamento del sistema. Alla attivita progettuale prendono parte diversi soggetti: • il committente è il soggetto che presenta il problema da risolvere. • L'analista che partendo dalle esigenze del committente le traduce modello. • Lo sviluppatore che partendo dal modello e di realizza il prodotto che realizza il un progetto informatico • il coordinatore di progetto è l'attore che individua i tempi, le risorse umane ed economiche, le modalità e i rischi derivanti dalla realizzazione, e interviene con azioni correttive laddove si presentino difficoltà od errori. In generale abbiamo modelli che utilizzano due differenti tipi di approccio: • top-down o decomposizione funzionale che consente il passaggio da un problema complesso a problemi di complessità minore, la soluzione dei quali consente di ottenere una soluzione al problema iniziale. • bottom-up o composizione modulare, nel quale, partendo da sottoproblemi di cui è già nota la soluzione, si cerca di comporli in un'unica soluzione complessiva che risolva il problema principale. I vantaggi dell'approccio top-down sono: • Il modello riflette più fedelmente la realtà che si vuole rappresentare. • Il modello non presenta le informazioni non richieste o non rilevanti per il raggiungimento dell' obiettivo. Gli svantaggi dell'approccio top-down sono: • Si corre il rischio di realizzare più volte qualcosa che è già stato realizzato in un progetto precedente o che è già disponibile sul mercato. • Si può perdere il beneficio derivante dall'uso di una funzionalità, non espressamente richiesta, presente in un modulo già sviluppato per altri progetti, o in un prodotto presente sul mercato. I vantaggi dell'approccio bottom-up sono: • I singoli problemi di minor dettaglio sono già risolti. • La soluzione finale contiene caratteristiche aggiuntive che possono rivelarsi utili, anche se non espressamente richieste. Gli svantaggi dell'approccio bottom-up sono: • Si deve compiere il lavoro aggiuntivo per integrare tra loro i moduli già sviluppati. • Le caratteristiche non richieste, presenti nella soluzione, possono rendere il prodotto finale complesso da utilizzare. Fasi e deliverables Una metodologia top-down porta alla sua suddivisione in fasi che, messe in connessione l'una con l'altra, forniscono una rappresentazione dell'intero processo. PROCESSO In ogni fase si possono identificarne attività o task cioè le azioni o compiti, che devono essere svolti da uno o più soggetti partecipanti al processo di sviluppo del progetto. La freccia in uscita da un'attività e in ingresso alla successiva rappresenta il diliverable cioè un prodotto intermedio necessario alla prosecuzione dell'attivita e quindi alla conclusione della fase. Il diliverable può provenire anche dall'esterno. Un deliverable deve essere chiaramente identificato e riconducibile alla fase del progetto che lo ha generato e inoltre deve essere riconoscibile e interpretabile da tutti i partecipanti al progetto. Vediamo alcuni esempi concreti di deliverable: • Un programma eseguibile per effettuare i test: realizzato per effettura e un collaudo. • Un documento che descriva i risultati dell'attività di test di un programma Ogni fase intesa come un in sieme di attività devono produrre un insieme di deliverable, ciascuno dei quali sia: • Ottenibile partendo dai deliverable prodotti dalle attività presenti in una o più fasi precedentemente completate. • Utilizzabile per lo svolgimento delle attività di una o più fasi successive. Le attività all'interno di una fase possono essere ordinate cronologicamente o logicamente per consentire la produzione di un piano di lavoro che contenga la sequenza completa di tutti i compiti da svolgere per portare a termine il progetto. L'insieme delle attività che devono essere svolte prima di una certa attività è detto insieme dei predecessori. L'insieme delle attività che possono essere svolte dopo il completamento di una certa attività è detto insieme dei successori. Fasi standard di un processo di sviluppo La standardizzazione svolge una funzione fondamentale nell'evoluzione dei processi di produzione di qualsiasi bene o servizio: questo vale anche per il processo di produzione del software. Definire uno standard è conveniente per i seguenti motivi: • Favoriscono la condivisione delle informazioni; • Suddividere un un servizio o prodotto complesso in parti o moduli, se questi si interfacciano l'uno con l'altro secondo modalità standard. • E' più conveniente condividere conoscenza che avviare nuovi studi; • Minimizzare la possibilità di errori; • Facilità di trovare documentazione riguardante uno standard; • Il prodotto realizzato secondo standard è facilmente integrabile con soluzioni più complesse; • Di contro potremo dire che : • Limita l'innovazione; • Dal punto di vista del produttore lo standard pone il prodotto in un mercato aperto con maggiore concorrenza. Ci sono numerosi enti e organizzazioni svolgono il ruolo di punto di riferimento per la definizione di uno standard. Ad esempio IEC (lnternational Electrotechnica! Commission) IEEE (lnstitute of Electrical and Electronics Engineers) ANSI (American National Standards Institute). IETF (Internet Engineering Task Force) W3C (World Wide Web Consortium) ECMA (European Computer Manufacturers Association) SEI (Software Engineering Institute) Il lavoro di gruppo Un software poco compesso potrebbe essere sviluppato da una singola persona Le fasi dovrebbero essere necessariamente sequenziali, ogni attività inizia quando la precedente finisce. Rispetto a problemi più complessi questa modalità potrebbe creare problemi: • Probabilmente un singolo non avrebbetutte la conoscenza e le competenze necessarie. • Due o più attivita non possono essere svolte in parallelo ( contemporaneamente ). In un progetto di produzione del software più complesso, sono coinvolte più figure professionali: • Capo progetto o Proiect Manager (PM): pianifica risorse, tempo, e costi del progetto. Si occupa della comunicazione fra le persone coinvolte nel progetto. • Analista: raccoglie i requisiti funzionali e non funzionali insieme al c1iente committente. L'analista solitamente oltre all'esperienza di produzione del software ha anche conoscenza delle tematiche che il software si propone di risolvere, quindi è in grado di interpretare al meglio le richieste del committente, cercando di risolvere dubbi, imprecisioni, lacune e contraddizioni. • Architetto: si occupa della progettazione dell'architettura del software. È un esperto di moduli, librerie, semilavorati e progetti precedenti, che possano costituire una base per lo sviluppo del nuovo software. • Progettista di interfaccia grafica: si occupa della progettazione delle interazioni che il software avrà con l'utente. • Sviluppatore: si occupa della scrittura di algoritmi. Conosce uno o più linguaggi di programmazione con cui produce i moduli software. • Collaudatore: si occupa della verifica del software. Accedendo ad un ambiente apposito, detto ambiente di collaudo, verifica che il software sviluppato risponda pienamente ai requisiti espressi dal committente Il piano di progetto Con il termine WBS (Work Breakdown Structure, struttura analitica di progetto) si indica un albero che rappresenta degli obiettivi del progetto. Ogni ramo dell'albero rappresenta un risultato o un obiettivo da raggiungere e può avere rami discendenti: questi, se presenti, rappresentano gli obiettivi o i risultati in cui è possibile decomporre l'obiettivo principale. La radice è il prodotto che corrisponde alla realizzazione dell'obiettivo principale del progetto. Ogni elemento nella WBS ha le seguenti caratteristiche: • Ha un codice identificativo. • E' un deliverable; per questo si dice che la WBS è orientata ai risultati e non alle azioni. • I deliverable identificati come obiettivi in un elemento dell'albero non devono essere presenti in nessun altro elemento. • La sequenza con cui sono rappresentati gli elementi non è necessariamente una sequenza cronologica. La WBS viene utilizzata, nelle attività di gestione del progetto, come base su cui determinare le informazioni fondamentali per il piano di progetto: • Il lavoro da svolgere per raggiungere un obiettivo o un risultato. • Il materiale necessario per realizzare il risultato. • Il tempo necessario per svolgere il lavoro. • Il costo della realizzazione del risultato. • Le abilità necessarie per portare a termine il lavoro. Le informazioni associate ad ogni elemento della WBS, non fanno parte della WBS in senso stretto, ma sono da essa derivate. Esempio Organizzare una festa di compleanno. Occorre considerare tutte le attività necessarie per la preparazione e lo svolgimento della festa. Scegliamo come schema di codifica la notazione puntata gerarchica (1, 1.1, 1.1.1, ... ). Ogni elemento della WBS rappresenta un deliverable quindi risultato o un prodotto. 1. Festa di compleanno 1.1 Lista degli invitati 1.1.1 Invio degli inviti 1.1.2 Raccolta delle adesioni 1.2 Attrezzature e suppellettili 1.2.1 Tavoli, sedie e panchine 1.2.2 Piatti, bicchieri e posate 1.2.3 Illuminazione 1.2.4 Impianto audio/video 1.3 Consumazioni 1.3.1 Bevande 1.3.1.1 Analcolici 1.3.1.2 Alcolici 1.3.2 Dolci 1.3.2.1 Torta di compleanno 1.3.2.2 Altri dolci 1.3.3 Salatini 1.4 Loc:ation 1.4.1 Affitto della sala 1.4.2 Allestimento della sala 1.5 Intrattenimento musicale 1.5.1 Ingaggio del DJ 1.5.2 Selezione dei brani e delle playlist 1.6 Pulitura locali 1.6.1 Smaltimento rifiuti 1.6.2 Pulitura sala 1.7 Assolvimento degli obblighi amministrativi 1.7.1 Autorizzazione della polizia locale 1.7.2 Autorizzazione della SIAE Un piano di progetto è la descrizione ordinata di attività, ruoli, tempi e costi con cui il progetto deve essere eseguito. Il piano di progetto è il risultato della fusione della metodologia di gestione del progetto con l'obiettivo che il progetto si prefigge. Esso contiene : • Data di inizio. • Data di consegna. • Elenco delle persone coinvolte nel progetto e relativi ruoli. • Costo del lavoro delle persone. • Elenco dei materiali necessari per il progetto (sistemi, hardware, licenze). • Costo dei materiali. • Elenco delle attività con indicazione delle persone che devono svolgerle e delle date di inizio e di fine di ogni attività. • Elenco delle date in cui è previsto un incontro con .il committente. Le informazioni contenute nel piano di progetto sono previsioni quindi questa previsione deve essere periodicamente rivista sulla base dei cambiamenti, degli esiti dei test, del verificarsi o meno di situazioni impreviste o della modifica delle condizioni iniziali. È compito del gestore del progetto informare i partecipanti dello stato delle attività, della loro percentuale di completamento e, in generale, delle modifiche apportate al piano di progetto. Il piano di progetto diventa lo strumento di comunicazione più importante per il lavoro di squadra. Solitamente un piano di progetto è rappresentato graficamente con il diagramma di Gantt che ha lo scopo di pianificare nel tempo ogni fase unitamente alle risorse che sono state individuate per lo svolgimento del lavoro. Il diagramma di Gantt ha due assi perpendicolari: l'asse orizzontale rappresenta il tempo, l'asse verticale rappresenta le attività. Le caratteristiche generali del diagramma sono: • Ogni attività è rappresentata da una barra rettangolare sul diagramma. • Su ogni riga del diagramma può essere rappresentata una sola barra • Barre diverse possono riferirsi allo stesso periodo di tempo per indicare attività parallele. • La lunghezza della barra, letta sull'asse orizzontale, rappresenta la durata dell'attività. • La posizione della barra, letta sull'asse orizzontale, rappresenta la pianificazione dell'attività; essa indica le date di inizio e fine dell'attività. Sul diagramma di Gantt possono essere indicate anche le attività cardine, denominate con il termine inglese mi/estone (letteralmente pietra miliare). Vengono rappresentate con un rombo e indicano un momento fondamentale nel piano di progetto. Il diagramma di Gantt aiuta a determinare le attività da svolgere per raggiungere i risultati definiti nella WBS, fornendo un utile strumento di calcolo nella previsione del progetto. Esercizio Definite il diagramma di Gantt del progetto Festa di compleanno 6. Modello a cascata Come si è visto in precedenza, per eseguire una certa attività è indispensabile avere a disposizione il risultato, o deliverable, dell'attività precedente. Analizzando il processo di sviluppo, si viene a creare una sequenza ordinata di attività, che mette in connessione le varie fasi dello sviluppo del progetto, detta modello a cascata, in inglese waterfall . II modello a cascata è stato impiegato per diversi decenni e si propone ancora come valida guida nel corretto approccio al progetto informatico. Diversi studi hanno dimostrato come l'adozione di un corretto processo di sviluppo del software aiuti a produrre un risultato di maggiore qualità e con minori costi. II costo della correzione dei difetti presenti in un software risulta correlato sia alla fase in cui il difetto viene rilevato, sia alla fase in cui il difetto è stato introdotto nel software. Un difetto o anomalia, si presenta quando un software ha un comportamento non atteso, cioè quando non rispetta un requisito definito con il committente. La correzione del difetto si ottiene intervenendo sul deliverable che contiene il difetto e revisionando tutti i deliverable derivati dal deliverable difettoso. Il difetto nel software può derivare da diverse fasi: • Nella specifica dei requisiti: un requisito non capito porta ad una progettazione non corretta o parzialmente corretta. • Dalla fase di progettazione: pur avendo ben compreso un requisito, una scelta di progetto si rivela errata, perché, per esempio, non risponde ai requisiti di prestazione o di costo. • Dalla fase di implementazione: sono stati commessi errori durante la scrittura del codice sorgente. Ci possiamo accorgere di un difetto dopo molto tempo, durante una delle fasi della vita del software: in generale più tardi ci accorgiamo dell'errore maggiore è l'impatto della correzione. La tabella che segue rappresenta il costo delle correzioni in relazione alla fase in cui si è generato e in quella in cui ci siamo accorti dell'anomalia. Introduzione del difetto Analisi dei requisiti Progettazione Implementazione Analisi dei requisiti Progettazione Identificazione del difetto Implementazione Test Dopo installazione 1 3 5 - 10 10 10 - 100 10 1 15 10 25 - 100 10 - 25 Da queste osservazioni si ricavano due obiettivi da perseguire durante lo sviluppo di un progetto informatico: • Condurre l'analisi nel modo più esaustivo ed efficace possibile. • Testare i deliverable appena è possibile. Il punto debole del modello a cascata consiste nella quantità di tempo che può trascorrere tra il momento in cui i difetti vengono introdotti e il momento in cui vengono identificati. Questo tempo deve essere il più breve possibile. I fattori che fanno dilatare questo tempo sono la dimensione del software e la sua complessità. Più il software è di grandi dimensioni, maggiore è il tempo che ciascuna fase impiega per essere completata. Per esempio, non essendo possibile testare un software fino a che esso non sia stato implementato, può trascorrere un tempo considerevole per riscontrare un comportamento anomalo rispetto ai requisiti. Più il software è complesso, maggiore è il rischio di effetti collaterali a catena, i quali aumentano l'impatto del singolo difetto, estendendolo anche a grosse porzioni di software. Occorre considerare un ulteriore aspetto: il problema nella sua completa definizione non è noto con precisione nelle fasi iniziali del processo, pertanto è spesso impossibile che tali fasi producano risultati perfetti. Le fasi iniziali, anche se complete e condotte in modo corretto, possono contenere errori che si evidenziano solo nelle fasi successive del processo. I correttivi migliorativi che possono essere apportati al modello a cascata sono: • L'introduzione di iterazioni e ciclicità già nella metodologia di gestione del progetto, con attività più precoci e frequenti di verifica e testing, con l'obiettivo di far emergere il più presto possibile i difetti e poter riportare ciclicamente il progetto alla fase precedente. • La riduzione della dimensione della singola unità di software: il problema di grandi dimensioni e di elevata complessità viene ridotto a problemi più piccoli e di complessità minore. La dimensione e la complessità del software risultante dalla ricomposizione funzionale sono di solito più elevate, perché occorre considerare anche il lavoro di integrazione tra i vari moduli. Di contro il maggiore contenuto qualitativo che si ottiene, rende comunque più conveniente il software sviluppato con un approccio modulare. Gestione della qualità La qualità rappresenta il valore che un prodotto porta con sé, oltre a quanto normalmente viene percepito ad una osservazione superficiale. La qualità può essere un fatto soggettivo: è il fattore che rende un prodotto migliore o peggiore di un altro prodotto simile. La ricerca della qualità in ambito industriale ha l'obiettivo di offrire sul mercato un prodotto diverso dalla concorrenza perché migliore. La qualità di un prodotto ha sempre un costo e pertanto viene offerto sul mercato ad un prezzo maggiore. Il mercato richiede un prodotto di qualità elevata con un prezzo che deve essere il più basso possibile. Quindi c'è una sorta di contrapposizione tra qualità e prezzo. L'obiettivo per qualunque azienda diventa quindi produrre beni e servizi con alta qualità senza alzare troppo il prezzo: quello che comunemente viene detto aumentare il rapporto qualità/prezzo. Gli standard ISO 9000 definiscono la qualità come il grado di aderenza delle caratteristiche di un prodotto ai fabbisogni che questo deve soddisfare. Le caratteristiche di qualità vengono osservate da tre punti di vista differenti: • Esterno: ciò che del software è osservabile dall'esterno, attraverso i test e le relative risposte. • Interno: ciò che del software è osservabile dall'interno, quindi conoscendo e avendo a disposizione l'implementazione e il codice sorgente. • In uso: ciò che del software è osservabile dall'utente in termini di efficacia, produttività, sicurezza e soddisfazione. Le caratteristiche enunciate devono essere valutate numericamente altrimenti risulterebbe difficile definire un livello di qualità. Generalmente alla fine del ciclo di sviluppo viene introdotta una nuova fase che ne definisca la qualità. La qualità del software deriva, oltre che dal modello del ciclo di vita, anche da fattori meno misurabili e più astratti, come: • L'esperienza e il livello professionale dei membri della squadra nei diversi ruoli. • L'aggiornamento tecnologico. • La qualità dell'ambiente lavorativo. • La sicurezza dell'ambiente di lavoro. • Certezza, chiarezza e trasparenza dei contratti che regolano i rapporti tra tutte le parti partecipanti ai progetti informatici.