Using SPIN

Gerard J. Holzmann

gerard@plan9.bell-labs.com

ABSTRACT

SPIN can be used for proving or disproving logical properties of concurrent systems. To render the proofs, a concurrent system is first modeled in a formal specification language called PROMELA. The language allows one to specify the behaviors of asynchronously executing processes that may interact through synchronous or asynchronous message passing, or through direct access to shared variables.

System models specified in this way can be verified for both safety and liveness properties. The specification of general properties in linear time temporal logic is also supported.

The first part of this manual discusses the basic features of the specification language PROMELA. The second part describes the verifier SPIN.

1. The Language PROMELA

PROMELA is short for Protocol Meta Language [Ho91]. PROMELA is a modeling language, not a programming language. A formal model differs in two essential ways from an implementation. First, a model is meant to be an abstraction of a design that contains only those aspects of the design that are directly relevant to the properties one is interested in proving. Second, a formal model must contain things that are typically not part of an implementation, such as worst-case assumptions about the behavior of the environment that may interact with the system being studied, and a formal statement of relevant correctness properties. It is possible to mechanically extract abstract models from implementation level code, as discussed, for instance in [HS99].

Verification with SPIN is often performed in a series of steps, with the construction of increasingly detailed models. Each model can be verified under different types of assumptions about the environment and for different types of correctness properties. If a property is not valid for the given assumptions about system behavior, the verifier can produce a counter-example that demonstrates how the property may be violated. If a property is valid, it may be possible to simplify the model based on that fact, and prove still other properties.

Section 1.1 covers the basic building blocks of the language. Section 1.2 introduces the control flow structures. Section 1.3 explains how correctness properties are specified. Section 1.4 concludes the first part with a discussion of special predefined variables and functions that can be used to express some correctness properties.

Up to date manual pages for SPIN can always be found online at: http://cm.bell-labs.com/cm/cs/what/spin/Man/

1.1. Basics

A PROMELA model can contain three different types of objects:

∙ Processes (section 1.1.1),

∙ Variables (section 1.1.2),

∙ Message channels (section 1.1.3).

All processes are global objects. For obvious reasons, a PROMELA model must contain at least one process to be meaningful. Since SPIN is specifically meant to prove properties of concurrent systems, a model typically contains more than one process.

Message channels and variables, the two basic types of data objects, can be declared with either a global scope or a local scope. A data object with global scope can be referred to by all processes. A data object with a local scope can be referred to by just a single process: the process that declares and instantiates the object. As usual, all objects must be declared in the specification before they are referenced.

1.1.1. Processes

Here is a simple process that does nothing except print a line of text:

init {

    printf("it works\n")

}

There are a few things to note. Init is a predefined keyword from the language. It can be used to declare and instantiate a single initial process in the model. (It is comparable to the main procedure of a C program.) The init process does not take arguments, but it can start up (instantiate) other processes that do. Printf is one of a few built-in procedures in the language. It behaves the same as the C version. Note, finally, that no semicolon follows the single printf statement in the above example. In PROMELA, semicolons are used as statement separators, not statement terminators. (The SPIN parser, however, is lenient on this issue.)

Any process can start new processes by using another built-in procedure called run. For example,

proctype you_run(byte x)

{

    printf("my x is: %d\n", x)

}

init {

    run you_run(1);

    run you_run(2)

}

The word proctype is again a keyword that introduces the declaration of a new type of process. In this case, we have named that type you_run and declared that all instantiations of processes of this type will take one argument: a data object of type byte, that can be referred to within this process by the name x. Instances of a proctype can be created with the predefined procedure run, as shown in the example. When the run statement completes, a copy of the process has been started, and all its arguments have been initialized with the arguments provided. The process may, but need not, have performed any statement executions at this point. It is now part of the concurrent system, and its execution can be interleaved arbitrarily with those of the other, already executing processes. (More about the semantics of execution follows shortly.)

