| |
Macchine Intermedie e Struttura a livelli dei computer moderni
La differenza di potenza espressiva fra una macchina astratta che vogliamo realizzare
e la macchina che abbiamo a disposizione per tale realizzazione (Macchina Ospite
o HOST) è detta semantic gap.
Spesso il semantic gap tra la macchina da realizzare e
la macchina HOST è talmente grande che è opportuno introdurre
una o piu' macchine astratte intermedie.
Tipicamente, nell'implementare un linguaggio di programmazione (cioe' la sua
corrispondente macchina astratta)
non si procede mai per pura compilazione o pura interpretazione
su una data macchina HOST.
La pura interpretazione potrebbe non essere soddisfacente a causa della scarsa
efficienza della macchina realizzata emulando via software strutture dati, algoritmi e soprattutto l'interprete.
Anche una realizzazione puramente compliativa a causa di un notevole semantic gap
potrebbe portare a produrre programmi per la macchina ospite di dimensioni
eccessive e magari lenti, senza considerare le difficolta' che si possono incontrare
per sviluppare dei traduttori tra linguaggi eccessivamente diversi.
Uno schema implementativo molto usato prevede fasi sia compilative che interpretative;
la situazione, relativamente ad una realizzazione compilativa sopra
una interpretativa è rappresentata nella figura seguente:
Fig.1: Implementazione di L
mediante traduzione nel linguaggio di una macchina intermedia MI
e simulazione di MIsulla macchina HOST.
 |
Per colmare il divario fra la macchina ML e la macchina HOST, si progetta
un'apposita macchina intermedia MI che viene realizzata sulla macchina HOST.
A questo punto la traduzione dei programmi in L avverrà in termini
del linguaggio di MI, e successivamente il programma per MI
cosí ottenuto verrà eseguito tramite interpretazione sulla macchina HOST.
Nel progetto della macchina intermedia bisogna tenere conto di due
requisiti di efficenza:
- velocità di simulazione della Macchina Intermedia MI
sulla macchina HOST;
- compattezza del codice prodotto nel linguaggio della macchina MI.
Per soddisfare tali requisiti, tipicamente MI è solo un'estensione
della macchina HOST, nel senso che ne condivide l'interprete, e ne potenzia alcuni
aspetti mediante un insieme di routine note come Run-Time System, atto a colmare, negli
aspetti, la differenza delle due macchine.
Tornando alla Fig. 1, notiamo che la pura interpretazione e la
pura compilazione si hanno nei casi estremi caratterizzati rispettivamente
dal fatto che la MI coincida con la macchina ML
o la macchina HOST.
Un esempio concreto di applicazione di questo schema si ha per il linguaggio di programmazione
Java. In questo caso, la macchina intermedia MI è la JVM
(Java Virtual Machine1), il cui linguaggio è detto Bytecode, mentre il
Run-Time System viene detto Java RunTime Enviroment. Per quanto riguarda la macchina HOST
,
nella pratica si hanno diverse realizzazione: dal PentiumII all'UltraSPARC, al PicoJavaII.
Non necessariamente la realizzazione di ML sulla macchina intermedia
deve essere compilativa. Ci possono essere svariati motivi per avere
sia ML che la macchina intermedia realizzati entrambi per interpretazione.
Nello sviluppo dei nostri sistemi di calcolo in genere il procedimento descritto
viene generalizzato, portando ad insieme di livelli di macchine astratte che si
frappongono fra la macchina realizzata in hardware e la macchina astratta del
"livello" utente, che potrebbe essere quella corrispondente ad un HLL
o ad un particolare applicativo (per esempio il programma "Office" o il sistema di
gestione utilizzato dalla vostra banca altro non sono che particolari
macchine astratte).
Gli scopi di tale stratificazione sono molteplici:
gestire la complessita' di progettazione, aumentare la flessibilita' del sistema ecc.
Fig.1: Gerarchia di macchine astratte
 |
Il livello più basso rappresenta il computer reale e il linguaggio macchina
che esso è in grado di eseguire direttamente. Ciascuno dei livelli
superiori rappresenta una macchina astratta, i cui programmi devono essere o
tradotti in termini di istruzioni di uno dei livelli inferiori (non
necessariamente del livello immediatamente al di sotto), o interpretati da
un programma che gira su di una macchina astratta di livello strettamente inferiore.
È da notare che nella pratica non è raro che un livello realizzi una
macchina astratta che non copre del tutto il livello sottostante ma ne potenzia alcune
delle caratteristiche, lasciandone trasparire del tutto altre.
E possibile cioe' che alune delle componenti della macchina siano esattamente
quelle della macchina ospite, mentre altre siano completamente diverse o magari
delle semplici estensioni. Nella seguente figura e' shematizzata la possibilita'
di una macchina, per esempio hardware, sulla quale e' realizzata un'altra macchina attraverso
emulazione via firmware solo di alcune sue componenti. Su quest'ultima e' poi
realizzata una ulteriore macchina che ha alcune componenti realizzate in software,
mentre altre sono esattamente le stesse della macchina sottostante o di
quella del livello ancora inferiore.
Fig.2: Il livello L0 non è
del tutto coperto dai livelli superiori
 |
