Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali...

24
Sviluppo di un ambiente per la scrittura e la simulazione di programmi per il processore PD32 Matteo Leonetti Introduzione Il PD32 è un processore didattico studiato nei corsi di Calcolatori Elettronici in diverse università italiane. La sua struttura e quella dell'assemblatore originale sono descritte in [1]. Il simulatore a corredo di tale libro è stato realizzato nel 1994, e gli undici anni che ha alle spalle iniziano a rendere evidente la necessità di un nuovo ambiente di sviluppo con le caratteristiche di un software moderno. Questo progetto ha l'obiettivo di creare un'alternativa portabile, che costituisca le fondamenta di una suite di applicazioni per la didattica relative al processore ed agli insegnamenti di Calcolatori Elettronici. Si prefigge, inoltre, di migliorare la gestione delle periferiche permettendo la simulazione di periferiche complesse. 1. Architettura DISsimulator 1 è scritto in Java ed è basato sulla versione 1.4 del JSDK. Questo ne garantisce la portabilità su tutti i sistemi per cui sia disponibile una Java Virtual Machine. Per permettere l'estensione del software con moduli esterni, è necessario utilizzare un framework che 1 Il nome è ispirato ad una frase del “ritratto di Catilina” di Sallustio: “Omnium rerum simulator ac dissimulator”. Inizialmente questo progetto mirava a realizzare una macchina universale di cui il PD32 fosse una specializzazione. Dunque un “simulatore di ogni cosa”. Oltre a ciò, la parola dissimulator inizia con l'acronimo del Dipartimento d'Informatica e Sistemistica e sembrava dunque particolarmente adatta.

Transcript of Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali...

Page 1: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Sviluppo di un ambiente per la scrittura e lasimulazione di programmi per il processore PD32

Matteo Leonetti

Introduzione

Il PD32 è un processore didattico studiato nei corsi di Calcolatori Elettronici in diverse

università italiane. La sua struttura e quella dell'assemblatore originale sono descritte in [1]. Il

simulatore a corredo di tale libro è stato realizzato nel 1994, e gli undici anni che ha alle spalle

iniziano a rendere evidente la necessità di un nuovo ambiente di sviluppo con le caratteristiche di un

software moderno.

Questo progetto ha l'obiettivo di creare un'alternativa portabile, che costituisca le

fondamenta di una suite di applicazioni per la didattica relative al processore ed agli insegnamenti di

Calcolatori Elettronici. Si prefigge, inoltre, di migliorare la gestione delle periferiche permettendo la

simulazione di periferiche complesse.

1. Architettura

DISsimulator1 è scritto in Java ed è basato sulla versione 1.4 del JSDK. Questo ne

garantisce la portabilità su tutti i sistemi per cui sia disponibile una Java Virtual Machine.

Per permettere l'estensione del software con moduli esterni, è necessario utilizzare un framework che

1 Il nome è ispirato ad una frase del “ritratto di Catilina” di Sallustio: “Omnium rerum simulator ac dissimulator”.Inizialmente questo progetto mirava a realizzare una macchina universale di cui il PD32 fosse una specializzazione.Dunque un “simulatore di ogni cosa”. Oltre a ciò, la parola dissimulator inizia con l'acronimo del Dipartimentod'Informatica e Sistemistica e sembrava dunque particolarmente adatta.

Page 2: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

metta a disposizione una serie di servizi.

Tenendo sempre presente l'aforisma di Eric Raymond:

“Good programmers know what to write. Great ones know what to rewrite (and reuse)” [2]

ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse

di IBM®. Entrambi sono software completi e molto complessi. Ho optato per il primo per la possibilità

di implementare l'interfaccia grafica con la libreria Swing piuttosto che con la SWT di IBM®, che non

fa parte delle librerie standard di Java. Sebbene possa essere utilizzata per creare applicazioni di

qualunque genere, la NetBeans Platform è la base del noto NetBeans IDE ed è quindi particolarmente

adatta per la realizzazione di ambienti di sviluppo. La piattaforma di NetBeans mette a disposizione

dei moduli la gestione delle impostazioni, delle finestre, delle eccezioni, e un editor con ampie

possibilità di estensione. Un modulo per NetBeans è un file jar, il cui manifest contiene alcune

informazione lette dalla piattaforma ed utilizzate per il caricamento delle classi. I moduli possono

dichiarare delle dipendenze da altri che vengono aggiunti al loro classpath.

DISsimulator è costituito da due moduli: il core e nbplugin. Il primo realizza tutte le

funzionalità relative al PD32 e non dipende in nessun modo da NetBeans, mentre il secondo

interfaccia il core con il resto della piattaforma: in questo modo è possibile utilizzare il core in

un'applicazione autonoma o in qualche altra piattaforma. Analizzerò ora il core nel dettaglio, trattando

più avanti il modo in cui DISsimulator dipende da NetBeans.

I due

blocchi funzionali

del core sono l'as-

semblatore ed il si-

mulatore. Schematicamente, il core appare come in Figura 1.

