• Non ci sono risultati.

3.3 Analisi del codice sorgente

3.3.1 Implementazione del sistema di input e output

3.3.1.5 Tastiera ed istruzioni di input

default : { i f ( simulation_mode == KERNEL_MODE) error_trap (UNSUPPORTED_DEVICE_ADDRESS_ERROR) ; else e r r o r ( " unsupported d e v i c e address " ) ; } } }

3.3.1.5 Tastiera ed istruzioni di input

Il funzionamento del nuovo sistema di gestione dell'input è duale rispetto a quel-lo di output. Tuttavia, la simulazione corretta del dispositivo della tastiera è risultata essere molto più problematica in quanto il normale funzionamento dello standard input è di tipo buerizzato e caratterizzato da echo automatico. Il me-todo per bypassare questo genere di meccanismo richiede di disabilitare il buer e l'echo sul terminale di ingresso e di eettuare una lettura a basso livello sfrut-tando la chiamata a sistema read(). In appendice A si trova il codice sorgente della funzione getkey() utilizzata come spunto per la scrittura del codice relativo al thread della tastiera.

L'introduzione del thread della tastiera ha comportato la necessità di ripen-sare la gestione del buer del dispositivo: il sistema input_buer[]\input_index (presentato in precedenza) è stato sostituito da un buer di tipo FIFO, la cui gestione è di tipo circolare. Il buer di ingresso FIFO, a dierenza del tipo pre-cedente, è caratterizzato da due indici che ne identicano l'inizio del contenuto e la ne di esso; la struttura dati che ne deriva è la seguente:

typedef unsigned char byte ; struct f i f o _ b u f f e r { int f i r s t ; int l a s t ; byte b u f f e r [ FIFO_SIZE+1]; } ; struct f i f o _ b u f f e r f i f o _ i n p u t _ b u f f e r ;

L'accesso a questa struttura dati viene realizzato mediante la chiamata di opportune macro predenite:

#define FIFO_SIZE 256

#define FIFOGET( f i f o , x ) \ x=f i f o . b u f f e r [ f i f o . f i r s t ] ; \

3.3. ANALISI DEL CODICE SORGENTE 43

f i f o . f i r s t = ( f i f o . f i r s t +1) % (FIFO_SIZE+1) ; #define FIFOPUT( f i f o , x ) \

f i f o . b u f f e r [ f i f o . l a s t ] = x ; \

f i f o . l a s t = ( f i f o . l a s t +1) % (FIFO_SIZE+1) ;

#define FIFOFULL( f i f o ) f i f o . f i r s t == ( f i f o . l a s t +1) % (FIFO_SIZE+1) #define FIFOEMPTY( f i f o ) f i f o . f i r s t == f i f o . l a s t

FIFOGET(fo_input_buer, x) restituisce in x il dato letto all'indirizzo puntato dal suo campo rst e aggiorna, incrementandolo, l'indice di inizio del buer;

FIFOPUT(fo_input_buer, x) pone in dato x nella locazione puntata da last ed incrementa last;

FIFOFULL(fo_input_buer) restituisce la condizione per la quale il buf-fer si considera pieno, ovvero che il primo e l'ultimo indice siano distanti tra di loro per un numero di elementi pari FIFO_SIZE

FIFOEMPTY(fo_input_buer) fornisce la condizione di buer vuoto, ov-vero quando gli indici di primo ed ultimo elemento coincidono. Consi-derato che qualsiasi stringa che verrà inserita nel buer sarà seguita dal carattere '\0', la condizione di ne buer può essere riscritta come -fo_input_buer.buer[fo_input_buer.rst] == '\0'.

In alcune occasioni, è necessario leggere o scrivere il valore puntato dal primo o dall'ultimo indice senza necessariamente eettuare una FIFOGET() e una FIFO-PUT(). In questi casi, è suciente accedere alla struttura ed operare direttamente sul campo buer.

Dal punto di vista architetturale, l'array di byte individuato da fo_input_ _buer.buer[] si frappone tra la CPU e l'utente: comunicare qualcosa all'archi-tettura equivale dunque a riempire questo buer. Nelle precedenti versioni del si-mulatore, l'unica maniera possibile per scrivere in questa area di memoria era me-diante il comando load input opportunamente utilizzato in COMMAND_MODE, nel prompt dei comandi di SimCPU. Ciò evidentemente non permetteva di comu-nicare in alcun modo con i programmi durante la loro fase di running in quanto il riempimento di tale buer avveniva in maniera sincrona rispetto al usso di esecuzione del programma.

