1. Introduction
In his paper [
1], T.J. (Dirk) Dekker reveals the way in which he invented the first mutual exclusion algorithm for two processes. The solution was prepared, in a few days, in response to a Dijkstra well-stated problem, of which the proponent and others were very doubtful of its practical solvability, about how to orchestrate two processes (machines), using only software means, in such a way that they never access some shared data simultaneously. The code fragment for accessing/modifying shared data is called the process of
critical section. Dekker’s solution was formulated in 1960, and Dijkstra made it public in 1962 [
2,
3]. Dijkstra stressed the mutual exclusion solution had to fulfill the following additional properties: (a) absence of deadlocks among the competing processes; (b) absence of starvation which in turn means bounded waiting for a process willing to enter its critical section; (c) the solution should be independent from the relative speed of processes; and (d) read/write operations on shared variables should be indivisible.
The last requirement concerns the use of atomic registers. Nowadays [
4,
5,
6,
7], considering the use of multi-port memory, low-cost devices like cell phones [
8] and similar hardware, where multiple read/write operations on the same register can occur simultaneously, it is felt that the operation of a mutual exclusion algorithm should be correct also in the presence of non-atomic registers. Another requirement often added to the above specifications is that (d), a process not interested in the critical section, should not forbid another process to enter its critical section.
Dekker’s algorithm for two processes represents the fundamental solution on which many subsequent proposals were developed. Two specific variants of the basic Dekker’s algorithm are described in [
9,
10]. A mutual exclusion algorithm for
processes was designed by Dijkstra in [
11], although the author himself recognized the possibility for a competing process to suffer unbounded waiting. Better solutions for
processes were presented by Knuth [
12], de Bruijn [
13], and Eisenberg and McGuire [
14], with a guarantee of bounded waiting, e.g., linear in the number
of waiting processes. Peterson in [
15] proposed a more compact and elegant solution than the Dekker’s basic algorithm and a more general solution for
processes.
The correctness of many proposed mutual exclusion algorithms was mainly based on atomic registers and informal mathematical arguments. However, intuitive reasoning, except for simple cases, cannot predict all the possible interleaving of actions that can occur in a nondeterministic concurrent/parallel system. This is testified, for example, by the different indications of the overtaking factor for a competing process in Peterson’s solution for
processes (see [
16]). Formal modeling and verification approaches are now widely recognized as an essential tool for the assessment of the properties of a mutual exclusion solution. For example, the use of assertions and theorem provers was advocated in [
5,
10], whereas the use of model checkers with an exhaustive verification of the execution states of the processes of a concurrent system regulated by a mutual exclusion protocol was explored, e.g., in [
6,
7]. The work described in this paper relies on the use of Timed Automata (TA) [
17] in the context of the Uppaal model checker [
18,
19,
20] because process modeling and interactions are felt more intuitively when expressed graphically using TA instead of using, e.g., process algebra as in mCRL2 [
6,
21]. On the other hand, the Uppaal support of (a subset of) the Timed Computational Tree Logic (TCTL) [
18] is simple yet powerful for capturing and checking properties (queries) of a TA-based mutual exclusion model. A unique feature of our TA-based approach is the possibility of investigating the interaction between timed and concurrent aspects in a model.
The adopted approach was successfully exploited to check exhaustively, under atomic and non-atomic registers, the properties of Dekker’s basic algorithm and some of its variants in [
10,
22], also when the solution for two processes is used as the arbitration unit in a general and standard tournament binary tree (TT) organization [
16,
23,
24]. The approach was also successfully applied to prove properties of several algorithms for
[
25,
26]. Three specific techniques are considered and compared in [
7] for modeling non-atomic registers.
In a recent paper [
1], Dekker proposed a generalization of his basic algorithm for two processes to cover
processes in a fair way. The new algorithm will be referred to as Dekker-N. To the best of our knowledge, Dekker-N was only studied by the author using informal reasoning and atomic registers. An original contribution of this paper is a formal specification of Dekker-N by TA and its thorough model checking when atomic registers or non-atomic registers are used. This paper demonstrates that the Dekker-N is effectively a correct algorithm under atomic registers. It fulfills all the Dijkstra stated mutual exclusion requirements and guarantees fair behavior of processes through a linear overtaking factor. The paper also shows, though, that Dekker-N becomes incorrect when not-atomic registers are used. A specific contribution of this paper is to prove that through fencing. Specifically, making a single shared communication variable safe under read/write, Dekker-N becomes correct and fair also under non-atomic registers.
The paper is structured in two parts. In the first part (
Section 2), the abstract structure of a mutual exclusion protocol and the modeling approach based on Uppaal are presented. The Dekker-2 algorithm is used as a working example. The modeling issues based on atomic and non-atomic registers are discussed. After that, the verification of all the properties of a mutual exclusion model using the Uppaal model checker with a few Timed Computational Tree Logic (TCTL) queries is detailed. In the second part (
Section 3), Dekker’s algorithm for
is thoroughly investigated. First, a Uppaal model is derived under the hypothesis of atomic registers, and its properties are verified. Then, a model for Dekker-N with non-atomic registers is presented and deeply model checked. It is shown that Dekker-N is correct for two processes with non-atomic registers, but it is incorrect for
. The effectiveness of the Uppaal-based approach is then demonstrated by showing how the general Dekker-N can be made correct for any number of processes and non-atomic registers. Finally, conclusions are drawn with an indication of future work.
2. Modeling a Mutual Exclusion Algorithm
The basic steps of a process involved in a mutual exclusion competition concerning the access to a common shared data resource
, is depicted in Algorithm 1. The process, with unique identifier
in
, obeys a protocol that regulates the enter/exit of
to/from its local critical section. This is the code of process
that accesses/modifies
. The entry/exit parts of the protocol depend on some (hopefully few)
shared communication variables. In an important case, a shared variable can be an
exterior [
23] or
output [
10] variable. Its write operation is the responsibility of only one process, but its value can be read, also concurrently, by all the remaining processes. In the more general case, a shared communication variable can be written/read by any process. In addition, multiple read/write accesses can occur simultaneously.
Algorithm 1. Abstract structure of a process involved in mutual exclusion. |
shared communication variables Process(i) local variables repeat NCS protocol-entry-part CS Protocol-exit-part forever |
In Algorithm 1, NCS denotes the non-critical section. A process in its NCS is not using the resource ; it is also not interested in entering its critical section. In NCS, a process can remain an arbitrary time or an infinite time to model process termination. CS indicates the critical section of a process. The protocol is played in the entry/exit sections, where the shared communication variables are operated. In the entry part, a process is competing with peers for entering its critical section. If the process cannot proceed, it is assumed that it enters a busy waiting state, where it actively awaits for the shared communication variables to change, thus favoring the process. Depending on the mutual exclusion algorithm, some busy wait states could also be required in the exit part of the protocol.
2.1. Dekker’s Algorithm for Two Processes
As a concrete example, Algorithm 2 reproduces in pseudo-code Dekker’s algorithm for N = 2 processes [
1,
22], here referred to as Dekker-2. The algorithm is based on the following
output shared communication variables:
bool flag[1..2] //all false initially
int[1..2] turn //immaterial is the initial value
There is one
per process. The variable
can be written by one process and read by the partner. A process communicates its willingness to enter its critical section through its Boolean
. However, a competing process (engaged in the entry part) gets the grant to enter the critical section provided its turn arrived, that is, its id is assigned to
. The process that achieved the permission to enter its critical section will be assigned to
the identity of the partner process on exiting from the critical section. Process
,
, introduces a local constant
to refer to the partner process. Busy waiting is expressed by the instruction:
This is equivalent to the following cycle:
. In other terms, process
spin-locks until the variable
contains
as its value.
Algorithm 2. Dekker-2 mutual exclusion algorithm. |
Process(i): Local constant: pid j = 3 − i; //j is partner process repeat NCS; //protocol-entry-part flag[i] = true; //request to enter while( flag[j] ){ //in competition if( turn == j ){ //not my turn flag[i] = false; //reset my request await( turn != j ); //busy-waiting until the turn is mine flag[i] = true; //rise my request again } } //competition end: the partner has no interest in CS and the turn is mine CS; //protocol-exit-part turn = j; //turn assigned to partner flag[i] = false; //reset my request forever |
The correct operation of Dekker-2 can be checked intuitively using Algorithm 2 with the help of comments. However, for a complete assessment of Dekker-2, our approach is to, preliminarily, reduce Algorithm 2 into the Timed Automata (TA) formalism of Uppaal [
18,
19,
20].
2.2. Reducing Dekker-2 on to Uppaal
Figure 1 (see also [
22]) shows the Uppaal model [
18,
19,
20] of
when atomic registers are used.
is a parameterized template model (automaton) with one single parameter:
; where
is a sub-range type for the integers from 1 to N:
const int N = 2; //number of processes
typedef int[1, N] pid;
At the system configuration time, two instances of Process will automatically be created and denoted as Process(1), and Process(2), which share global declarations for types and variables. The primitive types int, bool, and clock; arrays data structures of these types and channels; and an array of channels are available. Each Process instance owns its local declarations.
Shared communication variables are introduced in the global environment of the Uppaal model.
A process instance is a state machine composed of
locations (circle in
Figure 1) and
edges (arrows in
Figure 1) that connect locations. Each edge hosts a
guarded command that is made up of three optional attributes: (1) a
guard (Boolean expression, true by default if it is missing, green colored), (2) a channel
synchronization (azure colored), and (3) an
update part (blue colored) that is an ordered list of assignments to variables and clock resets. The guarded command is the
atomic unit of concurrency in Uppaal models. Like in Petri nets [
27], an enabled command
can be taken, but it is not forced to do it. Different locations can be used to provide the time behavior to a model. Time is usually expressed using clock variables (see the clock
in
Figure 1). Clocks can be reset in an update section of a command. Then, they all grow at the same rate as the global time of a model. They measure the
relative time elapsed from their last reset. In a normal location (see NCS in
Figure 1), the automaton can stay an arbitrary, possibly infinite, time. The dwell time of a normal location can be constrained by the use of an
invariant (see the invariant
attached to CS in
Figure 1). An urgent location (flagged with an internal U) must be abandoned immediately, without any time passage. Another kind of location to be exited without time passage is the committed one [
18], which is similar to an urgent location. However, committed locations have priority and are guaranteed to be abandoned before urgent locations.
Interactions between TA can be based on unicast or broadcast channels. A unicast channel can have a sender that executes the send signal operation (symbol
) and a receiver that can accept the synchronization signal (symbol
) sent by a sender. Unicast channels obey to
rendezvous. Only when both sender and receiver are ready to communicates, a synchronization can be taken. The first process that arrives to the synchronization point has to wait for the partner to arrive. Broadcast channels have one sender and zero, one, or multiple receivers. The sender of a broadcast channel never waits for receivers to be ready. This way, such kind of channels are useful for one-way, asynchronous interactions.
Figure 1 uses one single broadcast channel
that purposely has one sender and no receiver. Channels can be declared to be urgent (e.g., the
channel in
Figure 1). In this case, an enabled synchronization must be taken without time passage. Among the actions that can be executed at the same time, the order of execution is nondeterministic. For example, it is nondeterministic when exiting from the location BW in
Figure 1 with
(
being urgent), especially when this occurs simultaneously from an urgent location in the partner model.
2.3. Semantic Aspects of a Reduced Uppaal Model
A fundamental concern of a mutual exclusion algorithm like Dekker-2 reduced in Uppaal as shown in
Figure 1 is the proper reproduction of nondeterminism and action interleaving of concurrent/parallel processes. From this point of view, process actions are purposely embedded in the guarded commands of the exiting edges of urgent locations. Therefore, the duration of atomic actions is zero time units.
Regardless of the memory model, no more than one shared communication variable can be accessed in a single command. This constraint does not apply to local variables. Multiple local variables, supposed to be held in CPU registers, can be operated together in the same command without problems.
A reduced model, such as Dekker-2 in
Figure 1, is both concurrent and time sensitive. Time is allowed to pass in the NCS, CS, and busy waiting locations like BW. Exiting from NCS (which is also the initial location) is modelled by a spontaneous edge. This way a process can spend an arbitrary time in doing actions unrelated to the critical section. An infinite time in NCS models process termination. It has to be verified that a process in NCS should never forbid other processes from entering their critical section.
Busy waiting can be modelled by a normal location as BW in
Figure 1 or by a looped urgent location like L. The former solution is preferable from the model checker point of view, because it reduces the nondeterminism (or partial order) degree of the nodes of the state graph [
18,
19]. When the awaited condition holds (
), the BW is abandoned without time passage through the urgent synchronization
. More generally, exiting from a busy waiting location can be guarded by a
function that, optimistically, checks a complex condition based on multiple shared communication variables. In this case, the exiting is immediately followed by the evaluation, step-by-step and non-deterministically, of the compound condition. At any step that would contradict the exiting from the busy waiting location, the latter is immediately re-entered. The second solution with L urgent generates a zeno-cycle [
22,
28] in the model (logically present also in Algorithm 1), which is an infinite series of returns in L in zero time. However, in a concrete implementation of Algorithm 2, actions could not have a zero duration, and the zeno-cycle in the model has the effect of compromising model liveness. It is possible, virtually, that a process in L never will reach the CS. However, the analysis of the model in the time domain will confirm the full correctness of Dekker-2.
Timing is also used in the CS (critical section) location. First of all, the particular duration of the dwell time of a process in CS does not matter. During a critical section, concurrent competing processes can execute as many atomic and instantaneous actions of the mutual exclusion protocol as they want, or they can wait in a busy waiting location like BW. The adopted Uppaal approach purposely assumes a critical section consumes one time unit. This way, by using a clock variable per process (see the global array
in
Figure 1, which has one clock for each process instance), which gets reset just before entering a CS, it becomes possible to assess the overtaking bound, that is the number of times a competing process is by-passed by competing partners, by checking the maximum value of the clock of a process when it finds itself in L (a location immediately preceding CS).
In the case of an unfair mutual exclusion algorithm, and the continual arrival of competing processes (e.g., the NCS duration is always 0 time units), the overtaking factor can become infinite. For an (almost) fair algorithm, even under the worst case arrivals of processes, the waiting time in L for any competing process is finite, and the overtaking factor bounded. As a consequence, the particular duration of NCS, which occurs in parallel with the critical sections, does not affect the overtaking bound. To check specifically the overtaking bound, an arbitrary target process (
) can be defined in the model global declarations, and its clock is reset (through the
function in
Figure 1) when it exits from the NCS and starts competing.
2.4. TCTL Verification Queries of Dekker-2
A model like Dekker-2 of
Figure 1 can preliminarily be studied (debugging) using the Uppaal symbolic simulator [
20], which permits one to observe graphically the evolution of the process instances and the corresponding values of the global and local data declarations, when selected or randomly chosen transitions occur. The simulator can reveal some bad properties. For example, a deadlocked state can be reached, but one cannot conclude the model is correct. Model correctness can only be determined by exhaustive verification based on the preliminary creation of the (hopefully finite) model
state graph [
18,
19]. The state graph is a timed transition system, where nodes represent the possible execution states of the model, and the arcs denote the possible timed or instantaneous transitions between states. Each node contains a
data part and a
time constraint part. The data part depends on the sizes and the values of the global and local data declarations. The time constraint part is a clock inequality system that describes all the possible time instants at which the state node can be reached. Proving properties of a model rests on the model checker that systematically traverses all the execution paths in the state graph, which is strongly influenced by the nondeterminism degree of nodes, that is, the number of possible exiting transitions at each node.
Property checking of the Dekker-2 model can be practically accomplished by issuing TCTL queries [
18] as noted in the following. First of all, Dekker-2 makes no hypothesis on the relative speed of processes.
1. Mutual exclusion. At most one process at a time can enter its critical section. The following query can be used.
A[] (sum(i:pid)Process(i).CS) <= 1
This asks if it is always true (or invariantly) that in all the states of the state graph the number of processes simultaneously found in their CS location is less than or equal to 1.
Dekker-2 satisfies the mutual exclusion property.
2. Absence of deadlocks. One of the following TCTL queries can be issued.
A[] !deadlock
E<> deadlock
The first query asks the model checker to assess if invariantly that there is no deadlocked state in the state graph. The second query demands to verify if there exists at least one deadlocked state in the state graph. The absence of deadlocks is confirmed by the first query that gets satisfied, or, equivalently, by the second query that terminates unsatisfied.
Dekker-2 has no deadlock.
3.
Absence of starvation. A competing process should always wait for a bounded time before achieving the permission to enter its critical section. The following
suprema query [
21] can be issued.
sup{ Process(tp).L } : x[tp]
This query asks to determine the maximum value of the clock x[tp] when the process tp is found in the L location (see
Figure 1) that immediately precedes CS.
Dekker-2 guarantees 1 is the maximum waiting time for a competing process (overtaking bound), thus confirming the algorithm is fair.
4. Liveness 1. Every process eventually enters its critical section. For Dekker-2 the following queries can be used:
E<> Process(1).CS
E<> Process(2).CS
Both queries are satisfied, thus giving more arguments to the mutual exclusion property and to the absence of starvation.
5. Liveness 2. A process in its NCS should not forbid other processes to enter the critical section. The following query can be used.
E<> exists(i:pid)Process(i).NCS && exists(j:pid)Process(j).CS
This query asks if there exists at least one state where some process is in its NCS whereas some other process , , is in its CS.
Based on the responses to the above-described TCTL queries, Dekker-2 was found a correct mutual exclusion algorithm when atomic registers are used. All the experiments are carried out on a Win11 Pro desktop platform with Dell XPS 8940, Intel i7-10700 (eight physical + eight virtual cores), CPU@2.90 GHz, and 32 GB RAM.
2.5. Extending the Dekker-2 Model to Non-Atomic Registers
Due to the widespread diffusion of multi-port memory, low-cost devices like phone-cells and similar hardware equipment [
8,
29], nowadays, a mutual exclusion algorithm is required to behave correctly without guaranteeing that the read and write operations on the same register are always atomic or indivisible. The problem is then to check the correctness of an algorithm in the presence of non-atomic registers, too.
According to [
4,
6], a first scenario/pattern is the so called Single Writer Single Reader (SWSR), where a read operation can co-occur with a write operation on the same register. SWSR can affect the behavior of Dekker-2, because of the use of output-only shared communication variables, namely, that a variable is only written by one process and read by the partner. Reading a variable during a write gives rise to the
flickering phenomenon [
4,
6,
10]. Specifically, the reader achieves a nondeterministic value of the variable, belonging to the variable type domain.
The SWSR pattern generalizes to the Single Writer Multiple Reader (SWMR) case when, as in the Dekker-N algorithm studied later in this paper, shared registers are still output variables, and a single writer can be in access of a register simultaneously to multiple reader processes. In this case, each concurrent reader can receive a possibly different nondeterministic value chosen in the type domain of the register.
In the worst case Multiple Writer Multiple Reader (MWMR) scenario, simultaneous write operations on the same non-output variable determines the
scrambled register value [
4,
6,
10]. Reading a variable affected by scrambling returns a nondeterministic value in the type domain of the variable.
A mutual exclusion algorithm is said to be W-Safe [
10] if it is immune to the possible flickering effects of the shared registers. The algorithm is called RW-Safe [
10] when it is robust concerning both flickering and scrambling.
In a recent paper [
7], all the above-introduced patterns of non-atomic registers are modelled in Uppaal using three techniques: the
Simple, the
Loop, and the
Decoration techniques. In the Simple case, the writer first assigns the variable a nondeterministic value; then, this is followed by the assignment of the effective value. This way a read can effectively return the previous value, the flickered value, or the new value of the variable. In the Loop technique [
6,
10], the writer first executes a loop, which eventually terminates. At each iteration, a nondeterministic value is temporarily assigned to the variable. At the loop termination, the effective value is assigned to the variable. A reader can then achieve the previous value, any flickered value, or the final assigned value to the variable. The Decoration technique decorates the model by adding Boolean variables
, one for each shared communication variable
, which indicate whether
is under writing or not. The writer first sets the decoration variable to true, then it assigns the effective value to the variable. In the next action, the decoration value is reset to false. Depending on the values of
variables, a reader can access the last assigned value to the variable or a nondeterministic (flickered or scrambled) value.
The three techniques are equivalent in practical use, although they have different repercussions on the state graph and the model checker. The Simple technique was used, e.g., in [
16,
22]. The Decoration technique is more efficient. The decoration variables remain almost always at their default false value, except, temporarily, when a write operation is accomplished. In the rest of this paper, Decoration will be used for studying models under non-atomic registers.
Figure 2 shows a modification of the Dekker-2 model according to the Decoration technique and the SWSR pattern of use of non-atomic registers. Three global variables were added, namely,
and
, which are associated with the
and the
variables, respectively. When checking a shared communication variable, if it is under writing, a nondeterministic value is returned. Otherwise, the true value of the variable is used. For brevity, the int values of Boolean, 0 for false and 1 for true, are used. Decorated edges like the one from L to CS exploit the nondeterministic field (yellow colored in
Figure 2) of a guarded command, which assigns to variable
, strictly local to the command, a nondeterministic value selected in the type of the shared variable, e.g., the
interval [0, 1] for a
variable. The meaning of the command “
” is the following. In the case the variable
is not under writing (
is false), the last value of
is checked for false. Otherwise, (
is true), the non-deterministic value
is used for the test.
The Dekker-2 model of
Figure 2 was thoroughly verified using the Uppaal model checker. It proved to be W-Safe and fully correct from all the mutual exclusion queries discussed in
Section 2.4. This version too is fair and guarantees 1 is the overtaking bound.
It is worth noting that a decorated model like that in
Figure 2 is more expensive for model checking than the version for atomic registers because of the augmented degree of the nondeterminism in the model.
3. Dekker’s Mutual Exclusion for Any Number of Processes
In [
1], Dekker proposed a fair mutual exclusion algorithm for
processes. Fairness is based on a linked list of precedence. After completing its critical section, the process places itself at the bottom of the list, and lower ranking competing processes are moved one place up in the list. Since the list is consulted concurrently by processes, movements in the list are tricky. In particular, moves are carried out in such a way that all processes in the list continue to be visible to all the other processes. The process is placed at the bottom and temporarily occurs twice in the list. Due to this operation, the bottom is not used as the list end. The penultimate element is effectively the last element, and the bottom is the next element to it.
The algorithm uses the following shared communication variables with the shown initialization:
int top = N, penult = 2;
int[0, 2] c[1..N] //all 0 initially
int[0, N] next[1..N]; //for all j in [1..N], next[j] = j − 1
The array is analogous to the array of Dekker-2. However, instead of the two Boolean values 0 (false) and 1 (true), three values are now used to express the status of a process . When c[]=0, the process is not interested in the critical section. The value c[] = 1 denotes the process that starts competing (ready status). Finally, when c[] = 2, the process is up to enter its critical section.
All the communication variables are output or behave as output variables. Each variable is associated with a distinct process that has the responsibility of writing its status information in it. The process can change the , , and variables at its abandoning of the critical section. All the variables, though, can be checked concurrently by all the processes.
Algorithm 3 reproduces in pseudo-code Dekker’s algorithm for processes (Dekker-N). A competing process acquires permission to enter its critical section when predecessor processes, from the top of the list up to the one immediately before , are found in the status of being not interested in the critical section (). Such a situation must be assessed twice: with and then with . After that, the process must wait until posterior processes (from to penultimate) are at most in the ready status ().
On exiting from its critical section, the process is placed at the bottom of the list, provided is not already the bottom. If required, process becomes the next , and the previous is established as the penultimate of the list. After that, if the process was the , the variable is updated to the next element of . Otherwise, the previous will have the next as the next element. Finally, process sets its status to not interested in the critical section (), and the NCS is re-entered.
Dekker-N will be first reduced to Uppaal under atomic registers, and its properties, including fairness, will be investigated. Then, the algorithm will be adapted to work with non-atomic registers, and its correctness will be deeply verified.
Algorithm 3. Pseudo-code of Dekker-N. |
Process(i): local variables: int pre, previous, post, pos, subtop, bottom; repeat NCS; w0: c[i] = 1; w1: pre = top; in1: if( pre !=i ){ if( c[pre] > 0 ) goto w1; pre = next[pre]; goto in1 } // all c[pre] == 0 c[i] = 2; //same loop with c[i] = 2 pre = top; in2: if( pre !=i ){ if( c[pre] > 0 ) goto w0; previous := pre; pre := next[pre]; goto in2; } // c[i] = 2, all c[pre] = 0 //c[i] = 2, preceding processes not interested //now wait until posterior processes are ready if( i == top || previous != penult ){ w2: post = i; in3: pos = post; post = next[post]; if( c[post] == 2 ) then goto w2; if( pos != penult ) goto in3; } //c[i] = 2, all c[pre] = 0, all c[post] < 2 CS; //place process i at bottom bottom = next[penult]; if( i != bottom ){ next[bottom] = i; penult = bottom; subtop = next[i]; if( i == top ) top = subtop; else next[previous] = subtop; } c[i] = 0; forever |
3.1. Transforming Dekker-N into Uppaal by Using Atomic Registers
The following are the basic global declarations of the Dekker-N Uppaal model:
const int N = 4; //example
typedef int[1, N] pid;
typedef int[0, 2] value;
//shared communication variables
pid top = N, penult = 2;
value c[pid]; //all zero by default
pid next[pid]; //all 1 by default
urgent broadcast chan synch;
clock x[pid];
As one can see, the
list was changed, with respect to [
1], to have only process indexes in [1, N] as values. In fact, the
value is immaterial and can be assigned itself as the next value. This provision reduces the number of possible values of
, which in turn tends to improve the burden of the model checker.
The Uppaal model is organized in two timed automata: and .
(see
Figure 3) is a parameterless automaton that has the responsibility of model initialization. Its initial location is purposely declared as committed. This way its exiting edge will certainly be taken
before any other edge in the
models. Recall that
locations (see, e.g.,
Figure 1) are normal or urgent locations. The function initialize() is detailed in
Figure 4. The
, after the edge with initialize(), enters an anonymous normal location from which it will take no part in the subsequent model evolution.
Figure 5 shows the
automaton equipped with the sole parameter:
. Atomic registers are automatically ensured by the atomic actions of Uppaal TA-based modeling language. Atomic actions are executed one at a time and in any order. Consequently, considering that only one communication variable can be accessed/modified in a guarded command, only one write or read operation can occur at any time.
A notable point in
Figure 5 is the introduction of the busy waiting BW location that precedes the first loop on the predecessor processes when
. This provision was made to reduce the partial order degree in the state graph that inevitably would accompany the continuous checking, in the model, of communication variables by chained urgent locations. From BW, the
automaton tentatively exits when there is a chance all the predecessor processes of
are in the not interested status:
. Toward this, the try() function of
Figure 6 was prepared, which optimistically checks the exiting condition from BW. Optimism is related to the fact that multiple shared communication variables are simultaneously checked. However, model concurrency and nondeterminism are correctly ensured given that
, after abandoning the BW location through the synchronization on the urgent and broadcast
channel, evaluates one-at-a-time the single variables of the try() loop, and it immediately re-enters BW if any predecessor is found in the ready status (
).
In
Figure 5, it is worth noting the provision to reset as soon as possible local variables after their use. This action can improve model checking by ensuring variables return to their default status.
The system configuration of the Dekker-N model is achieved by the following declaration:
system Bootstrapper, Process;
which automatically creates one instance of the and instances of the automaton indexed by the integers in [1, N].
3.2. Model Checking the Dekker-N Model with Atomic Registers
The model in
Figure 5 was exhaustively verified by separately using NCS as a normal location (with arbitrary stay time) and as an urgent location (zero stay time). The use of NCS as normal location enables us to check that the algorithm correctly behaves when processes possibly terminate in NCS or re-enter the system after an arbitrary time. The use of NCS urgent ensures the worst case arrival of ready processes. Making NCS urgent favors the model checker because it eliminates a source of time behavior.
Figure 7 reports a snapshot of the Uppaal verifier when NCS is normal and N is set to three processes. As one can see from the green circle generated in response to each query, the model was found correct from all the mutual exclusion queries. In addition, the overtaking bound (
) emerged to be
.
The model of
Figure 5 was verified for N ranging in [2..4]. The same green answers as in
Figure 7 were found for each value of N.
Table 1 collects the observed overtaking bound
(see last query in
Figure 7), the elapsed time (ET) in sec, and the peak of memory usage (M) of the model checker. For
, the model with NCS normal suffers from state explosions in the state graph, and the verifier was stopped as soon as the virtual memory exceeded the available RAM (32 GB).
The Dekker-N model becomes more scalable when NCS is made urgent. The model was verified for
in [2..5]. For N = 6, the model is still affected by state explosions. All the queries, as shown in
Figure 7, achieved the same responses as in
Figure 7.
Table 2 reports the overtaking bound results in the new scenario.
From
Table 1 and
Table 2, it emerges that Dekker-N under atomic registers is fair with an overtaking bound
, that almost coincides with the
indication of informal analysis in [
1].
3.3. The Dekker-N Model with Non-Atomic Registers
Figure 8 shows Dekker-N model decorated for using non-atomic registers. The following global declarations were added:
//decoration variables—all false initially
bool wt, //true when writing to top
wp; //true when writing to penult
bool wc[pid], //true when writing to c[.],
wn[pid]; //true when writing to next[.]
The existence of only output shared communication variables implies that the model in
Figure 8 takes care only of variable flickering due to the SWMR pattern. The model in
Figure 8 was preliminarily debugged using the symbolic simulator of Uppaal. It emerged that due to flickering, the contents of the
list can temporarily become cyclic; thus, the try() function can enter an infinite loop during the evaluation of predecessor processes. As a consequence, the try() function was modified according to
Figure 9. A
array was introduced, and a possible loop in the
list was discovered. Under these circumstances, try() terminates by returning false. After that, the model was verified with the model checker by first assuming NCS is a normal location. For
, the model of
Figure 8 was found to be W-Safe and fully correct. The result complies with the author’s expectation stated in [
1] that the mutual exclusion solution, when reduced to operate with two processes, would be equivalent to Dekker’s algorithm for two processes (see
Section 2.2 and
Section 2.5). The experimental results confirm that the property holds both with atomic and non-atomic registers.
The next verification step was to check the model in
Figure 8 for
. Unfortunately, already for
, the decorated model loses the fundamental mutual exclusion property because more than one process can enter simultaneously the critical section (see
Figure 10). In addition, the query that checks for the overtaking bound becomes inconclusive.
In the light of the observed results, the Dekker-N model was found to be incorrect for
when non-atomic registers are used. The model was then studied by making some changes (see
Figure 11). In particular, considering the role played by the
common variable, the provision of fencing
was added, thus ensuring it behaves as a regular atomic variable. Reading
will always return either the previous or the newly assigned value, but never a flickered value. Fencing is supposed to be supported by some low-level hardware mechanism that controls the access to the
variable and enforces that read/write operations on
are indivisible. In
Figure 11, the
variable was eliminated, and any read to
returns the last value stored in the variable. With these changes, Dekker-N becomes correct also with non-atomic registers. However, the adapted model suffers from state explosions when
.
The verification process was then continued by turning NCS into an urgent location. Correct behavior, that is, satisfaction of all the mutual exclusion properties, including fairness, was then observed until .
The verification work was finally directed at further checking the behavior of the Dekker-N model with non-atomic registers when processes that exit the critical section immediately re-enter the system and are newly ready. In other terms, the NCS location was always kept urgent. An unexpected remarkable result emerged that the model in
Figure 8, without fencing the
variable and with the try() function used with atomic registers (see
Figure 6), turns out to be W-Safe and fully correct and has the same overtaking bound
of the atomic registers scenario. The model was verified up to
. This interesting result confirmed the usefulness and flexibility of the adopted Uppaal approach, which can reveal subtle, not intuitive properties, when timing and concurrency are allowed to be studied in combination.
The positive effects of fencing the top common variable were also retrieved when the Simple and the Loop techniques for handling the non-atomic registers were used. The Loop technique was confirmed to be the most expensive for the model checking.
4. Conclusions
Dekker’s mutual exclusion algorithm for two processes (Dekker-2) [
1,
2,
3] is the first well-known software-based solution that has triggered the development of subsequent algorithms for
processes. In [
22], Dekker-2 and some of its variants were formally modelled and verified by model checking under atomic and non-atomic registers [
6,
7], as well as in the context of a standard tournament tree-based organization [
24,
26]. The adopted approach is based on timed automata [
17] and the Uppaal toolbox [
18,
19,
20]. It is intuitive because it supports the graphical specification of processes as automata in a high-level modeling language. It is powerful for the exhaustive verification, assisted by Timed Computational Tree Logic (TCTL) queries [
18], of concurrent and timed systems. In [
1], Dekker proposed a new and fair mutual exclusion algorithm for any number of processes (Dekker-N). The author analyzed the algorithm using informal reasoning. To the best of our knowledge, no previous formal modeling and verification of the algorithm has been reported in the literature. This paper applied our Uppaal approach for a thorough analysis of Dekker-N. It emerged that Dekker-N is a fully correct and fair mutual exclusion algorithm when atomic registers are used. A competing process can suffer from an overtaking bound of
. Dekker-N was then adapted to work with non-atomic registers where it was confirmed to be correct only when
. For
, Dekker-N loses its fundamental mutual exclusion property. However, as an original contribution of this paper, the adopted approach was capable of showing that by fencing, with the help of some physical mechanism, just one single common variable and thus forcing it to have atomic read/write operations, Dekker-N turns out to be correct also with non-atomic registers.
A unique feature of our Uppaal approach is its capability of enabling a careful analysis of a general model as Dekker-N when timing is used in combination with concurrency and nondeterminism.
The continuation of the research will be geared to the following points. First, continue experimenting with the approach and non-atomic registers in other mutual exclusion algorithms for any number of processes, e.g., in the context of the tournament tree organization [
16,
22,
23,
24]. Second, compare Dekker-N with similar algorithms also based on a binary tree tournament approach. Third, implement the Dekker-N model and similar algorithms using the Theatre actor system [
16,
30] to study the model with non-atomic registers and higher values of
, using stochastic behavior and simulation.