L'assemblatore riceve dall'utente un programma e costruisce una struttura di dati che lo rappresenti. Il

1

Figura 1 Uno primo sguardo ai blocchi funzionali

Page 3: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

ArchitetturaArchitettura

simulatore quindi riceve la struttura di dati e procede con la simulazione.

Tutti i package sono sottopackage di it.softeaware.dissimulator, quindi nell'indicarne il

nome sottintenderò questa parte comune.

Ad esempio it.softeaware.dissimulator.assemblatore sarà indicato più

semplicemente con assemblatore.

2. L'assemblatore

L'assemblatore è costituito da due parti: l'analizzatore sintattico (o parser) e l'analizzatore

semantico. Analizzando la sintassi il parser costruisce una rappresentazione del programma in

memoria, tramite un'opportuna struttura di dati detta albero sintattico astratto (Abstract parse tree)

[3]. Sarebbe anche possibile effettuare l'assemblaggio direttamente nelle azioni semantiche del parser,

come avveniva alcuni anni fa, ma ciò renderebbe il codice dell'analizzatore sintattico difficile da

leggere e da modificare. I calcolatori odierni non hanno problemi di memoria, almeno per quanto

riguarda la compilazione o il solo assemblaggio dei programmi di dimensioni ridotte (come in questo

caso). Il parser, quindi, verifica che il programma rispetti la grammatica (Appendice A) generando

delle ParseException nel caso in cui riscontri degli errori. Se il programma risulta

sintatticamente corretto il parser completa la costruzione dell'albero, rendendolo disponibile per la

successiva fase di analisi semantica. L'analizzatore semantico termina i controlli sul programma ed

assegna a variabili ed istruzioni l'indirizzo di memoria a cui dovranno essere caricate; l'albero astratto

viene quindi elaborato e la sua versione definitiva trasferita al simulatore.

Nel caso in cui qualche verifica fallisca l'analizzatore semantico genererebbe delle

eccezioni di tipo AssemblaggioException (di cui ParseException è una sottoclasse

privata). Le eccezioni prodotte dall'analizzatore sintattico e da quello semantico vengono raccolte e

3

Page 4: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

L'assemblatoreL'assemblatore

mostrate all'utente. L'interfaccia del package verso il resto dell'applicazione consiste nella classe

Assemblatore (ParserPD32 e AnalizzatoreSemantico sono private) che fa da

wrapper per i due componenti e gestisce lo scambio dei dati, restituendo al chiamante l'albero astratto

al termine dell'elaborazione, o una lista di messaggi d'errore. Sia nell'assemblatore sia nel simulatore,

è molto importante che la generazione e lo scambio di dati siano ben individuati per evitare memory

leak1.

Una rappresentazione del flusso di dati all'interno dell'assemblatore è mostrata in Figura 2

mentre la Figura 3 illustra la sequenza delle operazioni.

L'albero astratto

è realizzato con una gerar-

chia di classi, contenute nel

package istruzioni.

Questo package è di grande

importanza per tutta l'appli-

cazione perché è ad esso

che vengono delegate gran

parte delle operazioni. Il pd32 possiede circa cinquanta istruzioni, ed otto modi di indirizzamento. Per

realizzare le funzioni specifiche di ogni istruzione senza rendere il codice un unico blocco

difficilmente gestibile è necessario un approccio fortemente orientato agli oggetti.

1 Gruppi di oggetti non più necessari ma raggiungibili dallo stack (anche indirettamente) che per questo non possonoessere deallocati dal garbage collector.

4

Figura 2 Il flusso di dati all'interno dell'assemblatore

Page 5: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Le istruzioniLe istruzioni

2.1 Le istruzioni

Come accennato, il package delle istruzioni è ortogonale sia all'assemblatore sia al

simulatore, costituendo una libreria di notevole importanza per tutta l'applicazione.

La sua struttura è rappresentata nel diagramma UML della Figura 4.

Un programma è composto da un Eseguibile principale, da una lista di Driver ed

una di Dichiarazione eventualmente vuote. Ogni Eseguibile contiene, a sua volta, una lista

di Istruzione. Ognuna di queste liste è riempita dal parser durante il riconoscimento delle relative

sezioni del programma. Le istruzioni dell'assembly PD32 sono rappresentate da una gerarchia di classi

la cui radice implementa l'interfaccia Istruzione. Su questa struttura si basa gran parte

dell'applicazione, che operando tramite un'interfaccia delega (mediante il polimorfismo) la funzione

da svolgere alla classe concreta più appropriata. Questo schema comporta alcuni vantaggi: il parser è

indipendente dal set di istruzioni; il codice riguardante ogni istruzione è molto breve e, di

conseguenza, facilmente manutenibile (meno di 100 LOC per classe); i metodi si possono condividere

5

Figura 3 Sequence diagram dell'assemblatore

Page 6: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Le istruzioniLe istruzioni

o nascondere seguendo le regole di visibilità e dell'ereditarietà; l'interfaccia dell'intera struttura verso

il resto dell'applicazione è estremamente semplificata, essendo costituita da due sole interfacce Java.