L'introduzione del thread della tastiera ha reso possibile il riempimento del buer anche in modalità on run e dunque non è più necessario bloccare il pro-gramma ed eseguire il comando opportuno e garantire il caricamento. Questa metodologia, tuttavia, resta comunque necessaria quando si vuole caricare il buf-fer durante la COMMAND_MODE in quanto il thread della tastiera, in questo caso, viene completamente disabilitato. Per la precisione, la lettura a carattere senza echo, viene abilitata solamente se il programma principale sta eseguendo la funzione execute(); nel momento in cui l'esecuzione passa alla user_control(),

viene ripristinato il sistema di riempimento normale del buer, ovvero quello mediante fgetc().

Per motivi di tempistiche, non è stato possibile particolarizzare e ampliare la questione riguardante l'input da tastiera; se ne fornisce però una implementazione basilare sulla quale poi poter progettare modiche future sempre più importan-ti. Questo ambito, infatti, si presta molto bene a modiche anche piuttosto complesse.

Qui di seguito verranno prima di tutto presentate le nuove funzioni di lettura di byte e word ed in seguito verrà analizzato a fondo il codice del thread del dispositivo.

byte input_byte ( word address ) { switch ( address ) { case CLOCK_ADDR_CNT: { i f ( trace_mode == TRACE_MODE_EXTENDED) { char s [MAXMSGLEN] ;

s p r i n t f ( s , "TRACE: %02X read from c l o c k CONTROL Address \n" , d e v i c e s _ b u f f e r [CLOCK_ADDR_CNT] ) ; message ( s ) ; } return d e v i c e s _ b u f f e r [CLOCK_ADDR_CNT] ; } case KEYBOARD_ADDR_CNT: { i f ( trace_mode == TRACE_MODE_EXTENDED) { char s [MAXMSGLEN] ;

s p r i n t f ( s , "TRACE: %02X read from Keyboard CONTROL Address \n" , ( byte ) d e v i c e s _ b u f f e r [KEYBOARD_ADDR_CNT] ) ; message ( s ) ; } return d e v i c e s _ b u f f e r [KEYBOARD_ADDR_CNT] ; } case KEYBOARD_ADDR_DAT: { i f ( trace_mode == TRACE_MODE_EXTENDED) { char s [MAXMSGLEN] ; i f ( ! (FIFOEMPTY( f i f o _ i n p u t _ b u f f e r ) ) )

s p r i n t f ( s , "TRACE: %02X read from input b u f f e r \n" ,

f i f o _ i n p u t _ b u f f e r . b u f f e r [ f i f o _ i n p u t _ b u f f e r . f i r s t ] ) ; else

s p r i n t f ( s , "TRACE: read from input b u f f e r f a i l e d ( b u f f e r empty ) \n" ) ; message ( s ) ; } i f ( ! (FIFOEMPTY( f i f o _ i n p u t _ b u f f e r ) ) ) { FIFOGET( f i f o _ i n p u t _ b u f f e r , d e v i c e s _ b u f f e r [KEYBOARD_ADDR_DAT] ) } else

3.3. ANALISI DEL CODICE SORGENTE 45 d e v i c e s _ b u f f e r [KEYBOARD_ADDR_DAT]=0; return d e v i c e s _ b u f f e r [KEYBOARD_ADDR_DAT] ; } default : { i f ( simulation_mode == KERNEL_MODE) error_trap (UNSUPPORTED_DEVICE_ADDRESS_ERROR) ; else e r r o r ( " unsupported d e v i c e address " ) ; } } }

Il nuovo sistema di input, come già accennato, e come si deduce ora più chia-ramente dal codice presentato, implementa la possibilità di leggere direttamente dal buer di input eseguendo una FIFOGET() che restituisce sul canale dati del-la tastiera il carattere inserito nel buer di ingresso. La lettura dal buer dei dispositivi avviene in un ciclo di buer e dunque non è più necessario esegui-re le operazioni di test sui ag di completamento che sono stati denitivamente eliminati.

Essendo la tastiera un dispositivo orientato a carattere, la lettura di una word è fuori luogo. Tuttavia, per questioni di simmetria, è stato deciso il suo mante-nimento e l'eettuazione di questa operazione, come si può intuire dal codice, si limita a restituire un dato word di cui il byte più signicativo è nullo e di cui il meno signicato contiene il dato letto.