Un semplice e chiaro esempio di livello che non copre i livelli
sottostanti lo potrete trovare
quando affronterete il paragrafo 5.5.10 del Tanenbaum: The picoJava II Instructions.
Vedrete come l'hardware dell'architettura picoJava II non realizza completamente
le istruzioni del linguaggio JVM. Alcune di esse sono infatti ralizzate con
microprogrammi, mentre altre, attraverso alune trap ad un software handler, sono
realizzate in software. Quindi e' come se esistesse una macchina astratta
JVM0 realizzata in hardware, sulla quale e' realizzata una
macchina JVM1 e su quest'ultima la JVM completa.
Ovviamente in tale semplice caso le parti firmware di JVM1
e software si JVM non coprono alcuna parte di JVM0, semplicemente
la estendono.
Un tipico computer moderno si può quindi pensare come una serie
di macchine astratte realizzate una sopra l'altra, ciascuna in grado di
fornire funzionalità via via più potenti.
Per chiarire le idee sulla complessità di questa decomposizione in
livelli, esaminiamo una possibile stratificazione dei comuni PC.
È da notare tuttavia che non è in alcun modo necessario che un sistema
di calcolo abbia un numero prefissato di livelli di astrazione: quella che segue
è solo una possibile soluzione, che mostra come ogni livello colmi la sua
parte di semantic gap.
Fig.3: Un esempio di gerarchia di macchine
astratte (di lato, il modo in cui sono realizzate)
 |