Le istruzioni svolgono due diversi ruoli all'interno dell'applicazione: nell'assemblaggio per

il controllo dei parametri e la creazione del codice binario; nell'esecuzione per lo svolgimento delle

6

Figura 4 Diagramma del package istruzioni

Page 7: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Le istruzioniLe istruzioni

operazioni caratteristiche dell'istruzione. Questi due ruoli sono realizzati dalle interfacce Java

istruzioni.Istruzione ed esecuzione.Esecutore. Ogni classe concreta che

implementa Istruzione ha una classe annidata statica che implementa Esecutore. Essendo

statica, non c'è alcuna dipendenza tra questa classe e quella che la contiene, ma è stata implementata

annidata per includere in una sola classe tutto quello che riguarda un'istruzione. Così, ad esempio, è

possibile aggiungere un'istruzione modificando un solo file, o localizzare eventuali errori molto

facilmente.

Poiché tutte le classi che implementano Istruzione ed Esecutore sono private, il

resto del programma necessita, di un intermediario all'interno del package per accedervi. La classe

IstruzioneFactory reperisce la sottoclasse concreta, che rappresenta l'istruzione desiderata, e

la restituisce come riferimento di tipo Istruzione (o Esecutore). Quando avviene la prima

richiesta IstruzioneFactory crea una tabella di hash che contiene le istruzioni ed una matrice

con gli esecutori. La tabella mappa l'insieme dei nomi delle istruzioni su quello delle sottoclassi che le

rappresentano, e viene riempita esaminando l'albero delle classi in profondità. L'analizzatore sintattico

al riconoscimento di un'istruzione richiede ad IstruzioneFactory di creare un oggetto

Istruzione appropriato. IstruzioneFactory, quindi, ottiene (in tempo O(1)) un riferimento

all'oggetto di classe Class relativo alla particolare istruzione, e tramite la reflection1 lo istanzia e lo

restituisce. Per gli esecutori la procedura è ancora più semplice e non richiede l'introspezione. Ogni

sottoclasse di Esecutore è un Singleton, ed ogni oggetto non conserva alcuna informazione sullo

stato dell'esecuzione. Questo rende lecito l'utilizzo degli stessi oggetti in più di una computazione

parallela. Un esecutore viene richiesto al momento del fetch dell'istruzione, di cui si può ricavare

(tramite opportune maschere binarie) la classe ed il tipo. Queste due informazioni indicizzano la

matrice dalla quale si può immediatamente ottenere un riferimento all'esecutore richiesto.

1 La reflection o “introspezione” è la possibilità, offerta dalla JVM, di ottenere riferimenti ai campi, metodi o costruttoridi una data classe. In IstruzioneFactory la reflection è utilizzata per reperire un riferimento di tipojava.lang.reflect.Constructor al costruttore della classe concreta che implementa Istruzione, edistanziare un oggetto.

7

Page 8: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

La simulazioneLa simulazione

3. La simulazione

La simulazione consiste nel prelevare la struttura generata dall'assemblatore, creare gli

oggetti necessari all'esecuzione del codice, caricare il codice binario in memoria ed eseguire le

istruzioni mostrando lo stato della computazione all'utente.

Sono state definite delle interfacce Java per i tipi PD32 e Memoria e le loro

implementazioni predefinite esecuzione.DefaultPD32 e gui.MemoriaTableModel. Per

8

Figura 5 Diagramma delle classi del package esecuzione

Page 9: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

La simulazioneLa simulazione

ottenere il duplice effetto di realizzare la simulazione indipendentemente dall'interfaccia grafica e

tenere la GUI costantemente aggiornata, ho integrato il package della simulazione nel modello MVC1

della libreria Swing. La memoria di lavoro è implementata dal modello di una tabella (JTable) e le

operazioni effettuate su di essa si riflettono immediatamente sull'interfaccia grafica. Il processore e le

periferiche non sono direttamente modello di qualche componente grafico ma possono registrare dei

listener a cui notificare i cambiamenti. Il pannello che visualizza lo stato del processore si registra

come listener dell'oggetto PD32 che esegue la computazione. Alla ricezione di un evento questo viene

memorizzato e ogni decimo di secondo un javax.swing.Timer aggiorna l'interfaccia.

L'aggiornamento non avviene immediatamente nel codice che notifica l'evento perché questo è

eseguito dal thread del processore mentre solo il gestore degli eventi2 (thread AWT nella macchina

virtuale) può modificare l'interfaccia grafica. Sarebbe, inoltre, molto rischioso che il thread del

processore tentasse di modificare anche i componenti sincronizzati perché potrebbe richiedere un lock

su un oggetto Swing , pur essendo stato interrotto, terminando conseguentemente con un'eccezione

(cfr. Capitolo 4). Questo, per altro, permette a due eventi riguardanti lo stesso registro e giunti

nell'intervallo tra un aggiornamento ed il successivo di sovrascriversi evitando di aggiornare due volte

(inutilmente) l'interfaccia. Il diagramma delle classi del package esecuzione è mostrato in Figura