Confrontando con le relative funzioni di output, si nota inoltre che il timer, dispositivo che sarà analizzato qui di seguito, presenta un canale di controllo bidirezionale in quanto è necessario sia poterlo leggere sia poterlo scrivere.

byte input_word ( word address ) { switch ( address ) { case CLOCK_ADDR_CNT: { i f ( trace_mode == TRACE_MODE_EXTENDED) { char s [MAXMSGLEN] ;

s p r i n t f ( s , "TRACE: %04X read from c l o c k CONTROL Address \n" , ( word ) d e v i c e s _ b u f f e r [CLOCK_ADDR_CNT] ) ; message ( s ) ;

}

return ( ( word ) d e v i c e s _ b u f f e r [CLOCK_ADDR_CNT] ) ; } case KEYBOARD_ADDR_CNT: { i f ( trace_mode == TRACE_MODE_EXTENDED) { char s [MAXMSGLEN] ;

s p r i n t f ( s , "TRACE: %04X read from Keyboard CONTROL Address \n" , ( word ) d e v i c e s _ b u f f e r [KEYBOARD_ADDR_CNT] ) ; message ( s ) ;

}

return ( ( word ) d e v i c e s _ b u f f e r [CLOCK_ADDR_CNT] ) ; } case KEYBOARD_ADDR_DAT: { i f ( trace_mode == TRACE_MODE_EXTENDED) { char s [MAXMSGLEN] ; i f ( ! (FIFOEMPTY( f i f o _ i n p u t _ b u f f e r ) ) )

s p r i n t f ( s , "TRACE: %04X read from input b u f f e r \n" ,

( word ) f i f o _ i n p u t _ b u f f e r . b u f f e r [ f i f o _ i n p u t _ b u f f e r . f i r s t ] ) ; else

s p r i n t f ( s , "TRACE: read from input b u f f e r f a i l e d ( b u f f e r empty ) \n" ) ; message ( s ) ; } i f ( ! (FIFOEMPTY( f i f o _ i n p u t _ b u f f e r ) ) ) { FIFOGET( f i f o _ i n p u t _ b u f f e r , d e v i c e s _ b u f f e r [KEYBOARD_ADDR_DAT] ) } else d e v i c e s _ b u f f e r [KEYBOARD_ADDR_DAT]=0;

return ( ( word ) d e v i c e s _ b u f f e r [KEYBOARD_ADDR_DAT] ) ; } default : { i f ( simulation_mode == KERNEL_MODE) error_trap (UNSUPPORTED_DEVICE_ADDRESS_ERROR) ; else e r r o r ( " unsupported d e v i c e address " ) ; } } }

Si analizzi dapprima il caricamento di una stringa in modalità COMMAND MODE mediante il comando Load Input. L'introduzione del buer circolare ne ha comportato una profonda revisione.

case TOKEN_LOAD:

switch ( get_command ( input_stream , " load " , " input−b u f f e r " , interactive_mode ) )

{

case TOKEN_INPUTBUFFER: /∗ load input−b u f f e r ∗/ {

byte dummy_buffer [ FIFO_SIZE+1]; i f ( get_ASCII_data ( input_stream ,

"ASCII input ( must begin with ' $ ' ) : " , dummy_buffer , interactive_mode ) != TOKEN_ASCII_DATA) { char s [MAXMSGLEN] ; s p r i n t f ( s , " I n v a l i d input \n" ) ; message ( s ) ; purge_input ( input_stream ) ;

3.3. ANALISI DEL CODICE SORGENTE 47 } else { int i ; for ( i =0; dummy_buffer [ i ]!=0 x00 ; i ++) {

FIFOPUT( f i f o _ i n p u t _ b u f f e r , ( ( byte ) dummy_buffer [ i ] ) ) }

FIFOPUT( f i f o _ i n p u t _ b u f f e r , ( ( byte ) dummy_buffer [ i ] ) ) ; } break ; } default : { char s [MAXMSGLEN] ; s p r i n t f ( s , "Wrong command\n" ) ; message ( s ) ; purge_input ( input_stream ) ; } } break ;

