Professional Documents
Culture Documents
Dispensa Di Informatica
Dispensa Di Informatica
Stefano Marrone
October 6, 2019
Questa dispensa ha lo scopo di fornire allo studente una trattazione sintetica degli
argomenti teorici relativi all’insegnamento di Fondamenti di Informatica per il corso
di Laurea in Matematica presso il Dipartimento di Matematica e Fisica dell’Università
della Campania “Luigi Vanvitelli”.
1 Introduzione
1.1 Studio dell’informatica
L’informatica riveste un ruolo essenziale nella vita di tutti i giorni diventando uno degli
strumenti principali nell’analisi e nella manipolazione del mondo contemporaneo. La
preparazione di uno studente in materie scientifiche non puó, pertanto, prescindere
dallo studio di tale materia.
La nostra vita é ormai permeata in tutte le sue forme dalla presenza del calcolatore:
non esiste nessun aspetto in cui il calcolatore non abbia fatto sentire il suo peso cam-
biando non solo il modo in cui i prodotti ed i servizi sono realizzati ed offerti, ma anche
l’interazione tra l’uomo e gli oggetti stessi. Basti pensare ad ambiti quali il diverti-
mento (cinema, videogiochi, intrattenimento, ...) oppure le comunicazioni (Internet,
telefonia mobile e fissa, email, ...) fino ad arrivare a settori vitali come la medicina con
la presenza di dispositivi come i pacemaker. Questo fenomeno é ormai cosı́ irreversibile
che l’immagine stessa di alcuni prodotti e servizi é totalmente cambiata da quando il
calcolatore é entrato in essi (es. l’auto).
L’informatica ed i calcolatori sono ormai diventati essenziali anche nello studio delle
scienze. Intuitivamente le scienze di tipo sperimentale traggono grande giovamento
dalla presenza del calcolatore: il computer é diventato ormai il centro di tutti i labora-
tori poiché unisce alla possibilitá di ricreare i piú disparati ambienti attraverso simu-
lazioni software, la capacitá di calcolare funzioni estremamente complesse con grande
precisione ed affidabilitá. Inoltre la sua grande velocitá lo rende strumento indispens-
abile nell’acquisizione, memorizzazione ed analisi di grandi quantitá di dati, al centro
di ogni esperimento di scienza applicata (fisica sperimentale, biologia, ...). Oltre a tali
discipline, il calcolatore sta diventando uno strumento sempre piú importante anche
nella matematica dato il suo utilizzo per risolvere problemi difficilmente risolvibili per
via analitica. Tali problemi includono non solo equazioni differenziali spesso risolti in
via numerica ma anche teoremi dimostrati solo dopo l’avvento del calcolatore (come
ad esempio il teorema dei quattro colori).
Y = F (X)
dove:
3
• X: insieme degli ingressi;
• Y: insieme delle uscite;
• finita: la lista della spesa é un qualcosa di finito ed inoltre il numero di azioni che
si svolgono é sempre finito (non si rischia di stare in eterno nel supermercato!);
• passi : ogni singolo passo (riempire il carrello, cercare sullo scaffale, ...) non im-
plica per sé il concetto di spesa: é solo nella sequenza (ossia nella concatenazione
intelligente di tali passi) che il problema viene risolto;
4
Leggi dalla lista INIZIO
Cerca prodotto
NO
è sullo scaffale?
SI
Metti prodotto
nel carrello
Cancella prodotto
dalla lista
NO SI
lista vuota? Vai alla cassa FINE
test che sta eseguendo. Inoltre un calcolatore esegue solo ció per cui é programmato:
non improvvisa e non inventa (se non nei limiti di quanto é programmato appunto!).
Un concetto fondamentale é quello di sequenza statica e dinamica. La sequenza stat-
ica definisce le azioni da svolgere indipendentemente dai dati specifici che ritroviamo
in ingresso: le azioni che un esecutore del nostro algortimo di esempio deve svolgere
in modo del tutto indipendente da quanti (e quali) elementi sono presenti nella lista.
Questa é una sequenza “astratta”, nel senso che non tiene conto di attivitá che possono
ripetersi (considerando ad esempio una lista con due o piú prodotti presenti sullo scaf-
fale, l’azione Metti prodotto nel carrello verrá eseguita piú volte). Di natura contraria
é la sequenza dinamica che fotografa la sequenza esatta di azioni considerando anche
le ripetizioni: ovviamente tale sequenza é dipendente dai dati in ingresso. Facciamo
un esempio sulla base dell’algoritmo prima descritto:
Caso 1; Lista=(petto di pollo, vino bianco, insalata):
Sequenza dinamica
1. INIZIO;
2. leggi dalla lista (petto di pollo);
5
3. cerca prodotto;
4. (é sullo scaffale?: SI) metti prodotto nel carrello;
Alternativamente presentiamo un secondo esempio che, sulla base dello stesso algo-
ritmo (e quindi della stessa sequenza statica di istruzioni) genera una lista dinamica
diversa.
Caso 2: Lista=(caffé, filetto di tricheco)
Sequenza dinamica
1. INIZIO;
2. leggi dalla lista (caffé);
3. cerca prodotto;
4. (é sullo scaffale?: SI) metti prodotto nel carrello;
6
1.3 Informazione, dato e valore
Cosa é e da dove viene l’informazione? Cerchiamo di spiegare questo concetto a partire
da esempi concreti. Analizziamo le seguenti frasi: π vale 3,14159...; oggi non piove;
questo é il corso di Fondamenti di Informatica. Queste sono chiaramente frasi che
portano una qualche informazione: ma cosa hanno in comune? Scorgiamo quelli che
sono i fattori unificatori: ognuna delle frasi d’esempio determina una risposta (val-
ore) rispetto ad un insieme di risposte possibili (tipo) all’interno di un contesto ben
preciso (attributo). L’informazione puó essere dunque formalizzata come una tripla di
(attributo,tipo,valore). Rispetto ai nostri esempi:
T = {x1 , . . . , xn }
E = {e1 , . . . , ek }
C : T −→ E +
dove si dice li lunghezza della codifica di xi il numero dei simboli contenuti in C(xi )
ossia |C(xi )|. La funzione inversa a quella di codifica sui dice decodifica:
7
INFORMAZIONE
TIPO ATTRIBUTO
VALORE
DECODIFICA CODIFICA
DATO
D : E + −→ T
tale che ∀xi ∈ T, D(C(xi )) = xi . Una delle prime classificazioni che é possibile
fare in tale ambito é quella tra codifiche a lunghezza fissa e variabile. Una codifica
si dice di lunghezza fissa se e solo se ∀xi ∈ T, |C(xi )| = m, ossia se tutte le stringhe
codificanti i valori di T hanno la stessa lunghezza; in caso contrario si parla di codifica
a lunghezza variabile. Ecco alcuni esempi: supposti T = {a, b, c, d} e E = {α, β}, una
possibile codifica a lunghezza fissa é quella per cui:
• C(a) = αα,
• C(b) = αβ,
• C(c) = βα,
• C(d) = ββ
• C(a) = α,
• C(b) = βββ,
• C(c) = βα,
• C(d) = βαβ
Senza entrare troppo nel dettaglio di una trattazione formale, enucleiamo alcune
caratteristiche che distinguono le codifiche fisse e quelle variabili: quelle a lunghezza
fissa sono di per certo piú facili da interpretare in quanto sappiamo a priori la lunghezza
delle stringhe di dato e quindi riusciamo facilmente a dire quando finisce la codifica di
8
un valore dell’insieme di partenza e quando comincia il successivo; tale semplicitá fa sı́
che la codifica e decodifica a lunghezza fissa siano facilmente implementabili attraverso
circuiti elettronici. Una codifica a lunghezza variabile invece puó essere piú efficiente
se consideriamo che i simboli non sono equiprobabili, ad esempio se nel nostro caso
il valore a si presenta nella stragrande maggioranza dei casi, assegnando ad esso una
stringa di lunghezza minore (anche minore di quella che avremmo nella codifica di
lunghezza fissa), avremmo un risparmio nella lunghezza media delle stringhe di dato.
Introduciamo adesso l’alfabeto binario B = {0, 1} avente due simboli detti BIT (BI-
nary digiT); esso é l’alfabeto di simboli per la codifica piú usato nella programmazione
e nel mondo di calcolatori data la sua estrema semplicitá e versatilitá. I Bit sono il
fondamento dei calcolatori elettronici.
30 = 3 · 101 + 0 · 100
In generale quindi possiamo scrivere una famiglia di funzioni di decodifica (dal dato
al valore) relativamente ad un sistema di rappresentazione posizionale in base b.
Data una stringa xn−1 xn−2 . . . x1 x0 , x−1 x−2 . . . x−m+1 x−m , tale che:
∀i ∈ {−m, . . . , n − 1} 0 ≤ xi ≤ b − 1
9
il valore di tale numerale é dato da:
Pn−1
i=−m xi · bi
1001B = 1 · 23 + 0 · 22 + 0 · 21 + 1 · 20 = 9
oppure
INIZIO
assegna il valore di
naq
q/b
NO
il quoziente è nullo?
SI
FINE
10
si arresta, altrimenti si ripete l’operazione sul quoziente finché esso non si annulla.
Facciamo l’esempio del numero 123: in pratica basta scrivere il numero 123 su un
foglio e tracciare accanto ad esso una linea verticale verso il basso che ci aiuterá nel
calcolo. A questo punto dividiamo il numero 123 per 2 (ottenendo 61) e scriviamo il
resto (1) alla destra della riga. Il 61 ottenuto lo scriveremo invece sotto al numero
precedente (123) e ripeteremo l’operazione fino a che il numero alla sinistra della riga
non diventi 1. A questo punto basta leggere la serie di 1 e 0 ottenuta (dal basso verso
l’alto) per ottenere il corrispondente binario del nostro 123.
Un caso particolare é dato dalla conversione tra le basi 16 e 2. Infatti essendo 16
una potenza di 2 (secondo l’esponente 4) é possibile passare da binario a esadecimale
(e viceversa) in modo semplice. Data una stringa di n bit possiamo raggrupparli a
partire dalla cifra meno significativa a quella piú significativa in gruppi da 4 e far
corrispondere ad ogni gruppo una cifra esadecimale: in altre parole volendo convertire
il numero binario 101100101001 possiamo scrivere:
|{z } 0010
1011 | {z} 1001
|{z }
B 2 9
101 |{z}
|{z} 100 |{z}
101 |{z}
001
5 4 5 1
F |{z}
|{z} 8 |{z}
C |{z}
1
1111 1000 1100 0001
• + : K × K → K OR
• · : K × K → K AND
• · : K → K NOT
Tali funzioni sono definite cosı́ come illustrato nelle tabelle di veritá espresse:
Definiamo Algebra di Boole < K, +, ·, · >. In tale algebra si puó dimostrare che
valgono le seguenti proprietá:
∀x ∈ K
x+0=x
x·1=x
x+1=1
11
Table 1.1: Tabella di veritá della OR
A B A+B
0 0 0
0 1 1
1 0 1
1 1 1
x·0=0
x+x=x
x·x=x
x+x=1
x·x=0
x=x
∀x, y ∈ K, x + y = x · y e x · y = x + y.
1.5 Automi
Altro concetto cardine dell’informativa é quello di automa a stati finiti. Si definisce
automa a stati finiti la sestupla:
12
< Q, I, O, q0 , δ, ε > dove:
Q: insieme non nullo degli stati,
I: insieme dei simboli (alfabeto) in ingresso,
O: insieme dei simboli (alfabeto) in uscita,
q0 : stato iniziale dell’automa,
δ: funzione di transizione di stato tale che δ : Q × I → Q,
ε: funzione di uscita tale che ε : Q × I → O.
Esistono essenzialmente due tipi di automi a stati finiti: gli automi di Mealy e quelli
di Moore. La differenza sostanziale tra questi due tipi di automi risiede nel fatto che,
in sostanza, negli automi di Moore l’uscita é funzione del solo stato e non degli ingressi.
Formalmente:
AM oore < Q, I, O, q0 , δ, ε > dove:
Q: insieme non nullo degli stati,
I: insieme dei simboli (alfabeto) in ingresso,
O: insieme dei simboli (alfabeto) in uscita,
q0 : stato iniziale dell’automa,
δ: funzione di transizione di stato tale che δ : Q × I → Q,
ε: funzione di uscita tale che ε : Q → O.
13
Table 1.4: Versione tabellare della macchina di Mealy in Fig.1.4
0 1
S0 S0/0 S1/0
S1 S0/0 S2/0
S2 S3/0 S2/0
S3 S0/0 S0/1
14
1/0
0/0
S0 S1
0/0
0/0
1/0
1/1
1/0
S3 S2
0/0
1 -1,0,1
sono tre simboli convenzionali che stanno per muovi di uno step a destra, stai fermo e muovi
di una posizione a sinistra
15
1
0
S0/0 S1/0
0 1
1
0
S4/1 1
S2/0
0
1 S3/0
16
Figure 1.6: Schema di principio della macchina di Turing
MEMORIA
DATI
UNITA’ DI
PROGRAMMI UNITA’ DI
INGRESSO
USCITA
é relativo al flusso di controllo (linea nera tratteggiata) che indica invece che un
elemento controlla un altro, ossia determina cosa il secondo componente deve fare. Ci
sono cinque elementi nel modello: l’unitá di ingresso, responsabile dell’acquisizione dei
dati di ingresso dal mondo esterno, la memoria, responsabile della memorizzazione dei
dati in attesa di una loro elaborazione, l’unitá logico-aritmetica, in grado di svolgere le
operazioni logiche ed aritmetiche necessarie alla trasformazione dei dati memorizzati;
l’unitá di uscita, in grado di interfacciarsi con le periferiche di uscita del nostro calcola-
tore e l’unitá di controllo avente la responsabilitá di controllare l’intero procedimento
di elaborazione.
Una delle caratteristiche piú importanti del modello di Von Neumann é che sia i
programmi che i dati sono memorizzati in modo indifferenziato nella stessa memoria
e solo lo stato dell’elaborazione determina se si sta leggendo dalla memoria un dato o
un’istruzione del programma.
Per comprendere fino in fondo il modello di Von Neumann occorre introdurre il mod-
ello comportamentale del processore, anche detto ciclo del processore (vedi Fig.1.8).
All’accensione del calcolatore il processore avvia la fase di bootstrap (avvio). Quando
il processore si é avviato, ad ogni ciclo vengono svolte le seguenti attivitá:
17
BOOTSTRAP
Fetch
Decode
Operand Assembly
Execute
18
2 Il calcolatore
In questo capitolo analizzeremo i principali elementi dell’architettura di un calcola-
tore al fine di comprendere le motivazioni di diverse caratteristiche dei linguaggi di
programmazione.
Periferica 1 CPU
BUS
Periferica N Memoria
19
Analizziamo nel dettaglio alcuni di questi elementi.
2.1.1 La memoria
Possiamo considerare la memoria come una grande matrice di celle ognuna capace di
memorizzare un bit. Le celle sono organizzate in righe e colonne come raffigurato in
Fig.2.2: ad esse é possibile accedere in lettura o scrittura.
2^N
Vi sono dunque K celle di memoria ogni riga: questo numero viene detto parallelismo
della memoria e rappresenta il numero di bit presenti in ogni riga. Ci sono invece un
numero di righe pari a una potenza di due, nella fattispecie 2N . La minima quantitá
di memoria accessibile in memoria (sia in lettura che in scrittura) é la parola (ossia la
riga); per potervi accedere viene fornito quello che si definisce indirizzo di memoria.
L’indirizzo della parola i-esima non é altro dunque che la codifica binaria del numero
i-12 . Ad esempio supponendo di avere 8 parole di memoria, esse saranno indirizzate
dai seguenti: 000, 001, 010, 011, 100, 101, 110 e 111.
2.1.2 La CPU
La CPU (Central Processing Unit) rappresenta il centro delle attivitá del calcolatore
e presiede e a tutte le attivitá di controllo nonché a quelle di elaborazione e trasfor-
mazione dei dati. Rispetto al modello di Von Neumann (vedi Figura1.7) il processore
implementa le funzionalitá di unitá di controllo e di unitá logico-aritmetico.
2 Per convenzione la prima parola di memoria é 0, quindi l’indirizzo della i-esima riga corrisponde al
20
PC
MAR
IR
MDR
R1
ALU
RN
Gli elementi evidenti nel modello di Fig.2.3 (ad esclusione dell’ALU) sono registri.
Un registro é una piccola porzione di memoria all’interno del processore che il pro-
cessore stesso utilizza sia per operazioni di carattere generale (ossia per i dati) con i
registri R1 ...RN , sia per operazioni di tipo dedicato. Tra questi annoveriamo:
• MAR: Memory Address Register, contiene l’indirizzo di memoria del dato che si
vuole leggere/scrivere dalla memoria;
numero i-1
21
2.1.3 Il bus
Il bus collega dunque la memoria, il processore e le altre periferiche del calcolatore.
Esso é strutturato in tre parti distinte ognuna della quali specifica per alcuni tipi di
informazioni:
• dati : ivi viaggiano i dati che vengono scambiati tra gli elementi collegati al bus;
• indirizzi : vi viaggiano gli indirizzi di memoria che il processore richiede (lettura
e/o scrittura);
• controllo: linee speciali che servono per comunicare i tipi di operazioni richieste
agli elementi dal processore (ad esempio se un indirizzo di memoria deve essere
letto o scritto dalla memoria).
Memoria
CPU
MDR MAR
(6) (5)
(7) (4)
(8) (3)
(1)
MDR MAR
(10)
(9) (2)
BUS CONTROLLO
BUS INDIRIZZI
BUS DATI
Facendo riferimento alla Fig.2.4, illustriamo il processo di lettura di una dato dalla
memoria. Innanzitutto quando il processore stabilisce di voler leggere un dato dalla
memoria, scrive nel suo registro MAR l’indirizzo (ad N bit) relativo a quale parola si
vuole leggere (1), tale valore viene trasferito sul bus all’analogo registro presente nei
22
circuiti di memoria (2). Successivamente il processore dice (sul bus dei controlli) di
voler leggere il dato (3) e tale comando arriva alla memoria (4). A questo punto i
circuiti della memoria leggono il MAR (5) ed estraggono tra le parole quella relativa
all’indirizzo selezionato copiando il contenuto di tale parola (k bits) nel registro MDR
(6) ed attivano un segnale di risposta verso il processore appena l’operazione si é
conclusa (7), comando che, arrivato al processore (8), permette il trasferimento del
dato dalla memoria al processore sul bus dei dati (9) e (10).
Scrittura in memoria
Memoria
CPU MAR
MDR (8) (7)
(9) (6)
MDR MAR
(10) (5)
(3) (1)
(4) (2)
BUS CONTROLLO
BUS INDIRIZZI
BUS DATI
Un processo del tutto analogo avviene per la scrittura di un dato in memoria (vedi
Fig.2.5). In questo contesto, il processore stabilisce quale dato deve essere scritto
e dove; innanzitutto dunque scrive nel suo registro MAR l’indirizzo relativo a quale
parola si vuole scrivere (1), tale valore viene trasferito sul bus all’analogo registro
presente nei circuiti di memoria (2). Ugualmente si procede per il dato da scrivere (3)
e (4). Successivamente il processore dice (sul bus dei controlli) di voler scrivere il dato
(5) e tale comando arriva alla memoria (6). A questo punto i circuiti della memoria
leggono il MAR (7) ed il MDR (8) e scrivono il contenuto di quest’ultimo nella parola
individuata dal primo. Alla fine di questa operazione, la memoria attiva un comando
di risposta verso il processore (9) e (10).
23
2.2 Memoria cache
Le prestazioni dei calcolatori basati sul modello di Von Neumann sono fortemente
influenzate dagli accessi alla memoria; infatti, dato che ad ogni ciclo ci possono essere
diversi accessi alla memoria (sia in lettura con le istruzioni ed i dati, sia in scrittura con i
dati), occorre velocizzare tale attivitá al fine di migliorare globalmente le prestazioni del
calcolatore. Migliorare le prestazioni di una memoria non é peró semplicissimo: infatti
le memorie sono caratterizzate da costi, velocitá e capacitá. Ebbene non possiamo
pensare di avere contemporaneamente memoria poco costose, molto veloci e molto
capienti. In genere se scegliamo due di queste caratteristiche (ad esempio grandi
velocitá e bassi costi), dobbiamo accontentarci di memorie poco capienti.
Occorre enucleare adesso due regole empiriche nate dall’osservazione statistica dei
programmi di maggior uso:
Diciamo memoria cache quella memoria che viene frapposta tra il processore e la
memoria principale e che conserva una copia dei dati. La caratteristica principale
della memoria cache é una maggiore velocitá (altrimenti sarebbe inutile averla!). Lo
schema architetturale del calcolatore con la presenza della cache diventa dunque quella
descritto in Fig.2.6.
CPU
Periferica 1 CACHE
BUS
Periferica N Memoria
Vediamo che la cache viene posta tra il processore ed il bus. Il suo funzionamento é
il seguente: inizialmente la cache é vuota e quindi quando il processore chiede il dato
24
alla cache, quest’ultima lo richiede alla memoria. A questo punto il trasferimento di
dati dalla memoria alla cache non interessa solo la parola che il processore ha richiesto
ma un blocco di dati relativo a quella locazione di memoria ed a quelle adiacenti.
Appena il dato viene letto, la cache non viene svuotata ma il blocco trasferito dalla
memoria resta nella cache. Quando invece il processore richiede un dato alla cache ed
esso si trova giá in cache, allora la cache non passa per la memoria. In questo caso si
dice che si é avuto un cache hit: diciamo PHIT la probabilitá di avere un tale evento.
Questi due accorgimenti permettono un notevole miglioramento delle prestazioni:
• innanzitutto il fatto che si trasferisce un blocco di dati dalla memoria alla cache
invece della singola parola permette di sfruttare a pieno il principio della localitá
spaziale in quanto se un dato adiacente viene chiesto c’é un’alta probabilitá che
esso si trovi giá in cache;
• in secondo luogo, il fatto che si conserva il dato in cache anche dopo che lo si é
usato, permette di sfruttare il principio della localitá temporale in quanto se il
dato in questione viene richiesto giá si trova in cache.
Quando viene rimosso un blocco dalla cache? Quando ne viene chiesto un altro non
presente e non c’é piú spazio libero in cache: in questo caso si prende uno dei blocchi
“vecchi” e lo si elimina dalla cache (scrivendo in memoria quanto era stato scritto
precedentemente in cache).
Quali sono dunque le prestazione di un sistema in presenza della memoria cache?
Supponendo TM il tempo di accesso di un dato dalla memoria principale e TC il tempo
di accesso alla memoria cache abbiamo che ogni accesso in assenza di cache é dunque
TM , mentre in presenza di cache abbiamo:
TT OT = PHIT · TC + (1 − PHIT ) · TM
Considerando che PHIT ' 0.8 e che TM ' 50 · TC possiamo capire quanto buono é
il guadagno che si ottiene in presenza di cache.
Le limitazioni della cache sono legate soprattutto alle sue dimensioni. Infatti essendo
una memoria molto veloce, é necessario, al fine di contenere i costi, di limitarne le
dimensioni; infatti se le cache fossero veloci e grandi il loro costo renderebbe impossibile
(dal punto di vista economico) la loro esistenza.
25
3 La programmazione dei calcolatori
3.1 Linguaggio macchina
Nel capitolo precedente abbiamo illustrato quelli che sono gli elementi fondamentali
dell’architettura di un calcolatore elettronico. Il grande potere di un calcolatore é che
il limite delle cose che puó fare é dettato esclusivamente da come é stato programmato,
dai programmi che su di esso vengono eseguiti. Qui evidenzieremo gli aspetti legati
alla programmazione dei calcolatori parlando innanzitutto dei linguaggi in cui i tali
programmi sono espressi.
Cosa esprime una istruzione all’interno del calcolatore? Abbiamo visto nel modello
di Von Neumann diverse tipologie di controllo che l’unitá di controllo stessa effettua
sulle diverse unitá del processore. Questa diversitá riflettono un’analoga diversitá tra
le possibili categorie di istruzioni:
A queste si aggiunge una categoria legata alla gestione del flusso di controllo stesso
(istruzioni effettuate su altre istruzioni ossia legate all’individuazione della prossima
istruzione all’interno di un programma).
Tipicamente la forma di un’istruzione é la seguente:
26
dati. Detto questo risulta naturale pensare che anche per quanto riguarda le istruzioni
esista una codifica verso il codice binario: tale codice viene detto linguaggio macchina.
Questo é infatti l’unico metodo attraverso cui il calcolatore puó interpretare ed eseguire
le istruzioni.
Il linguaggio macchina presenta peró due grandissime limitazioni che ne rendono
l’uso (dal punto di vista della programmazione) pressocché limitato a pochi casi spo-
radici. Il primo di questi problemi é quello della complessitá: essendo il linguaggio
macchina di un livello di astrazione estremamente basso, specificare anche il piú sem-
plice programma richiede diverse righe di codice. Pertanto scrivere programmi comp-
lessi risulta pressocché impossibile. La seconda limitazione riguarda la portabilitá ossia
la possibilitá di scrivere un programma una sola volta e di eseguirlo su quanti piú tipi
di macchine possibile. Il linguaggio macchina é, per sua stessa definizione, dipendente
fortemente dalla macchina e pertanto un programma scritto in tale linguaggio non puó
essere eseguito (portato) su un computer di tipo diverso (che monti cioé un processore
diverso).
Per ovviare parzialmente a tali inconvenienti si sono diffusi velocemente i linguaggi
assemblativi (assembler) che sostituiscono ai codici binari condici simbolici: ad esempio
l’istruzione di esempio mentre se scritta in linguaggio macchina apparirebbe come:
000101 110100010 1101001
in assembler potrebbe apparire come:
M OV E A R0
ossia sposta il dato dalla locazione A al registro R0. Un programma scritto in
assembler deve poi esser trasformato in linguaggio macchina attraverso un apposito
programma (l’assemblatore). L’assembler per quanto migliori la leggibilitá di un pro-
gramma non risolve totalmente i problemi di portabilitá e complessitá primi descritti.
27
in quanto un calcolatore, sulla base di quanto giá detto alla Sez.3.1, comprende esclu-
sivamente il linguaggio macchina. Sono dunque necessari degli strumenti in grado di
colmare il divario tra i linguaggi 3GL ed il linguaggio macchina.
Generalmente si tende a suddividere i linguaggi ad alto livello in 3 categorie2 :
• imperativi: sono composti da una sequenza di istruzioni in grado di modificare il
contenuto della memoria del computer o di determinare le modalitá di esecuzione
di altre istruzioni. Tipici linguaggi imperativi sono: Pascal, Basic, Fortran, C,
...;
28
La compilazione inoltre traduce il programma nella sua interezza permettendo quindi
di ottimizzare il codice oggetto ossia di renderlo (a paritá di funzionalitá svolte) piú
veloce oppure occupante meno spazio. Durante l’esecuzione inoltre si ha giá a dispo-
sizione tutte le istruzioni che verranno eseguite facendo sı́ dunque che il codice prodotto
sia molto piú veloce dell’analogo interpretato. D’altro canto l’interpretazione permette
uno sviluppo piú interattivo del programma in quanto le istruzioni vengono tradotte ed
eseguite una alla volta. Gli interpreti, inoltre, mancando di tutta una serie di controlli
che il compilatore fa e mancando del tutto della fase di ottimizzazione del codice, hanno
una struttura piú semplice (a paritá del linguaggio) rispetto agli analoghi compilatori.
Una cosa che accomuna interpreti e compilatori é la dipendenza dall’architettura.
Abbiamo visto nella sezione precedente che il linguaggio macchina é caratteristico di
un’architettura e quindi i programmi scritti in linguaggio macchina (e possiamo dire
anche assembler) funzionano solo sull’architettura per la quale sono stati sviluppati
risultati dunque scarsamente portabili. I linguaggi di alto livello invece si astraggono
dall’architettura considerata: infatti quando si scrive un programma in C, ad esempio,
e lo si compila su una certa macchina, si produce un codice che funzionerá su tutte
le macchine di quel tipo. Per poter portare il codice su un’altra architettura, sará
necessario cambiare compilatore scegliendone uno che riesca a tradurre il linguaggio
C nel linguaggio macchina della nuova architettura mantenendo inalterato il codice
sorgente. Analoghe considerazioni valgono per gli interpreti.
Occorre peró precisare che é possibile l’esistenza, per un qualsiasi linguaggio di
programmazione, sia di compilatori che di interpreti. Non solo, esistono modalitá
ibride che prevedono l’uso combinato (unendo i vantaggi di entrambi) sia della fase
compilativa che di quella interpretativa. In questi casi la compilazione avviene sempre
prima dell’interpretazione ed ha come linguaggio target non il linguaggio macchina ma
una sorta di linguaggio intermedio (come avviene ad esempio nel bytecode di Java).
29
4 Sistemi operativi
I calcolatori elettronici, nella loro complessitá, vengono schematizzati, studiati e pro-
gettati secondo modelli che riescono a dominare quanto piú possibile tale complessitá.
Uno dei modelli piú utilizzati in tale ambito descrive il calcolatore attraverso l’astrazione
dei livelli. In tale modello, raffigurato in Fig 4.1, possiamo vedere diversi livelli ognuno
dei quali si poggia su altri. Verso il livello superiore, ogni livello fornisce dei servizi
mentre allo stesso momento richiede servizi al livello inferiore.
Sw Applicativo
Servizi
Esportati
Sw di base
Servizi
richiesti
Hardware
1. hardware: il livello piú basso, é quello della macchina fisica cosı́ come visto nel
Cap. 2: es. server dual-core piuttosto che netbook di fascia bassa;
2. sw di base: su tale livello si poggia quello hardware ed ha lo scopo di fornire
tutta quella serie di programmi in grado di far funzionare la macchina hardware
e di fornire inoltre un’interfaccia unica che nasconda i dettagli della macchina
stessa: es. Windows, Linux, Mac;
3. sw applicativo: software in grado di fornire funzionalitá ben specifiche e creati
per scopi particolari: es. word processor, multimedia player, browsers. Gli
applicativi sono realizzati attraverso la stesura di un apposito programma scritto
con le tecniche ed i linguaggi evidenziati in Cap. 3.
30
4.1 Funzionalitá
Un sistema operativo é un sistema complesso in sé dato il suo duplice compito di gestire
da una parte hardware sempre piú complessi e dall’altra di fornire funzionalitá sempre
piú sofisticate ed interfacce utente intuitive e gradevoli. A tal fine si preferisce iniziare
a descrivere i sistemi operativi attraverso un ulteriore modello a livelli (raffigurato in
termini concentrici) che prende anche il nome di schema a cipolla (vedi Fig. 4.2).
Shell ed Interpreti
File System
Gestione periferiche
Gestione Memoria
Kernel
Hardware
Rispetto al modello a livelli come visto in Fig. 4.1, si deve considerare qui lo strato
piú interno come quello piú basso. Partendo dall’interno vediamo che il sistema oper-
ativo si appoggia sull’hardware (e sui servizi direttamente offerti da questo). Il primo
livello del sistema operativo vero e proprio é il nucleo (kernel) che offre funzionalitá
di gestione dei processi e di gestione della risorsa piú importante della macchina, os-
sia il processore. Successivamente troviamo quelle routine che si occupano di gestire
la memoria ossia di stabilire quanta memoria ed a chi viene stata allocata (il ge-
store della memoria). Successivamente incontriamo il livello di gestione delle
periferiche che si occupa di interfacciarsi con le periferiche del calcolatore al fine di
avere delle interfacce e dei modi di comunicazione con tali elementi unici. Ancora piú
su troviamo il livello del file system, ossia di gestione dei file e delle informazioni
persistenti del sistema. All’ultimo livello troviamo l’interprete dei comandi, os-
sia l’insieme di software deputato all’interfacciamento verso l’utente e la gestione dei
comandi da questi ricevuti.
31
4.1.1 Il kernel
Alla base della gestione di un sistema operativo c’é il concetto di processo. Abbiamo
visto nei Capitoli 1 e 3 che i programmi sono costituiti dal codice oggetto generato
dalla compilazione del codice sorgente e che rappresentano quindi concetti statici. Di-
versamente, il processo é l’entitá utilizzata dal sistema operativo per rappresentare
una specifica esecuzione di un programma (concetto dunque dinamico): oltre al codice
sorgente (il programma), un processo contiene anche i dati relativi alla specifica es-
ecuzione nonché tutte le informazioni che ne definiscono lo stato, come il contenuto
della memoria indirizzata, i file e le periferiche in uso, etc...
In un sistema operativo moderno, ci sono piú processi attivi contemporaneamente.
Uno dei compiti piú importanti di tali sistemi é appunto quello di gestire opportuna-
mente tutti questi processi rendendoli attivi uno per volta. I primi sistemi operativi
erano monoprocesso ossia riuscivano a gestire solo un processo alla volta: i vari processi
procedevano in batch (lotti) ossia l’i-esimo non poteva iniziare prima che l’(i-1)-esimo
avesse finito (vedi Fig. 4.3): questo prende il nome di schedulazione FIFO.
processo A
processo B
processo C
tempo
Questo modo di schedulare, ossia di pianificare nel tempo l’esecuzione di tali processi,
ha come inconveniente il permettere che alcuni processi monopolizzino il processore
per troppo tempo. Infatti come si evince dalla figura il processo C, che é il piú piccolo,
deve aspettare che tutti gli altri finiscano. Obiettivo diverso ha invece la seconda
modalitá di schedulazione (Round Robin) che permette, attraverso la suddivisione del
tempo in quanti (time slice) di eseguire un pó di processo per volta, facendo insomma
“un pó di processore per uno” tra i processi permettendo dunque a quelli piú veloci
di non dover aspettare troppo tempo prima di poter essere eseguito (vedi Fig. 4.4).
Questa si chiama schedulazione Round-Robin ed ha come vantaggio tangibile quello
32
di una diminuzione media della durata del tempo di esecuzione dei processi.
processo A
processo B
processo C
tempo
Un processo vive diverse fasi: richiede dati alle periferiche e quindi “perde tempo” in
attesa di dati che vengono dall’esterno. In un sistema monoprocesso in questa perdita
di tempo viene subita anche dal processore in quanto, in questo tempo, il processo
continua a detenere la CPU.
Facendo dunque riferimento alla Fig. 4.5, quando viene attivato un processo si trova
in modalitá READY dove viene inserito in una coda di processi pronti per essere
eseguiti e che aspettano solo il processore. Quando il processore non ha nulla da fare,
viene scelto un processo dalla coda (in genere quello che ci sta da piú tempo) e lo si
fa diventare RU N N IN G ossia viene allocato a lui il processore e gli viene permesso
di essere eseguito. Quando il tempo a disposizione del processo finisce (si esaurisce il
quanto di tempo precedentemente descritto) l’esecuzione del processo viene interrotta
ed il processo viene riaccodato ai READY . Quando invece, dallo stato di RU N N IN G
un processo fa una richiesta di I/O (ad esempio richiede dei dati ad una periferica)
esso viene messo della coda dei W AIT , dalla quale si esce solo in caso di avvenuto
trasferimento dati di I/O. A questo punto il processo viene rimesso nella coda dei
READY .
33
Primo in
coda
RUNNING Nascita
READY
Tempo scaduto
Fine
Evento esterno
Richiesta
periferica
WAIT
1 Possiamo dire che mentre le directory sono i nodi non terminali dell’albero del file system, i file ne
sono i nodi terminali.
34
DIR
DIR FIle
FIle DIR
D1
D2 F4
F1 D3
D4 F2 F3
35