5 .

3.1 Avvio dell'esecuzione

La costruzione di tutti gli oggetti necessari alla simulazione non è un operazione semplice

ed è stata implementata in BuilderEsecuzione. Avviare per la prima volta l'esecuzione richiede:

1 Swing ha un'architettura basata su una variante del pattern MVC (Model-Control-View) nota come “separable modelarchitecture”. View e Control di MVC sono fusi in un unico componente responsabile della visualizzazione e delcomportamento nell'interfaccia grafica mentre il Model contiene i dati e notifica gli oggetti associati dei cambiamenti.

2 In Swing un solo thread può modificare l'interfaccia ed è il “gestore degli eventi” o thread di AWT (Abstract WindowToolkit). Alcuni componenti sono sincronizzati e possono essere modificati da altri thread ma l'accesso al loro statorichiede l'acquisizione di un lock.

9

Page 10: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Avvio dell'esecuzioneAvvio dell'esecuzione

1. La creazione della memoria, cioé di un oggetto la cui classe implementa esecuzione.Memo-

ria (gui.MemoriaTableModel).

2. La creazione di una DaisyChain inizialmente vuota.

3. La creazione del processore, cioè di un oggetto la cui classe implementa esecuzione.Pro-

cessore (DefaultPD32).

4. La scansione del file con le impostazioni delle periferiche, la loro creazione ed inserimento nella

daisychain.

5. La creazione dell'interfaccia grafica e la sua registrazione come listener del processore e della

memoria.

Il processore, all'atto della sua istanziazione, crea un oggetto StatoEsecuzione che

racchiude tutto il necessario alla simulazione. Tramite questo è possibile ottenere dei riferimenti alla

memoria di lavoro, alla daisychain ed al processore stesso. Lo StatoEsecuzione contiene anche

delle variabili usate dagli esecutori (cfr. Capitolo 2.1) per effettuare la computazione.

Quando tutti gli oggetti sono stati creati il programma assemblato deve essere caricato in

memoria dalla classe Loader ed il registro PC del processore inizializzato con l'indirizzo della prima

istruzione.

3.2 Le periferiche

La simulazione delle periferiche è l'innovazione più importante tra quelle introdotte da

DISsimulator. Nel precedente simulatore l'utente era completamente responsabile del comportamento

delle periferiche dovendo personalmente modificarne lo stato. In DISsimulator le periferiche sono

autonome ed eseguite in un proprio thread in concorrenza con quello del processore.

Una periferica è costituita da una classe che implementa eseuzione.periferi-

che.Periferica e da almeno una classe che implementa esecuzione.periferiche.In-

10

Page 11: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Le perifericheLe periferiche

terfacciaIO. Prima di procedere con la descrizione è indispensabile una precisazione riguardo al

termine “interfaccia”. Con “interfaccia Java” si indica un tipo del linguaggio di programmazione

definito in [4]; con “interfaccia hardware” si indica il circuito della periferica deputato allo scambio di

dati con il processore. La periferica e le interfacce sono simulate ognuna da una classe che deve

implementare le interfacce Java menzionate. Per semplificare la scrittura delle periferiche, renderle

meno soggette ad errori, e standardizzare le operazioni comuni sono disponibili delle implementazioni

di default che possono essere ereditate ridefinendo pochi metodi.

Alla creazione degli oggetti responsabili della simulazione viene analizzato il file xml che

contiene le impostazioni delle periferiche installate. Per ognuna viene memorizzato il nome della

classe che implementa Periferica e per ogni interfaccia hardware l'IVN e l'indirizzo. Tutte le

interfacce hardware sono inserite in una lista (daisychain) da cui dipende la priorità della periferica.

Nel PD32 ogni interfaccia hardware deve specificare un IVN ed un indirizzo. Un array di

256 elementi da quattro byte memorizza gli indirizzi dei driver di ogni interfaccia. L'Interrupt Vector

Number è l'indice dell'interfaccia hardware in questo array. L'indirizzo è il numero che identifica

l'interfaccia hardware. Quando il processore riceve un' interruzione manda un segnale di ACK alla

prima interfaccia hardware installata. Nel caso in cui quella fosse l'interfaccia che ha richiesto

l'interruzione, il segnale di ACK sarebbe mascherato e la periferica avvierebbe il protocollo di

comunicazione con il processore. Altrimenti il segnale di ACK verrebbe propagato all'interfaccia

successiva. Da questa descrizione si evince che l'ordine in cui le interfacce hardware sono installate

nel sistema (costituendo la daisychain) ne determina la priorità. Per molte periferiche con più di una

interfaccia hardware è fondamentale che l'ordine tra queste sia rispettato.

Come precedentemente accennato, il nome delle classi delle periferiche installate viene

letto dal file xml e la periferica istanziata. Successivamente l'inserimento delle interfacce nella

DaisyChain viene delegato alla periferica poiché questa conosce l'ordine in cui devono essere

installate. Ogni interfaccia hardware ha un riferimento ad un oggetto di tipo

11

Page 12: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Le perifericheLe periferiche