Nello schema non sono rappresentati i livelli sottostanti
la logica digitale.
Le componenti fondamentali del livello 0 sono le
cosiddette porte logiche (GATES):
a partire da raggruppamenti di esse si formano i registri e le memorie.
Le GATES sono dispositivi elettronici che, pur appartenendo
al mondo analogico, costituiscono il ponte verso il mondo digitale, poichè esse tipicamente
realizzano funzionalità (Not, And, Or, ...) che sono ben
formalizzabili in termini di una teoria discreta nota come Algebra di Boole.
Collezioni di registri e circuiti digitali che realizzano funzionalità
aritmetico-logiche sono alla base del livello 1. Qui troviamo altri elementi
di interesse come il datapath e le strutture che presiedono al suo
controllo. A questo livello inizia a diventare chiara l'idea di flusso di
informazione poichè sequenze di bit viaggiano da una componente all'altra
(della macchina astratta di questo livello) venendo eventualmente elaborate.
Il linguaggio del livello 2 è quello cui tipicamente ci si riferisce quando
si parla di linguaggio macchina di un certo computer. In termini tecnici,
questo livello è noto come ISA Level (Istruction Set Architecture Level);
tipiche istruzioni di questo livello sono ADD, MOVE, SUB, ...
Questo livello è di solito direttamente implementato in hardware
nelle architetture RISC, mentre e'
tipicamente è realizzato mediante interpretazione firmware nelle architetture
CISC.
Il livello 3 evidenzia quella caratteristica precedentemente notata di non
coprire del tutto i livelli sottostanti: si tratta del livello del Sistema
Operativo (SO). In esso la maggior parte delle istruzioni dell'ISA level sono
disponibili tali e quali; sono inoltre presenti nuove operazioni, che
consentono un livello di astrazione notevolmente più alto nell'ambito della
gestione della memoria e del flusso di controllo.
Tre delle principali funzionalità offerte dal Sistema Operativo sono:
- il concetto di FILE: a questo livello si realizza il salto
qualitativo verso una memoria
molto strutturata e organizzata gerarchicamente (File System) in cui
informazioni complesse
possono essere immagazzinate e recuperate con grande facilità
- la MEMORIA VIRTUALE: il Sistema Operativo offre ai livelli di astrazione
superiore una macchina astratta con una quantità di memoria principale di
gran lunga superiore a quella effettiva, liberando così il programmatore
dai limiti fisici derivanti dalla particolare dotazione della macchina;
- l'astrazione di PROCESSO e il MULTITASKING: la possibilità di avere più
"pezzi" di programma (processi) in esecuzione indipendente e
(pseudo-)parallela è una delle caratteristiche che si è andata affermando
negli anni; è tale meccanismo che rende possibili funzionalità quali la
stampa in background o la garbage collection, etc.
Questo in linea di principio significa che il livello del Sistema Operativo in realta'
puo' mettere a disposizione non una singola, ma molteplici macchine astratte.
La programmazione dei livelli introdotti fino a questo momento è
tipicamente compito di una cerchia ristretta di programmatori (i cosiddetti
sistemisti), poichè si tratta di macchine astratte che sono state ideate non perchè
interessanti in sè, ma in quanto necessarie per riuscire a gestire la
complessità dell'implementazione delle macchine astratte di livello superiore.
Il livello 4 segna quindi una rottura con i livelli precedenti: si tratta
del livello dell'assembly, il primo che presenti qualche caratteristica
(sebbene elementare) dei linguaggi di programmazione moderni (uso di
etichette, mnemonici per le istruzioni, presenza di macro, variabili
globali, procedure, etc...). Tipicamente il livello 4 è realizzato sulle
macchine sottostanti mediante il meccanismo di traduzione realizzato da un
pezzo software (tipicamente scritto in ISA level language) noto come
ASSEMBLY language.
tale livello si puo' definire come il piu' basso utilizzabile dal programmatore
di applicazioni eseguite dal sistema di calcolo.
Ad ogni modo è al livello 5 che generalmente entrano in gioco gli HLL: C, Java, C++,
Haskell, Prolog, etc... Si tratta del livello più vario, dove troviamo
tutte le possibili soluzioni implementative, anche per macchine astratte relative allo
stesso linguaggio: ad esempio, esistono varianti sia interpretate che
compilate del BASIC.
Come accennato in precedenza l'avvento di Java ha portato alla nascita di un livello
intermedio tra quelli del linguaggio ad alto livello e quello dell'assembly. Infatti
prima di
essere eseguiti, i programmi Java vengono tradotti in bytecode, ovvero nel
linguaggio macchina della Java Virtual Machine (JVM), che è
realizzata mediante interpretazione sui livelli sottostanti; successivamente saranno i
bytecode così ottenuti che verranno eseguiti sulla JVM.
I vantaggi di questa tecnica hanno recentemente portato alla implementazione
di molti linguaggi di programmazione (anche non Object Oriented) sulla JVM,
piuttosto che sui livelli 2-3-4: sostanzialmente la JVM si sta affermando
come macchina astratta standard all'interno della stratificazione tipica dei moderni
sistemi di calcolo.
Teniamo a precisare ancora una volta che i livelli appena descritti sono una
tra le infinite possibilita' teoriche di strutturazione a livelli di un sistema di calcolo.
Programmazione delle Macchine Astratte
Riguardo la programmazione
e' talvolta possibile imbattersi (sempre piu' raramentente, per fortuna)
in affermazioni del seguente tipo:
Una programmazione consapevole ed adeguata per una particolare realizzazione
di una macchina astratta e' possibile solo se l'utente conosce non solo la
struttura della macchina astratta, ma anche quali componenti sono stati realizzati
direttamente in hardware, quali emulati, quali interpretati. Infatti, scegliere
un modo piuttosto che un altro di realizzare un algoritmo puo' risolversi in
un guadagno di prestazioni, dovuto alla maggiore efficienza della realizzazione
di alcune componenti della macchina astratta rispetto ad altri.
Gia' il fatto che questa sia una affermazione eccessivamente generica (che non fa cioe'
riferimento a particolari contesti in cui poter essere utilizzata) dovrebbe
insospettirci. L'Informatica e' la metafora della vita e nella vita non si puo'
fare alcuna affermazione che sia valida a prescindere da specifici contesti.
Ergo, e' possibile che in particolari contesti l'affermazione citata possa
esser vera (pensiamo per esempio al caso di programmare una macchina astratta
corrispondente al sistema di controllo della temperatura del nocciolo di una
centrale atomica; ovviamente bisogna sfruttare al massimo tutte le possibilita'
di velocizzare la routine che, identificato un insolito aumento di temperatura, metta
in moto tutte le procedure di sicurezza). In generale pero' sappiamo che non esiste
solo la velocita' di esecuzione di un programma come unico parametro di valutazione;
per esempio c'e' anche la trasportabilita' di un programma. Un programma che abbia
buone prestazioni solo perche' fa affidamento si certe caratteristiche della
realizzazione della macchina astratta su cui gira, potrebbe avere prestazioni
scadentissime su altre realizzazioni della stessa macchina. Questa considerazione
e' ancora piu' importante se si considera' come sia sempre piu' rilevante oggi
il concetto di codice mobile.
In generale quindi dobbiamo dire che nel programmare una macchina astratta
dobbiamo far riferimento solo alla sua specifica, non alla sua
realizzazione.
1.
E' bene tener presente la possibile ambiguita' che potrebbe generare questo nome.
La Java Virtual Machine infatti non e' la macchina astratta corrispondente al linguaggio
JAVA, bensi' la macchina intermedia per JAVA.
|