In many cases, we are only interested in creating a single instance of each process type that is declared, and the processes require no arguments. We can define this by prefixing the keyword proctype from the process declaration with another keyword: active. Instances of all active proctypes are created when the system itself is initialized. We could, for instance, have avoided the use of init by declaring the corresponding process in the last example as follows:

active proctype main() {

    run you_run(1);

    run you_run(2)

}

Note that there are no parameters to instantiate in this case. Had they been declared, they would default to a zero value, just like all other data objects that are not explicitly instantiated.

Multiple copies of a process type can also be created in this way. For example:

active [4] proctype try_me() {

    printf("hi, i am process %d\n", _pid)

}

creates four processes. A predefined variable _pid is assigned to each running process, and holds its unique process instantiation number. In some cases, this number is needed when a reference has to be made to a specific process.

Summarizing: process behavior is declared in proctype definitions, and it is instantiated with either run statements or with the prefix active. Within a proctype declaration, statements are separated (not terminated) by semicolons. As we shall see in examples that follow, instead of the semicolon, one can also use the alternative separator -> (arrow), wherever that may help to clarify the structure of a PROMELA model.

Semantics of Execution

In PROMELA there is no difference between a condition or expression and a statement. Fundamental to the semantics of the language is the notion of the executability of statements. Statements are either executable or blocked. Executability is the basic means of enforcing synchronization between the processes in a distributed system. A process can wait for an event to happen by waiting for a statement to become executable. For instance, instead of writing a busy wait loop:

while (a != b)  /* not valid Promela syntax */

    skip;   /* wait for a==b */

...

we achieve the same effect in PROMELA with the statement

(a == b);

...

Often we indicate that the continuation of an execution is conditional on the truth of some expression by using the alternate statement separator:

(a == b) -> ...

Assignments and printf statements are always executable in PROMELA. A condition, however, can only be executed (passed) when it holds. If the condition does not hold, execution blocks until it does. There are similar rules for determining the executability of all other primitive and compound statements in the language. The semantics of each statement is defined in terms of rules for executability and effect. The rules for executability set a precondition on the state of the system in which a statement can be executed. The effect defines how a statement will alter a system state when executed.

PROMELA assumes that all individual statements are executed atomically: that is, they model the smallest meaningful entities of execution in the system being studied. This means that PROMELA defines the standard asynchronous interleaving model of execution, where a supposed scheduler is free at each point in the execution to select any one of the processes to proceed by executing a single primitive statement. Synchronization constraints can be used to influence the interleaving patterns. It is the purpose of a concurrent system’s design to constrain those patterns in such a way that no correctness requirements can be violated, and all service requirements are met. It is the purpose of the verifier either to find counter-examples to a designer’s claim that this goal has been met, or to demonstrate that the claim is indeed valid.

1.1.2. Variables

The table summarizes the five basic data types used in PROMELA. Bit and bool are synonyms for a single bit of information. The first three types can store only unsigned quantities. The last two can hold either positive or negative values. The precise value ranges of variables of types short and int is implementation dependent, and corresponds to those of the same types in C programs that are compiled for the same hardware. The values given in the table are most common.

The following example program declares a array of two elements of type bool and a scalar variable turn of the same type. Note that the example relies on the fact that _pid is either 0 or 1 here.

/*

 * Peterson’s algorithm for enforcing

 * mutual exclusion between two processes

 * competing for access to a critical section

 */

bool turn, want[2];

active [2] proctype user()

{

again:

    want[_pid] = 1; turn = _pid;

    /* wait until this condition holds: */

    (want[1 - _pid] == 0 || turn == 1 - _pid);

  Executability is the basic means of enforcing synchronization between the processes in a distributed system. A process can wait for an event to happen by waiting for a statement to become executable. For instance, instead of writing a busy wait loop:

while (a != b)  /* not valid Promela syntax */

    skip;   /* wait for a==b */

...