RicevitoreDiSegnali che rappresenta una vista del processore mostrando solo il metodo

sendInterrupt() ed impedendo la modifica diretta del suo stato. In DefaultPD32 una variabile intera

memorizza il numero di interruzioni pendenti e viene incrementata ad ogni invocazione di

sendInterrupt(true) da parte delle interfacce hardware. Se il numero di interruzioni ricevute è

maggiore di zero, al termine dell'istruzione corrente, il processore ottiene dalla DaisyChain l'ivn

della prima (per posizione) interfaccia hardware che ha richiesto l'interruzione, ed imposta il registro

PC all'indirizzo contenuto nella relativa cella dell'Interrupt Vector. In questo modo l'istruzione

successiva sarà quella del driver della periferica. Il driver deve modificare il flag status (con le

istruzioni start o reset) dell'interfaccia per rimuovere l'interruzione ed evitare di essere eseguito

all'infinito. L'invocazione del metodo sendInterrupt(false) decrementa il numero di interruzioni

pendenti nel processore. In questo modo eventuali interruzioni giunte durante l'esecuzione della stessa

istruzione non si sovrascrivono e gli eventi che vengono simulati sono esattamente quelli previsti nella

progettazione del PD32.

3.2.1 Scrivere nuove periferiche

La principale conseguenza delle innovazioni apportate alle periferiche è la necessità di

realizzarne di nuove. E' certamente possibile scrivere delle periferiche molto semplici che si possano

usare esattamente come nel vecchio simulatore ma limitarsi a questo renderebbe vano il nuovo

sistema.

Per poter essere installata dall'utente una periferica deve apparire nella lista di quelle di-

sponibili che corrisponde al contenuto del file esecuzione/periferiche/periferiche.-

list. Quest'ultimo è un file di testo le cui righe riportano il nome della classe della periferica. Ad

ogni periferica è associato un file “periferica.properties” in cui vengono specificati il nome ed una

descrizione da mostrare all'utente. Nel pannello “Gestione periferiche” sono visualizzate a sinistra le

12

Page 13: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Scrivere nuove perifericheScrivere nuove periferiche

periferiche disponibili ed a destra quelle attualmente installate. Inserendo un periferica a destra questa

sarà installata nella stessa posizione della daisychain in cui figura nella lista. Subito dopo la sua

creazione, all'oggetto Periferica viene assegnato un riferimento ad un RicevitoreDiSe-

gnali necessario per l'azione successiva. Alla creazione della periferica segue l'installazione delle

interfacce con i metodi Periferca.installaIn() e Periferica.installaOut().

Come indicato precedentemente, ogni periferica è eseguita in un proprio thread. Nel

nuovo thread viene invocato il metodo Periferica.esegui() tramite il quale il controllo passa

alla classe della periferica. Con lo scopo di agevolare la realizzazione di nuove periferiche sono state

create le classi PerifericaAstratta, InputAstratta e OutputAstratta che

ridefiniscono gran parte dei metodi richiesti. Esattamente come le interfacce hardware sono standard

per tutte le periferiche del PD32, InputAstratta e OutputAstratta implementano tutti i

metodi e bisogna aggiungere solo quelli per la comunicazione con la propria periferica che, dovendo

generalmente essere package private, non possono comparire nella definizione dell'interfaccia. La

classe che estende periferica astratta, invece, oltre ai metodi per l'installazione delle interfacce

hardware deve definire dei tamplate method ([5]):

• eseguiInizializza(): invocato prima di qualsiasi stard(). La periferica può creare

un'interfaccia grafica ed effettuare qualunque operazione di inizializzazione.

• eseguiStard(): esegue l'operazione caratteristica della periferica

• eseguiTermina(): chiamato alla fine della simulazione, quando l'utente clicca sul tasto

“Stop”. Esegue tutte le operazioni per una corretta terminazione, ad esempio nasconde la GUI.

Quando tutti i metodi sono stati realizzati è sufficiente aggiungere il nome completo (cioè

includendo il nome del package) della classe che estende PerifericaAstratta in periferiche.li-

st.

13

Page 14: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Il ciclo istruzioneIl ciclo istruzione

3.3 Il ciclo istruzione

E' possibile eseguire la simulazione in tre modalità:

• Intero programma

• Un'istruzione

• Una fase

Dopo che tutti gli oggetti sono stati creati seguendo la procedura descritta nel capitolo 3.1

il GestoreSimulazione diventa il responsabile della creazione e terminazione dei thread. A

questo oggetto è associato il seguente diagramma di stato:

Nello stato iniziale il GestoreSimulazione è in attesa di un oggetto

StatoEsecuzione da cui ottenere riferimenti a tutti gli oggetti creati per la simulazione. Ricevuto

uno StatoEsecuzione il GestoreSimulazione può avviare la simulazione. A questo scopo

crea ed avvia un thread per il processore ed uno per ogni periferica. In questa fase la simulazione è in

corso ed il thread del processore è fermo in attesa di un comando. I comandi vengono inseriti in una

