• Non ci sono risultati.

Traduzione del codice dei processi

3.2 Traduzione sistematica da LC a C++ con libreria di co-

3.2.4 Traduzione del codice dei processi

Il codice dei processi deve essere scritto nel linguaggio C++. Dal punto di vista delle comunicazioni, il codice applicativo scritto nel linguaggio LC e quello scritto in C++/MammuT differiscono comunque per la sintassi delle primitive di comunicazione e del comando alternativo.

Primitive di comunicazione

Presentiamo subito la firma delle primitive di comunicazione:

void C h a n n e l _ t :: send ( T * msg ); T * C h a n n e l _ t :: r e c e i v e ();

I messaggi inviati attraverso i canali di comunicazione devono essere me- morizzati in aree di memoria allineate, a seconda dell’implementazione selezionata, ai 16 o ai 128 bytes, di dimensione pari a un multiplo di 16 bytes. Le implementazioni che richiedono un allineamento del messaggio

3.2. Traduzione sistematica da LC a C++ con libreria di comunicazione

ai 16 bytes sono meno efficienti in termini di latenza della comunicazione rispetto alla stessa implementazione che invece richiede un’allineamento ai 128 bytes, ma risultano pi`u efficienti in termini di utilizzo di memo- ria. Le aree di memoria contenenti i messaggi da inviare devono essere allocate utilizzando due funzioni fornite dalla libreria:

( void *) m a l l o c _ d m a( unsigned i n t size ); ( void *) m a l l o c _ d m a _ 1 2 8 ( unsigned i n t size ); void f r e e _ d m a( void * area );

La prima restituisce un puntatore allineato ai 16 bytes, la seconda un puntatore allineato ai 128 bytes, mentre l’ultima libera un’area di me- moria allocata con la malloc dma o la malloc dma 128. Ad esempio, se si volesse inviare l’intero 10 attraverso il canale ch1, si pu`o scrivere

i n t * msg = ( i n t *) m a l l o c _ d m a( sizeof ( i n t )); * msg = 10;

ch1 - > send ( msg ); f r e e _ d m a( msg );

La receive restituisce un puntatore ad un area di memoria contenente il messaggio ricevuto. Si noti che il puntatore `e valido fino a che non si esegue un’altra receive. Nel caso sia necessario salvare il messaggio per un utilizzo posteriore alla receive successiva, la copia del puntatore non costituisce un metodo valido, sar`a invece necessario copiare il valore del messaggio.

Traduzione del comando alternativo

La traduzione del comando alternativo `e un’operazione pi`u complessa rispetto a quelle viste fino ad’ora. Come visto nella sezione precedente di questo capitolo, guardie e comando alternativo sono realizzati come classi, in particolare, per definire una guardia `e necessario definire una classe, il cui codice, ovviamente, dovr`a essere scritto esternamente rispet- to al codice applicativo. Analizziamo la sintassi del comando alternativo in LC: a l t e r n a t i v e { $ ( G1 ) do $ ( A1 ) ... $ ( Gn ) do $ ( An ) }

Una generica guardia $(Gi) ha la forma

mentre $(Ai) `e un blocco di codice racchiuso tra parentesi graffe. La traduzione del comando alternativo comporta innanzitutto la traduzio- ne di ogni guardia del costrutto. La traduzione di un generico gruppo $(Gi) $(Ai) comporta l’estensione della classe Guard act. La priorit`a della guardia tuttavia, dovr`a essere definita in un’altro contesto, che in- dicheremo in seguito. L’estensione della classe Guard act comporta la scrittura di tre metodi:

• Il costruttore della classe • Il metodo evaluate • Il metodo compute

Lo scheletro di una generica classe guardia estesa `e

c l a s s Guard : p u b l i c G u a r d _ a c t { $ ( CH_t ) * ch ; G e n e r i c _ a l t _ c m d * cmd ; $ ( EXTVAR ) p u b l i c : Guard ( $ ( CH_t ) * ch , G e n e r i c _ a l t _ c m d * cmd , $ ( T )** var , $ ( CTORP )) { this - > cmd = cmd ; this - > ch = ch ; $ ( CTORB ); } bool e v a l u a t e () { $ ( EVALB ) } i n t c o m p u t e () { $ ( C O M M U N I C A T I O N ) $ ( COMPB ) } }

