I used a slightly different version of the same algorithm, though the underlying idea is the same.
Label the entry and exit point of each statement in your code:
-- void p(iflag, turn, id) {
-- l0: while(true) do
-- l1: flag := true
-- l2: while iflag do
-- l3: if turn != id
-- l4: flag := false;
-- l5: wait until turn = id;
-- l6: flag := true
-- l7: // Critical section
-- l8: turn := 1 - id
-- l9: flag := false;
-- }
Note that some statements, e.g. while iflag do
, might have more than one exit point depending on the value of the guard: if iflag
is true, then the exit point is l3, otherwise the exit point is l7.
Create a corresponding module that takes the same inputs and has a state variable which evaluates to the newly introduced labels.
MODULE p(iflag, turn, id)
VAR
state : { l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, error };
flag : boolean;
Here, notice that i added the special state error. This is only to ensure that during the definition the transition relation for state we are not leaving out any correct transition step. In general, it should be omitted as it does not belong to the original code.
Define the transition relation for state:
ASSIGN
init(state) := l0;
next(state) := case
state = l0 : l1;
state = l1 : l2;
state = l2 & iflag : l3;
state = l2 & !iflag : l7;
state = l3 & turn != id : l4;
state = l3 & turn = id : l2;
state = l4 : l5;
state = l5 & turn != id : l5;
state = l5 & turn = id : l6;
state = l6 : l2;
state = l7 : l8;
state = l8 : l9;
state = l9 : l0;
TRUE : error; -- if you match this then the above
-- description of transitions are incomplete
esac;
As you can see, I simply connected every entry point with the corresponding exit point. Moreover, wherever more than one exit point is defined for a given entry point I also added other state conditions to determine what line is executed next.
Add the transition relation for flag:
init(flag) := FALSE;
next(flag) := case
state = l1 | state = l6 : TRUE;
state = l4 | state = l9 : FALSE;
TRUE : flag;
esac;
Add some definitions to identify which sections of code the process is executing:
DEFINE
critical_section := (state = l7);
trying_section := (state = l1 | state = l2 | state = l3 |
state = l4 | state = l5 | state = l6);
Create a main module which creates two instances of p:
MODULE main ()
VAR
turn : {0, 1};
p1 : p(p2.flag, turn, 0);
p2 : p(p1.flag, turn, 1);
ASSIGN
init(turn) := 0;
next(turn) := case
p1.state = l8 : 1;
p2.state = l8 : 0;
TRUE : turn;
esac;
Add some very typical mutual exclusion properties to be verified by the model checker:
--*-- PROPERTIES --*--
-- Safety: two processes are never in the critical section at the same time
CTLSPEC AG !(p1.critical_section & p2.critical_section);
-- Liveness: if a process is in the trying section, then sooner or later
-- it will access the critical section.
CTLSPEC AG (p1.trying_section -> AF p1.critical_section);
-- Absence of Starvation
CTLSPEC ! EF AG (p1.trying_section & p2.trying_section);
-- Never in Error State
CTLSPEC AG !(p1.state = error);
Run the tool
~$ NuSMV -int
and check that all properties are verified:
NuSMV > reset; read_model -i dekker.smv; go; check_property
-- specification AG !(p1.critical_section & p2.critical_section) is true
-- specification AG (p1.trying_section -> AF p1.critical_section) is true
-- specification !(EF (AG (p1.trying_section & p2.trying_section))) is true
-- specification AG !(p1.state = error) is true