lista dall'esterno e estratti dal thread del processore secondo il classico modello del produttore-

consumatore. I comandi sono oggetti di classi che implementano un'opportuna interfaccia Java con un

14

Figura 6 Diagramma di stato di un GestoreSimulazione

Page 15: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Il ciclo istruzioneIl ciclo istruzione

solo metodo: esegui(); Queste classi sono l'equivalente orientato agli oggetti dei puntatori a

funzione e sono descritti come pattern Command in [5]. Esistono tre comandi, corrispondenti alle

modalità d'esecuzione. Si può eseguire un'intera istruzione o una fase e ad ognuno corrisponde un

comando. Quello dell'intero programma, invece, mette in coda il comando per una istruzione e poi se

stesso realizzando una sorta di ricorsione utilizzando la coda.

L'inserimento di un comando attiva il thread del processore iniziando il ciclo

dell'istruzione. La prima operazione è l'individuazione dell'Esecutore corretto. L'istruzione puntata

dal registro PC viene letta dalla memoria per estrarne la classe ed il tipo che indicizzano la matrice

degli esecutori (cfr. Capitolo 2.1). L'esecuzione viene quindi delegata all'oggetto individuato che

esegue fetch e completamento (separatamente, se richiesto) agendo come un Visitor (uno dei pattern

descritti in [5]) dell'oggetto StatoEsecuzione del processore. Dopo ogni istruzione viene

verificato lo stato delle interruzioni ed eventualmente si esegue il salto alla prima istruzione del driver

dell'interfaccia che ha mandato il segnale d'interruzione.

Quando l'utente clicca sul tasto “Stop” il GestoreSimulazione transita verso lo

stato “Thread in esecuzione” con la simulazione non più in corso. In questa fase ai thread è inviato il

comando di terminazione, alla ricezione del quale le periferiche possono rimuovere la GUI e

completare le proprie operazioni correttamente. Quando tutti i thred hanno concluso il loro ciclo vitale

la simulazione può essere riavviata.

Al termine della simulazione lo stato del processore e delle periferiche non viene

reimpostato se non esplicitamente richiesto dall'utente. Dunque l'esecuzione può essere ripresa dallo

stato in cui era stata sospesa in qualsiasi momento.

In Figura 7 è mostrato il DFD del simulatore.

15

Page 16: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Il ciclo istruzioneIl ciclo istruzione

4. L'interfaccia Grafica

Gran parte dell'interfaccia grafica dipende dalla piattaforma (in questo caso NetBeans) e

quindi non è integrata nel core. Alcune parti però possono essere realizzate in modo piuttosto generico

ed in comune tra tutte le piattaforme, in particolare il pannello per comandare l'esecuzione e quello

per le interfacce hardware. In questi casi ho deciso di realizzare un'implementazione generica che

potesse essere utilizzata dai client del core ed è il motivo principale per cui ho scelto Swing piuttosto

che SWT e conseguentemente NetBeans piuttosto che Eclipse.

Nell'analisi dello svolgimento della simulazione è stato illustrato come questa non dipenda

dall'interfaccia verso l'utente e possa procedere autonomamente come un'applicazione batch. Lo stato

della simulazione è costituito dal processore, dalla memoria e dalle periferiche, e tutto è raccolto

16

Figura 7 DFD del simulatore

Page 17: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

L'interfaccia GraficaL'interfaccia Grafica

nell'oggetto StatoEsecuzione che contiene anche le variabili utilizzate dagli esecutori. Il

meccanismo con cui avvengono gli aggiornamenti dell'interfaccia si basa sulla notifica degli eventi e

su un Timer di Swing.

Nel package esecuzione.eventi sono definite le classi ModificaRegistroE-

vent, StatoInterfacciaEvent e StatoSimulazioneEvent con i relativi Listener.

Questi eventi vengono generati rispettivamente dal processore, dalle interfacce hardware e dal

GestoreSimulazione. Il pannello dell'esecuzione si registra come listener di eventi del

processore e del gestore della simulazione, mentre il pannello delle interfacce hardware con

l'interfaccia a cui è associato. Una cosa da tenere in considerazione, nella scrittura del codice dei

metodi che ricevono la notifica di un evento, è che ad eseguire il codice del metodo è il thread che

genera l'evento cioé, in particolare, il thread del processore. D'altra parte in Swing un solo thread può

modificare l'interfaccia ed è il gestore degli eventi o thread AWT. Solo alcuni componenti in Swing

sono sincronizzati. Aggiornare l'interfaccia grafica nei metodi di notifica comporterebbe due

svantaggi: la simulazione dovrebbe accollarsi la responsabilità dell'aggiornamento, risultando

fortemente rallentata, e nel modificare dei componenti sincronizzati il thread del processore dovrebbe

richiedere dei lock1. Il thread del processore, quando non ha dei comandi da attuare, sospende la

propria esecuzione fino all'inserimento di un nuovo comando nella coda e alla conseguente

invocazione del metodo interrupt() sul thread stesso. Ciò significa che il flag interrupted associato al