Prima di tutto, la stringa ASCII inserita utilizzando il comando Load Input non viene più caricata direttamente nel buer in quanto ora vi è la necessità di aggiornare l'indice di ne buer ad ogni scrittura, cosa che prima non veniva fatta in quanto non vi era alcun indice di questo tipo. Per evitare di modica-re la funzione get_ascii_data() è stato deciso di ovviamodica-re al problema copiando preventivamente i caratteri da immettere nel buer di ingresso in un array tem-poraneo (dummy_buer[]) e poi eseguire ciclicamente il FIFOPUT() di ogni suo carattere sul buer di ingresso vero e proprio no a quando l'ultimo valore letto dal buer temporaneo non coincide esattamente con il carattere di NULL ('\0'). Così facendo, il ciclo for non permette il caricamento del carattere NULL in cal-ce alla stringa: è dunque necal-cessario, come ultima operazione, eseguire un ultimo FIFOPUT() per terminare il caricamento della stringa mediante il carattere del dummy_buer[] puntato dal valore raggiunto da i, che corrisponde esattamente al carattere di NULL.

A questo punto verrà analizzato il thread della tastiera.

void ∗ thread_keyboard ( void ∗argument ) { while ( 1 ) { while ( e x e c u t i n g ) { char ch ; set_raw_mode ( ) ; while ( e x e c u t i n g ) {

while ( ( read (STDIN_FILENO, &ch , 1)<=0 ) && ( errno == EAGAIN )

&& e x e c u t i n g == 1) {

i f (FIFOEMPTY( f i f o _ i n p u t _ b u f f e r ) )

} i f ( e x e c u t i n g ) { i f ( ch == ESC) { set_break_on_next_instruction ( 1 ) ; } else { FIFOPUT( f i f o _ i n p u t _ b u f f e r , ( ( byte ) ch ) ) f i f o _ i n p u t _ b u f f e r . b u f f e r [ f i f o _ i n p u t _ b u f f e r . l a s t ]= ' \0 ' ; d e v i c e s _ b u f f e r [KEYBOARD_ADDR_CNT]=1; } } } reset_mode ( ) ; } } }

Con riferimento al codice sopra riportato, si supponga ad esempio che il si-mulatore si trovi in COMMAND MODE e attenda l'inserimento di un comando da parte dell'utente. La variabile globale executing, viene settata ad un valore unitario quando la funzione execute() viene eseguita e viene posta a zero ogni qual volta la stessa funzione è in procinto di uscire e ritornare alla user_control().

Come già accennato, le modalità di uscita di questa funzione sono essenzial-mente tre:

Istruzione di Halt;

Variabile single_step = 1;

Breakpoint sull'istruzione successiva;

In tutti e tre le occasioni, la variabile executing viene posta a zero. L'impor-tanza dell'introduzione di questa variabile verrà citata anche in occasione della discussione riguardante il thread del timer.

La variabile globale executing, quindi, è unitaria durante l'esecuzione di una o più istruzioni, mentre risulta nulla quando il simulatore si trova in COMMAND MODE. In questa congurazione (executing=0) è banale vericare che il thread della tastiera esegue il ciclo più esterno, senza mai entrare nel ciclo while interno. Qualora a questo punto l'utente esegua un comando che causa l'ingresso del usso di programma nella funzione execute() (che può avvenire con i comandi run forever, run next e next), la variabile executing viene portata ad 1 e il thread della tastiera entra nel secondo while indentato.

La prima funzione che si trova ad eseguire (set_raw_mode()) è quella che garantisce il corretta impostazione della nuova modalità di funzionamento del dispositivo di input standard: la tastiera. Una descrizione sommaria del funzio-namento di questa parte di codice può essere trovata in appendice B, con la sola dierenza che in questo caso è stata aggiunta l'istruzione

3.3. ANALISI DEL CODICE SORGENTE 49