Il metodo evaluate esegue il calcolo del predicato booleano della guardia (metavariabile $(EVALB)). In generale, il predicato booleano sar`a funzio- ne di un certo numero di variabili, che per le regole di scoping del C++ non sono visibili all’interno del metodo evaluate. Questo problema pu`o essere risolto estendendo la classe con delle variabili membro atte a con- tenere i riferimenti delle variabili da cui il predicato booleano dipende - le dichiarazioni di tali variabili membro sono identificate dalla metava- riabile $(EXTVAR). I riferimenti saranno copiati nel costruttore, che do- vr`a accettare come parametri detti riferimenti (metavariabile $(CTORP))

3.2. Traduzione sistematica da LC a C++ con libreria di comunicazione

e copiarli nelle apposite variabili membro (metavariabile $(CTORB)). In questo modo, dereferenziando i puntatori, saranno sempre disponibili i valori aggiornati delle variabili il cui riferimento `e stato copiato. Il corpo del metodo evaluate (metavariabile $(EVALB)) dovr`a quindi essere scrit- to in funzione delle variabili membro contenenti i riferimenti alle variabili esterne. Nel metodo compute, la metavariabile $(COMMUNICATION) iden- tifica la traduzione della primitiva di comunicazione. Una primitiva di comunicazione in LC ha la forma

send ( $ ( CHN ) , $ ( VAR ))

o la forma

r e c e i v e ( $ ( CHN ) , $ ( VAR ))

Il canale su cui eseguire la primitiva dovr`a essere passato come primo pa- rametro al costruttore (la metavariabile $(CH t) identifica il tipo di dato canale). Come `e stato gi`a affermato, le primitive di comunicazione messe a disposizione dalla libreria utilizzano i puntatori per i messaggi invia- ti e ricevuti. Un riferimento al puntatore utilizzato per la primitiva di comunicazione deve essere passato al costruttore come terzo parametro. La metavariabile $(T) identifica il tipo dei messaggi inviati attraverso il canale. La primitiva di comunicazione dovr`a essere scritta rispetto al canale ch. La metavariabile $(COMPB) identifica la traduzione dell’azione associata alla guardia $(Ai). Anche in questo caso vale il discorso fatto per il predicato booleano. I riferimenti alle variabili utilizzate in $(Ai) dovranno essere copiati in apposite variabili membro, e il codice dovr`a essere scritto utilizzando gli opportuni puntatori invece delle variabili esterne. Riportiamo lo scheletro di un generico processo concorrente.

# include < libcom .h > i n t main ()

{

MammuT :: Stcom * x = new MammuT :: Stcom (); $ ( GETV ) $ ( REGC2 ) $ ( D I C H I A R A Z I O N I ) $ ( ALTCMD ) $ ( CODE ) delete x ; }

Nella sezione identificata dalla metavariabile $(ALTCMD) andr`a inserito il codice relativo alla costruzione degli oggetti guardia e dell’oggetto co- mando alternativo, e il codice relativo alla procedura di registrazione delle guardie. Una volta creati gli oggetti, sar`a necessario utilizzare il meto-

do add entry v della classe Generic alt cmd per rendere disponibili le guardie all’oggetto comando alternativo. La firma del metodo `e

i n t G e n e r i c _ a l t _ c m d :: a d d _ e n t r y _ v ( G u a r d _ a c t * ga , i n t ch_num , C h a n n e l _ s p e * chs , i n t p r i o r i t y =0);

Il primo parametro indica la guardia da registrare, il secondo il nume- ro di canali su cui si effettua la primitiva di comunicazione (si indichi 1 nel nostro caso), il parametro chs indica il canale su cui deve essere eseguita la primitiva di comunicazione, e l’ultimo la priorit`a della guar- dia. L’oggetto comando alternativo deve essere creato prima degli oggetti guardia, e passato come secondo parametro ai costruttori delle guardie stessi. Questo `e necessario in quanto per poter cambiare le priorit`a del- le guardie `e necessario utilizzare il metodo set priority della classe Generic alt cmd, che ha questa firma:

void G e n e r i c _ a l t _ c m d :: s e t _ p r i o r i t y ( i n t guard_index , i n t p r i o r i t y );

Il parametro guard index rappresenta l’indice della guardia, l’ultimo la nuova priorit`a che deve essere assegnata. L’indice di una guardia `e dato dall’ordine di registrazione della stessa, assegnato progressivamente partendo da 0.