thread viene costantemente settato e resettato. I componenti di Swing con metodi sincronizzati, come

ad esemempio JTextField.setText() implementano un meccanismo di locking ad un livello

superiore dei semplici blocchi sincronizzati. Infatti, permettono a più thread di leggere il contenuto

dell'oggetto contemporaneamente (nel caso di JTextField il testo memorizzato nel modello,

generalmente una sottoclasse di AbstractDocument) ma ad uno solo di modificarlo. Tale

sincronizzazione ha però una limitazione non documentata: il thread che vuole modificare il

1 In Java ogni oggetto ha associato un lock, cioé una chiave che permette al solo thread che è riuscito ad ottenerla dieseguire i metodi sincronizzati sull'oggetto.

17

Page 18: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

L'interfaccia GraficaL'interfaccia Grafica

componente non può essere interrotto durante il tentativo di acquisizione del lock. Nel caso in cui un

thread fosse interrotto sarebbe generato un Error che quindi porterebbe all'immediata terminazione

del thread senza possibilità di gestire l'eccezione. Un thread continuamente interrotto come quello del

processore certamente non riuscirebbe ad evitare che questo accada.

Il pannello dell'esecuzione possiede un array di eventi indicizzato dal codice associato a

ciascun registro. Quando il thread del processore notifica un evento imposta la cella dell'array relativa

al registro coinvolto nel cambiamento di stato. Ogni decimo di secondo nel thread AWT viene

eseguito il metodo di aggiornamento dell'interfaccia che per ogni evento ricevuto apporta le dovute

modifiche. Se più di un evento dovesse avvenire tra due aggiornamenti dell'interfaccia, solo l'ultimo

comporterebbe una effettiva modifica. Poiché l'utente non è in grado di cogliere due cambiamenti in

meno di un decimo di secondo questo limita il numero di modifiche da apportare alla GUI e riduce i

tempi d'esecuzione.

Le modifiche alla memoria di lavoro si riflettono immediatamente sulla tabella che ne

visualizza il contenuto poiché la memoria stessa è anche il modello della JTable che la rappresenta.

La RAM a disposizione del PD32 ha una dimensione di 10KB e non sarebbe stato efficiente

duplicarla nel simulatore e nell'interfaccia grafica dovendo, in tal caso, risolvere i conseguenti

problemi di sincronizzazione. Anche il pannello dell'esecuzione riceve la notifica dei cambiamenti

nelle memoria e la salva in una variabile per selezionare, al successivo aggiornamento dell'interfaccia,

la riga con l'ultima cella modificata.

Il pannello delle interfacce hardware è a disposizione delle periferiche. Può essergli

assssegnata un'interfaccia hardware e viene automaticamente aggiornato alla notifica dei

StatoInterfacciaEvent.

18

Page 19: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

NetBeans platformNetBeans platform

5. NetBeans platform

Il modulo nbplugin integra il core con la piattaforma di NetBeans. NetBeans è costituito a

sua volta da un core non accessibile dall'esterno, da un'api (openide) e dai moduli. Tutte le

funzionalità non relative alla piattaforma sono realizzate dai moduli.

Un modulo è un file jar contenente, oltre alle classi che utilizza, dei particolari attributi nel

manifest il cui valore specifica: nome, descrizione, versione ed eventuali dipendenze da altri moduli,

da una versione della JVM o della piattaforma stessa. L'installazione di menù, actions, toolbar ed altri

componenti grafici o relativi al comportamento dell'interfaccia avviane mediante dei file xml.

La aspetto della piattaforma può essere modificato tramite il branding (personalizzazione)

con un meccanismo analogo alla localizzazione (tramite file properties).

5.1 Il caricamento delle classi

Una piattaforma basata su dei file jar riconosciuti durante l'esecuzione pone particolari

problemi relativi al caricamento delle classi dato che il classpath non è noto a priori. Ad ogni modulo

viene assegnato un ClassLoader che gli permette di accedere all'api e alle classi nel proprio file

jar ma non a quelle di altri moduli. Per poter richiedere il caricamento di classi in moduli esterni è

necessario specificare la dipendenza nel file manifest. In tal caso il ClassLoader relativo al

modulo avrà nel path anche tutti i jar dei moduli da cui questo dipende.

Inizialmente il progetto di DISsimulator prevedeva che le periferiche non fossero

contenute nel core ma ognuna in un proprio file potendo essere facilmente scritta ed aggiunta da

qualsiasi utente. Una tale realizzazione richiedeva la creazione di un URLClassLoader con il

percorso del file jar della periferica per accedere alle classi. Il modulo doveva provvedere al

19

Page 20: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Il caricamento delle classiIl caricamento delle classi

caricamento delle classi della periferica creando il classloader necessario come figlio1 di quello

associato al modulo in modo che la periferica potesse avere acesso anche all'api di NetBeans.

Purtroppo questa soluzione si è rivelata inattuabile per via di un meccanismo di serializzazione interno

alla piattaforma.

NetBeans, all'avvio, tenta di ripristinare la precedente sessione deserializzando i