che attribuisce la caratteristica non bloccante alla chiamata a sistema read() utilizzata in seguito. Per caratteristica non bloccante si intende che qualsiasi funzione di lettura dallo standard input (read(), fgetc() etc), non attende più la pressione di un tasto (e il caricamento del buer con almeno un carattere) per terminare, ma verica semplicemente se il buer suddetto non è vuoto; se lo è ritorna il valore -1 (e pone nella variabile globale errno il valore identicativo dell'errore EAGAIN), se non lo è, ritorna il numero di byte letti, svuota di un carattere il buer e pone in ch (secondo argomento della funzione read() passato by reference) il carattere letto.

int set_raw_mode ( void ) { int fd = STDIN_FILENO; struct termios t ; i f ( t c g e t a t t r ( fd , &t ) < 0) { p e r r o r ( " t c g e t a t t r " ) ; return −1; } old_attr = t ;

t . c _ l f l a g &= ~ICANON; /∗ no per l i n e b u f f e r i n g ∗/ t . c _ l f l a g &= ~ECHO; /∗ no s t d i n echo ∗/

f c n t l (STDIN_FILENO, F_SETFL, O_NONBLOCK) ; i f ( t c s e t a t t r ( fd , TCSANOW, &t ) < 0) { p e r r o r ( " t c s e t a t t r " ) ; return −1; } s e t b u f ( stdin , NULL) ; return 0 ; }

Viceversa, la funzione reset_mode(), ripristina gli attributi del dispositivo di standard input e riabilita come bloccanti le funzioni di lettura dallo stesso dispositivo (questo passaggio è fondamentale in quanto se rimanessero come non bloccanti, la funzione get_command() in user_control() ritornerebbe con un TOKEN_EXIT e causerebbe l'uscita dal simulatore).

int reset_mode ( void ) {

t c s e t a t t r (STDIN_FILENO, TCSANOW, &old_attr ) ; f c n t l (STDIN_FILENO, F_SETFL, ~O_NONBLOCK) ; return 0

}

Una volta impostata correttamente le modalità di lettura dallo standard input mediante l'opportuna funzione, il while successivo garantisce il graduale

riempi-mento del fo_input_buer.buer[] e si occupa di aggiornare il canale di controllo della tastiera.

In particolar modo, la seguente sezione di codice realizza l'attesa della lettura di un carattere e l'aggiornamento del canale di controllo della tastiera qualora nel frattempo un numero suciente di caratteri sia stato letto da un programma in esecuzione.

while ( ( read (STDIN_FILENO, &ch , 1)<=0 ) && ( errno == EAGAIN )

&& e x e c u t i n g == 1) {

i f (FIFOEMPTY( f i f o _ i n p u t _ b u f f e r ) )

d e v i c e s _ b u f f e r [KEYBOARD_ADDR_CNT]=0; }

La condizione del ciclo while, si può interpretare in questa maniera: qua-lora la lettura dal dispositivo standard di input non avvenga in quanto non vi è presente ancora nessun nuovo carattere (read(STDIN_FILENO, &ch, 1)<=0 && errno == EAGAIN ) e qualora il programma si trovi ancora in fase di ese-cuzione di istruzioni (&& executing==1 ) allora verica se il buer di ingresso è nullo o meno, se è nullo, azzera il canale di controllo della tastiera segnalando che non è disponibile nessun dato in ingresso. La condizione sulla variabile exe-cuting è necessaria in quanto le direttive di implementazione delle modalità di input di SimCPU richiedono che se avviene il passaggio da esecuzione a COM-MAND MODE (e quindi executing si porta a zero), il ciclo di lettura deve uscire indipendentemente dal fatto che un carattere sia stato letto o meno.

Qualora avvenga la lettura di un carattere (read() restituisce 1) e la variabile executing sia ancora unitaria, allora viene eseguita la parte di codice caratte-rizzata dal costrutto if. La prima operazione che viene eettuata è quella del controllo del carattere con il codice ASCII del tasto ESC (#dene ESC 0x1B). La pressione del tasto ESC, infatti, al pari di Control-C, deve causare il ritorno al COMMAND MODE da una situazione di run forever. In realtà, oltre a fornire un'alternativa al Control-C, l'utilizzo di ESC è decisamente consigliato.

Se il carattere letto non corrisponde a quello del tasto ESC, allora tale carat-tere deve venire posto nel buer di ingresso assieme al caratcarat-tere NULL di ter-minazione. A questa operazione è associata l'attivazione del canale di controllo della tastiera ad uno, per indicare che il buer di ingresso non è vuoto.

i f ( e x e c u t i n g ) { i f ( ch == ESC) { set_break_on_next_instruction ( 1 ) ; } else { FIFOPUT( f i f o _ i n p u t _ b u f f e r , ( ( byte ) ch ) )

3.3. ANALISI DEL CODICE SORGENTE 51

f i f o _ i n p u t _ b u f f e r . b u f f e r [ f i f o _ i n p u t _ b u f f e r . l a s t ]= ' \0 ' ; d e v i c e s _ b u f f e r [KEYBOARD_ADDR_CNT]=1;

}

Riprendendo in mano il codice completo del thread della tastiera, nel caso in cui durante l'attesa della pressione di un tasto la variabile executing venga posta a zero (in quanto la funzione executor() è nel frattempo ritornata alla funzione chiamante) si ha come conseguenza l'uscita dalla fase di lettura, l'uscita dai vari cicli while e l'esecuzione della funzione di reset_mode() no all'attesa ciclica che la variabile executing venga nuovamente posta ad 1.

Documenti correlati