Appunti di Tecnologie e progettazione di Sistemi Informatici e di

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.