componenti attivi al momento della chiusura. Se una periferica crea un pannello all'interno della

finestra principale questo viene registrato e serializzato. Quando la piattaforma tenta di ripristinare il

pannello non è più in grado di localizzarne la classe, essendo questa stata caricata dal modulo e non

dal classloader di NetBeans. Questa evenienza genera numerosi messaggi d'errore nel file di log e,

sebbene non sembri avere altre conseguenze, ho preferito unire le periferiche al core dove possono

essere regolarmente caricate. Non è possibile nemmeno l'inserimento delle periferiche in un modulo

esterno, diverso dal core perché i due moduli (core e periferiche) dovrebbero dichiarare delle

dipendenze reciproche e l'attivazione dei moduli è un processo strettamente sequenziale. In questo

caso la piattaforma non sarebbe in grado di attivare uno dei due

Conclusioni

Questo progetto è nato con l'intento di modernizzare un software datato avvicinandolo ai

suoi utenti e facendo tesoro dell'esperienza accumulata nei dieci anni in cui è stato utilizzato. L'intero

design dell'applicazione è stato concepito per permetterne facilmente la modifica e l'estensione.

Sono già in fase di realizzazione dei programmi a scopo didattico che faranno presto parte

di DISsimulator, con la speranza che il progetto possa diventare una suite completa e matura.

Il software è opensource ed è rilasciato con licenza Gnu GPL (General Public License).

1 I classloader sono organizzati in maniera gerarchica ad albero. Ognuno di essi nel tentativo di localizzare una risorsadeve prima fare riferimento al padre (il primo classloader verso l'alto nella gerarchia) e solo se questi non riescenell'operazione cercare tra le proprie risorse.

20

Page 21: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Appendice A: La grammatica dell'assembly PD32

parse := ( <FINE_LINEA> )*

programma

<EOF>

programma := "ORG" ( numero )? <FINE_LINEA>

( <FINE_LINEA> )*

( dichiarazione <FINE_LINEA> ( <FINE_LINEA> )* )*

"CODE" <FINE_LINEA>

( <FINE_LINEA> )*

( istruzione <FINE_LINEA> ( <FINE_LINEA> )* )*

( driver )*

"END"

( <FINE_LINEA> )*

driver:= "DRIVER" numero ( "," numero )? <FINE_LINEA>

( <FINE_LINEA> )*

( istruzione <FINE_LINEA> ( <FINE_LINEA> )* )*

numero:= ( ( "+" | "-" ) )? restoNumero

restoNumero := ( <IDENTIFICATORE> | <INTERO> )

dichiarazione := <IDENTIFICATORE> <DIRETTIVA_DI_DEFINIZIONE> numero

( "," numero )*

istruzione := <IDENTIFICATORE> ( ( ":" ( <FINE_LINEA> )*

<IDENTIFICATORE> restoIstruzione ) |

( restoIstruzione )

)

restoIstruzione := ( parametro ( "," parametro )? )?

parametro := ( conRegistro |

immediato |

assolutoOSpiazzamento |

"-" ( assolutoOSpiazzamento | predecremento ) |

indirettoOPostincremento )

21

Page 22: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

conRegistro := <REGISTRO>

immediato := "#" numero ( ( "+" | "-" ) <INTERO> )?

assolutoOSpiazzamento := restoNumero ( ( "+" | "-" ) <INTERO> )?

( "(" ( <REGISTRO> | "PC" ) ")" )?

indirettoOPostincremento := "(" <REGISTRO> ")" ( "+" )?

predecremento := "(" <REGISTRO> ")"

22

Page 23: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Bibliografia

1: Cioffi G., Jorno A., Villani T., Il processore PD32, 1994

2: Eric Steven Raymond, The Cathedral and the Bazaar, 2000

3: Andrew W. Appel, Modern Compiler Implementation in Java, 1998

4: Gosling J., Joy B., Steele G., Bracha G., The Java Language Specification, 2000,

http://java.sun.com/docs/books/jls/second_edition/html/j.title.doc.html

5: Gamma E., Helm R., Johnson R., Vlissides J., Design Patterns, 1995

Page 24: Sviluppo di un ambiente per la scrittura e la simulazione ... · ho individuato i due principali framework basati su Java: NetBeans di Sun Microsystems® ed Eclipse di IBM®. Entrambi

Indice generale

Introduzione.............................................................................................................................................2

1. Architettura..........................................................................................................................................2

2. L'assemblatore.....................................................................................................................................4

2.1 Le istruzioni..................................................................................................................................6

3. La simulazione....................................................................................................................................9

3.1 Avvio dell'esecuzione.................................................................................................................10

3.2 Le periferiche..............................................................................................................................11

3.2.1 Scrivere nuove periferiche..................................................................................................13

3.3 Il ciclo istruzione........................................................................................................................15

4. L'interfaccia Grafica..........................................................................................................................17

5. NetBeans platform.............................................................................................................................20

5.1 Il caricamento delle classi...........................................................................................................20

Conclusioni............................................................................................................................................21

Appendice A..........................................................................................................................................22