Pa��ra�ica su� i�guaggi� Pr��e�a
U� pri�� pr�gra��a i� Pr��e�a
active proctype P() {
int value = 123; /* prova con una variabile byte qui.... */
int reversed; // .....e anche qui
reversed = (value % 10) * 100 +
((value / 10) % 10) * 10 + (value / 100);
printf("value = %d, reversed = %d\n", value, reversed)
}
(All’interno del modulo è praticamente C)
I�i�ia�� ad uti�i��are �Spi�
� jSpin: ambiente integrato grafico per sviluppo di codice Promela e utilizzo di SPIN
� Editing simile ad altri ambienti di sviluppo codice� Per eseguire un programma promela selezionare
Random� Per eseguire una verifica sintattica selezionare Check� jSpin mostra tutti gli stati del programma dopo
l’esecuzione di ogni istruzione� per la prima esecuzione annulliamo questo settaggio:
Options/Common/Clear all/OK e quindi Options/Save per salvare il cambio di settaggio
I�sta��a�i��e di �Spi�
� Scaricare i files di jSpin e mingw.exe (compilatore gcc)� http://code.google.com/p/jspin/downloads/list
� lanciare installer di jSpin (windows) oppure compilare i sorgenti
� lanciare mingw.exe in modo da installare software in directory c:\mingw (è importante perchè jSpin lancia c:\mingw\bin\gcc.exe e sembra che non possa essere settato diversamente)
Tipi i� Pr��e�a (seque��ia�e)
� bit e bool entrambi per tipo boolean � per bit i valori sono 1 e 0� per bool sono true e false
� byte: valori 0..255, 8 bit� short: valori -32768..32767, 16 bit� int: valori -231..231-1, 32 bit� unsigned: valori 0..2n-1, max 32 bit (possiamo
usare uno specificato numero di bit n≤32) � es: unsigned x : 5 = 10; //x è un unsigned di 5 bit
�sserva�i��i
� Non c’è un tipo char, ma si può usare byte e stampare il carattere con printf e marcatore %c
� Le printf sono igorate quando si effettua verifica
� Manca un tipo floating point� Le variabili sono inizializzate a zero ma è
meglio dare un valore iniziale� valori iniziali possono condizionare la taglia del
modello SPIN
C��versi��i di tipi
� Tutti i tipi sono convertiti a int prima di un’operazione
� negli assegnamenti il valore dell’espressione è convertito al tipo della variabile� se il valore è fuori range viene troncato� se avviene troncamento viene visualizzato un
messaggio di errore che ne fornisce i dettagli� esecuzione non viene interrotta
�perat�ri e espressi��i
� Differenze con C: � assegnamento non è espressione � ++ e -- si usano solo in notazione post-fissa e solo
come istruzioni a sèAd es. a = b++; non è consentito
� In breve, nelle espressioni sono impediti costrutti che possono generare effetti collaterali
ista �perat�ri c�� pri�rit�
Dichiara�i��e variabi�i
� Variabili possono essere dichiarate ovunque (non necessariamente all’inizio)
� Dichiarazioni vengono implicitamente mosse all’inizio della definizione di un processoe hanno visibilità in tutto il processo� Ad es. byte a = 1;
.....a=5;byte b=a+2;printf(“b = %d\n”, b);
stampa “b = 3”!!
C�sta�ti si�b��iche
� Come in C si può usare #define� regola di sostituzione per il pre-processore� Es. #define N 20
� mtype: � definisce domini finiti specificando nomi� mappato su tipo byte: solo 256 costanti definibili� si può definire un unico dominio per un processo� per stampare mtype si usa printf con %e oppure
printm (preferibile, stampa anche in tracce di errore)
Ese�pi �type
mtype = { red, yellow, green }; mtype light = green;
active proctype P() {do:: if
:: light == red -> light = green:: light == yellow -> light = red:: light == green -> light = yellowfi;printf(“light = %e\n", light)
od}
mtype = { red, yellow, green };mtype = { greenYellow, yellowRed };
mtype light = green;
active proctype P() {do:: if
:: light == red -> light = yellowRed:: light == yellowRed -> light = green:: light == green -> light = greenYellow:: light == greenYellow -> light = redfi;printm(light)
od}
Strutture di c��tr����
� Ispirate a guarded commands di Dijkstra� più intuitivo quando non determinismo è consentito
� Istruzioni sono separate da “;”� location counter: mantiene l’indirizzo (control point) della
prossima istruzione che può essere eseguita� Promela ha 5 strutture di controllo:
sequenza, selezione, ripetizione, salto e unless� La sequenza è implicito: le istruzioni in un blocco sono
eseguite nell’ordine in cui sono scritte (in assenza di altrestrutture
� unless serve per modellare eccezioni
Strutture di c��tr����% se�e�i��e� Struttura:
if:: guardia 1 -> istruzioni 1......:: guardia n -> istruzioni nfi
� ogni riga “:: guardia -> istruzioni” è detta alternativa� esecuzione:
� vengono valutate le guardie� se almeno una delle guardie è vera, allora viene
eseguita la lista di istruzioni corrispondente ad unadelle guardie vere
Ese�pi�% discri�i�a�te equa�i��e
active proctype P() {int a = 1, b = -4, c = 4;int disc;disc = b * b - 4 * a * c;if:: disc < 0 -> printf("disc = %d: no real roots\n", disc):: disc == 0 -> printf("disc = %d: duplicate real roots\n", disc):: disc > 0 -> printf("disc = %d: two real roots\n", disc)fi
}
Guardie �e��’if)state�e�t
� Guardie sono espressioni condizionali� Si possono usare le costanti true e false
� true introduce un’alternativa che può essere sempre presa
� false introduce un’alternativa che non può mai essere presa (effetto analogo a non inserirla)
� Clausola else può essere usata come guardia di un’alternativa (l’ultima)� significa: “tutte le altre guardie sono false”� è la negazione logica della disgiunzione di tutte le
altre guardie (il complemento di tutte le altre guardie)
Ese�pi�% �u�er� di gi�r�i i� u� �ese
active proctype P() {byte days;byte month = 2;int year = 2000;if:: month == 1 || month == 3 || month == 5 || month == 7 ||
month == 8 || month == 10 || month == 12 -> days = 31
:: month == 4 || month == 6 || month == 9 || month == 11 ->days = 30
:: month == 2 && year % 4 == 0 -> days = (year % 100 != 0 || year % 400 == 0 -> 28 : 29)
fi;printf("month = %d, year = %d, days = %d\n", month, year, days)
}
Ese�pi�% �u�er� di gi�r�i i� u� �ese
active proctype P() {byte days;byte month = 2;int year = 2000;if:: month == 1 || month == 3 || month == 5 || month == 7 ||
month == 8 || month == 10 || month == 12 -> days = 31
:: month == 4 || month == 6 || month == 9 || month == 11 ->days = 30
:: month == 2 && year % 4 == 0 -> days = (year % 100 != 0 || year % 400 == 0 -> 28 : 29)
fi;printf("month = %d, year = %d, days = %d\n", month, year, days)
}
else -> //equivalente al precedente a patto// che month è compreso tra 1 e 12
Pi* guardie vere% ��� deter�i�is��
active proctype P() {int a = 5, b = 5;int max;int branch;if:: a >= b -> max = a; branch = 1;:: b >= a -> max = b; branch = 2;fi;printf("The maximum of %d and %d = %d by branch %d\n",
a, b, max, branch);}
U�teri�ri c��sidera�i��i
� Se tutte le guardie sono false l’esecuzionedel processo si blocca finchè una delleguardie diventa vera� potrebbe essere sbloccato da processo
concorrente� in programmazione sequenziale semplicemente
l’esecuzione si interrompe per timeout
� Istruzione nulla:� skip oppure true oppure 1 (tutte equivalenti)
� Nell’if-statement si può usare “;” invece di “->”
Espressi��i c��di�i��a�i
� Sintassi: (espressione -> parte if : parte else)(parentesi necessarie)
� Es. max=(a>b -> a : b) // calcola max(a,b)� Differenza con if-statement:
istruzione atomica� viene eseguita interamente senza interleaving con
altri processi� nell’if-statement una guardia e la corrispondente
lista di istruzioni possono essere interfogliate con istruzioni di altri processi
Strutture di c��tr����% ripeti�i��e
� Sintassi come if-statement solo le parole chiave sono do e od
� Semantica: 1. vengono valutate le guardie 2. se almeno una guardia è vera, viene eseguita la
lista di istruzioni corrispondente ad una guardia vera, altrimenti si blocca finchè una guardia non diventa vera
3. ripeti da 1. a meno che istruzione break è incontrata
Ese�pi�
active proctype P() {int x = 15, y = 20;int a, b;a = x; b = y;do:: a > b -> a = a - b:: b > a -> b = b - a:: a == b -> breakod;printf("The GCD of %d and %d = %d\n", x, y, a);
}
Cic�� f�r� Esiste solo do-statement, il ciclo for può essere
implementato come:
#define N 10
active proctype P() {int sum = 0;byte i = 1; do :: i > N -> break :: else -> sum = sum + i; i++od;printf("The sum of the first %d numbers = %d\n", N, sum);
}
Strutture di c��tr����% sa�t�
� Uso di goto con etichette� Es:
do :: i > N -> goto exitloop:: else -> ......odexitloop:
printf(....);
� Nota: break è da preferirsi per questi usi
�sserva�i��i
� Etichette sono usate per riferirsi ad un control point in un programma
� Non ci sono control point all’inizio di ogni alternativa in un if- o do-statement
� Esiste però un control point comune all’inizio
start: do:: wantP -> if
:: wantQ -> goto start:: else -> skipfi
:: else -> ........od
Eserci�i
� Implementare in Promela semplici programmi e sperimentare istruzioni con jSpin
Pr�cessi c��c�rre�ti i� Pr��e�a
� Ogni processo viene definito con la parola chiave proctype
� La parola chiave active indica a SPIN che il processo deve anche essere istanziato
� Le variabili condivise sono dichiarate all’esterno delle definizioni dei processi
� Uno stato è individuato dalle control location di ogni processo, il valore delle variabile globali e il valore delle variabili locali
� Istruzioni e espressioni vengono eseguite atomicamente� nel do e nell’if è possibile interleaving tra guardia e istruzioni di
un’alternativa
Se�p�ice pr�gra��a c��c�rre�te
� 6 computazioni possibili
� tutti i possibili interleaving di 2 processi con 2 istruzioni
� output dipende dall’ordine delle istruzioni
byte n = 0; // variabile condivisa
active proctype P() { // processo 1n = 1;printf("Process P, n = %d\n", n);
}
active proctype Q() { // processo 2n = 2;printf("Process Q, n = %d\n", n);
}
C��puta�i��i ese�pi� precede�te
Visua�i��a�i��e c��puta�i��i
� Quando SPIN simula un programma visita una delle possibili computazioni del programma
� In jSpin, dopo aver aperto Options/Common se si seleziona Set all e poi OK,il risultato della simulazione è scritto nel riquadro a destra mettendo in colonna:process ID, nome processo, numero di linea e istruzione eseguita, valore variabili
At��icit�
int d = 10, n=100, res=0;
active proctype P(){if
:: d > 0 -> assert(d>0); res = n/d:: else -> res = n
fi;printf ("Risultato: %d \n", res)
}
active proctype Q(){d=0
}
� asserzione può essere falsa� Q azzera d tra la
guardia e le istruzioni della prima alternativa
� Si può correggere rendendo atomica la prima alternativa
� Si mettono le istruzioni nel blocco d_step{.....}(oppure atomic)
B��cchi at��iciint d = 10, n=100, res=0;
active proctype P(){if
:: d_step {d > 0 -> assert(d>0); res = n/d}:: else -> res = n
fi;printf ("Risultato: %d \n", res)
}
active proctype Q(){d=0
}
L’asserzione ora è sempre vera
d.step vs. at��ic� entrambi ottengono l’esecuzione del blocco in maniera
atomica � le istruzioni del blocco non vengono interfogliate con quelle
di altri processi� è come se il blocco fosse un’unica istruzione
� d_step sta per deterministic step� viene usato per blocchi di codice sequenziale� le istruzioni della sequenza vengono eseguite/verificate in
maniera deterministica (non determinismo viene risolto sempre scegliendo la prima alternativa)
� atomic meglio per primitive di sincronizzazione� sul confronto ci torniamo quando parleremo di
sincronizzazioni
Si�u�a�i��e i�terattiva
� Spin supporta la simulazione interattiva
� Ad ogni punto della simulazione in cui più istruzioni sono possibili, l’utente sceglie la prossima instruzione da eseguire
� In jSpin, scelta è inserita pressando un pulsante grafico
0�de��� i�cre�e�t� i� registr�byte n = 0;
active proctype P() {byte temp;temp = n + 1;n = temp;printf("Process P, n = %d\n", n)
}
active proctype Q() {byte temp;temp = n + 1;n = temp;printf("Process Q, n = %d\n", n)
}
� programma modella :� load R1, n� add R1,#1� store R1,n
� add è un’operazioneinterna, non visibile� load e add
implementati come “temp=n+1”
� Esiste una computazioneper cui alla fine n=1 (perfect interleaving)
Perfect i�ter�eavi�g
I�sie�i di pr�cessi
� active [2] proctype P() {...} istanzia 2 procssi usando come template P(){...}
� _pid: variabile assegnata con un numero da 1 a 255 (process identifier)� 255 è il numero max di processi in Promela
byte n = 0;
active [2] proctype P() {byte temp;temp = n + 1;n = temp;printf("Process P%d, n = %d\n", _pid, n);
}
Pr�cess� i�it e �perat�re ru�� è il primo processo
istanziato (_pid =0)� i processi con template P
vengono istanziati con run� atomic serve a non far
iniziare l’esecuzione dei processi prima che tutti siano stati istanziati
� NOTA: � i parametri di proctype sono
separati da “;”� run è un operatore e restituisce
il numero del processo istanziato (restituisce 0 se già 255 processi sono istanziati)
byte n;
proctype P(byte id; byte incr) {byte temp;temp = n + incr;n = temp;printf("Proc. P%d, n = %d\n", id, n)
}
init {n = 1;atomic {
run P(1, 10); run P(2, 15)}
}
Ca�a�i i� Pr��e�a
Siste�i distribuiti� un sistema distribuito è costituito da un insieme di processi e un
insieme di canali di comunicazione� ogni processo rappresenta un nodo di computazione del sistema
distribuito e la rete di comunicazione è modellata dai canali� in Promela:
� un canale non è riservato solo ad una coppia di processi � un processo può inviare e ricevere messaggi sullo stesso
canale � Sintassi:
chan ch = [capacity] of {typename,…,typename}
� un array non è un typename lecito nella definizione di un canale, un typedef si
Ese�pi�
chan request = [0] of { byte };
active proctype Server() {
byte client;
end:
do
:: request ? client ->
printf("Client %d\n", client);
od
}
active proctype Client0() {
request ! 0;
}
active proctype Client1() {
request ! 1;
}
Us� ca�a�i
� Messaggio ha il tipo dichiarato nella dichiarazione� Es: chan c = [1] of {byte,byte}
su c possono essere inviati/ricevuti coppie di byte
� Primitive� send: invio messaggio su canale
Es: c! 3,10 � receive: ricezione messaggio su canale
Es: c? x,y (x,y variabili assegnate con le componenti del messaggio nell'ordine in cui sono scritte)
Us� ca�a�i
� una variabile di tipo chan può essere passata come parametro con inline e procType:� Es: proctype P(chan c) { c!5,7 }� Es: inline P(chan c) { c? x,y }
� canali possono essere dichiarati localmente ad un processo� cessano di esistere quando il processo termina
� canali possono essere di due tipologie:� rendez-vous: la capacità è 0
(non usano spazio per memorizzazione messaggi)� buffered: la capacità è >0
Ca�a�i re�de�)v�us mtype { red, yellow, green };
chan ch = [0] of { mtype, byte, bool };
active proctype Sender() {
ch ! red, 20, false;
printf("Sent message\n")
}
active proctype Receiver() {
mtype color; byte time; bool flash;
ch ? color, time, flash;
printf("Received message %e, %d, %d\n",
color, time, flash)
}
• eseguibilità a coppie (matching send/receive):un send è blocking per un processo fino a quando un matchingreceive può essere eseguito in un altro processo (e vice-versa)
• matching send/receive sono atomici
Rep�y cha��e�schan request = [0] of { byte };
chan reply = [0] of { bool };
active proctype Server() {
byte client;
end:
do
:: request ? client ->
printf("Client %d\n", client);
reply ! true
od
}
active proctype Client0() {
request ! 0;
reply ? _
}
active proctype Client1() {
request ! 1;
reply ? _
}
• simbolo "_" denota che non ci interessa il campo corrispondente del messaggio (anonymous variable)
• nei reply channels è importante solo informazione di ricezione messaggio non il messaggio stesso (acknowledgement)
Ese�pi� c�� due server
chan request = [0] of { byte };
chan reply = [0] of { byte, byte };
active [2] proctype Client() {
byte server, whichClient;
request ! _pid;
reply ? server, whichClient;
printf("Reply received from server
%d by client %d\n", server, _pid);
assert(whichClient == _pid)
}
active [2] proctype Server() {
byte client;
end:
do
:: request ? client ->
printf("Client %d processed by server
%d\n", client, _pid);
reply ! _pid, client;
od
}
• _pid usato per tracciare matching server/client nei rendez-vous• asserzione usata per verificare che client riceve effettivamente la
risposta dal server che ha dato seguito alla sua richiesta
Err�re
� Output generato da una traccia d'errore:� Client 0 processed by server 2� Client 1 processed by server 3� Reply received from server 2 by client 1� Reply received from server 3 by clent 0
� Possiamo correggerlo assegnando un reply channel differente ad ogni client� usiamo un array globale di chan,� oppure chan locali
S��u�i��e c�� array di cha�chan request = [0] of { byte, chan };
chan reply [2] = [0] of { byte, byte };
active [2] proctype Client() {
byte server;
byte whichClient;
request ! _pid, reply[_pid];
reply[_pid] ? server, whichClient;
printf("Reply received from server %d by
client %d\n", server, _pid);
assert(whichClient == _pid)
}
active [2] proctype Server() {
byte client;
chan replyChannel;
end:
do
:: request ? client, replyChannel ->
printf("Client %d processed by server
%d\n", client, _pid);
replyChannel ! _pid, client;
od
}
S��u�i��e c�� cha� ��ca�ichan request = [0] of { byte, chan };
active [2] proctype Client() {
byte server;
byte whichClient;
chan reply = [0] of { byte, byte };
request ! _pid, reply;
reply ? server, whichClient;
printf("Reply received from server %d by
client %d\n", server, _pid);
assert(whichClient == _pid)
}
active [2] proctype Server() {
byte client;
chan replyChannel;
end:
do
:: request ? client, replyChannel ->
printf("Client %d processed by server
%d\n", client, _pid);
replyChannel ! _pid, client;
od
}
Buffered cha��e�s
� Messaggi inviati sono memorizzati in una coda FIFO
mtype { red, yellow, green }; chan ch = [3] of { mtype, byte, bool };
Se�a�tica buffered cha��e�
� Invio e ricezione avvengono in due momenti differenti in una computazione
� coda dei messaggi di un canale entra a far parte dello stato del sistema
� send è blocking solo se canale pieno (coda piena)� possibile avere send non blocking (con perdita di messaggi in
caso di coda piena) in SPIN specificando parametro -m
Pri�itive per testare stat� buffered cha��e�� empty(chan): testa se canale è vuoto� nempty(chan): testa se canale non è vuoto
(! empty(chan) non è consentito)� full(chan): testa se canale è pieno� nfull(chan): testa se canale non è pieno� len(chan): restituisce numero di messaggi nel canale
� Importante:� non usare else in blocchi if-fi e do-od se si usano espressioni con
canali (usare invece le coppie empty/nempty e full/nfull)� preferire empty/nempty/full/nfull a len in quanto facilitano ottimizzazioni
di SPIN
Pr�gra��a c�� cha��e� expressi��s
chan request = [2] of { byte, chan};
chan reply[2] = [2] of { byte };
active [2] proctype Client() {
byte server;
do
:: full(request) ->
printf("Client %d w. for non-full chan\n", _pid)
:: request ! _pid, reply[_pid] ->
reply[_pid] ? server;
printf("Reply received from server %d by
client %d\n", server, _pid)
od
}
active [2] proctype Server() {
byte client;
chan replyChannel;
do
:: empty(request) ->
printf("No requests for server %d\n", _pid)
:: request ? client, replyChannel ->
printf("Client %d processed by server
%d\n", client, _pid);
replyChannel ! _pid
od
}
Ra�d�� receive
� ricezione messaggio non in posizione frontnella coda
� operatore di ricezione random (??)reply ?? server, 3
significa: ricevi il primo messaggio con valore 3 nel secondo campo e assegna il primo campo a variabile server� Se abbiamo bisogno di usare il valore di _pid,
per la ricezione random possiamo usare eval(_pid)
Pr�gra��a c�� ra�d�� receive
chan request = [4] of { byte };
chan reply = [4] of { byte, byte };
active [4] proctype Client() {
byte server;
request ! _pid;
reply ?? server, eval(_pid);
printf("Reply received from server %d by client
%d\n", server, _pid)
}
active [2] proctype Server() {
byte client;
end:
do
:: request ? client ->
printf("Client %d processed by server
%d\n", client, _pid);
reply ! _pid, client
od
}
I�vi� �rdi�at� (s�rted se�d)
� invio messaggio con posizionamento nella coda in ordine
� chan !! messageinserisce message nella posizione che precede il primo messaggio che lo segue nell'ordinamento
� ai fini dell'ordinamento i campi del messaggio sono visti come interi, e in caso di più campi si usa l'ordinamento lessicografico
Pr�gra��a che sta�pa tre �u�eri ge�erati ���deter�i�istica�e�te i� �rdi�e ��� decresce�te
chan ch = [3] of { byte };
inline getValue() {
if
:: n = 1
:: n = 2
:: n = 3
fi
}
active proctype Sort() {
byte n;
getValue(); ch !! n;
getValue(); ch !! n;
getValue(); ch !! n;
ch ? n; printf("%d\n", n);
ch ? n; printf("%d\n", n);
ch ? n; printf("%d\n", n)
}
A�tre �pera�i��i c�� ca�a�i� copia senza rimozione messaggio da coda:
� ch ? <color,time,flash>� ch ?? <color,time,flash>
� polling receive: verifica se un messaggio con determinate caratteristiche (valori) è nella coda senza copiarlo� ch ?? [green, time, false]� non ha effetti collaterali rispetto a receive con copia� adeguato per scrivere guardie in istruzioni if/do � si può usare in espressioni booleane
Buffered vs. re�de�)v�us cha��e�s
� rendez-vous channel:� non usa spazio aggiuntivo � sincronizzano processi (un processo resta in attesa di
un altro processo)� send e receive di uno messaggio eseguite
atomicamente come un'unica istruzione
� buffered channel:� contribuisce alla complessità dello spazio degli stati� maggiore versatilità d'uso� consente di modellare computazioni asincrone