• No results found

Mutual Exclusion Using Weak Semaphores

N/A
N/A
Protected

Academic year: 2021

Share "Mutual Exclusion Using Weak Semaphores"

Copied!
75
0
0

Bezig met laden.... (Bekijk nu de volledige tekst)

Hele tekst

(1)

Mutual Exclusion Using

Weak Semaphores

Master's Thesis Computer Science

January 2011

Student: Mark IJbema

Primary supervisor: Wim H. Hesselink

Secondary supervisor: Marco Aiello

(2)

2

(3)

3

Abstract

In this thesis we describe two algorithms which implement mutual exclusion without individual starvation, one by Udding[8] and one by Morris[7]. We prove, using the theorem prover PVS, that they both implement mutual exclusion, and are free from both deadlock and individual starvation. Though the algorithms were provided with a proof, they were not mechanically veried as of yet.

We conclude by looking at a conjecture by Dijkstra, which was disproved by the existence of the algorithms by Udding and Morris. We weaken it in such a way that it is true, and prove it informally.

(4)

4

(5)

CONTENTS 5

Contents

Abstract 3

1 Introduction 9

1.1 Appendices . . . 9

2 Mutual Exclusion 11 2.1 Solutions to the Mutual Exclusion Problem . . . 12

3 Semaphores 13 3.1 Safety Properties . . . 14

3.1.1 Mutual Exclusion . . . 14

3.1.2 Freedom from Deadlock . . . 14

3.1.3 Freedom from Starvation . . . 14

3.2 Semaphores with a Progress Property . . . 14

3.3 Formal Denitions . . . 15

3.3.1 Invariants for Semaphores . . . 15

3.3.1.1 Split Binary Semaphores . . . 15

3.3.2 Types of Semaphores . . . 15

3.3.2.1 Weak Semaphore . . . 16

3.3.2.2 Polite Semaphore . . . 16

3.3.2.3 Buered Semaphore . . . 17

3.3.2.4 Strong Semaphore . . . 17

3.4 Dijkstra's Conjecture . . . 18

4 Mutual Exclusion using Weak Semaphores 19 4.1 The Mutual Exclusion Problem . . . 19

4.2 General Structure of the Algorithm . . . 19

4.3 Multiple Models . . . 20

4.4 Morris' Algoritm . . . 21

4.5 Udding's Algorithm . . . 22

4.6 Switching between Open and Closed . . . 22

5 Implementation of the algorithms 25 5.1 Udding's Algorithm . . . 25

5.2 Morris' Algorithm . . . 26

6 Concurrent Programs in PVS 29 6.1 Model of a Non-Concurrent Program . . . 29

6.2 Model of a Concurrent Program . . . 30

6.3 Proving an Invariant . . . 31

6.4 Using Invariants on the End-State . . . 32

6.4.1 Disadvantages . . . 33

6.4.2 Guidelines . . . 33

6.5 Proving Other Statements . . . 33

(6)

6 CONTENTS

6.6 Interesting Commands we Devised . . . 33

6.6.1 expand-all-steps . . . 33

6.6.2 expand-only-steps . . . 34

6.6.3 splitgrind and splitgrind+ . . . 34

6.6.4 assume . . . 34

7 Mechanical Proof of Mutual Exclusion 35 7.1 Methodology . . . 35

7.2 Generic Proof . . . 35

7.3 Udding's Algorithm . . . 36

7.4 Morris' Algorithm . . . 36

8 Mechanical Proof of Freedom from Deadlock 37 8.1 General Idea of the Proof . . . 37

8.2 Udding's Algorithm . . . 37

8.2.1 No Deadlock on Enter . . . 38

8.2.2 No Deadlock on Mutex . . . 39

8.3 Morris' Algorithm . . . 40

8.4 Formalization of the Proof . . . 41

9 Mechanical Proof of Freedom from Individual Starvation 43 9.1 Participation . . . 43

9.2 Proof Using a Variant Function . . . 43

9.3 Proof Obligation . . . 43

9.4 Choosing the Variant Function . . . 44

9.5 Phases of the Algorithm . . . 44

9.6 The Variant Function . . . 45

9.7 Proof . . . 45

10 The Variant Function is Decreasing 47 10.1 Structure of the Proof . . . 47

10.1.1 The Easy Part . . . 47

10.1.2 The Hard Part . . . 47

10.2 Function d is Invariant Over Most Steps . . . 47

10.2.1 Invariants Using pred-enter and pred-mutex . . . 48

10.2.2 Phase0 is Invariant Over Most Steps . . . 49

10.2.3 Phase1 is Invariant Over Most Steps . . . 50

10.2.4 Phase2 is Invariant Over Most Steps . . . 50

10.3 VF is Decreasing for Most Steps . . . 51

10.4 VF is Decreasing for Phase-Changing Steps . . . 51

10.4.1 Step 26, phase0 . . . 52

10.4.2 Step 26, phase1 . . . 52

10.5 Combining the Proofs . . . 52

11 Executions 53 11.1 Executions . . . 53

11.2 Valid Executions . . . 54

11.3 Grammar . . . 54

11.3.1 Meta Functions . . . 55

11.3.1.1 Permute . . . 55

11.3.1.2 Zip . . . 55

11.3.1.3 Split . . . 55

11.3.1.4 Rnd . . . 55

11.3.2 Terminals and Functions to Create Them . . . 55

11.3.3 The Grammar . . . 56

(7)

CONTENTS 7

12 Dijkstra's Conjecture 57

12.1 Proof . . . 57 12.2 The V Operation is no Valid Candidate . . . 58

13 Future Work 59

13.1 Dijkstra's Conjecture . . . 59 13.2 Comparison of Algorithms . . . 60 13.3 Automating the Proof of Invariants . . . 60

14 Conclusion 61

A List of Invariants Used for the Proof of Udding's Algorithm 65 B List of Invariants Used for the Proof of Morris' Algorithm 69 C Polite Semaphore in the Algorithms by Udding and Morris 73 C.1 Motivation for Trying the Polite Semaphore . . . 73 C.2 Algorithm by Udding . . . 74 C.3 Can the Polite Semaphore be Used . . . 75

(8)

8 CONTENTS

(9)

9

Chapter 1

Introduction

In this thesis we dene and introduce the problem of mutual exclusion, and dis- cuss the implementations and proofs of two algorithms which implement mutual exclusion without individual starvation, as proposed by Udding[8] and Morris[7].

To this end we rst discuss the problem of mutual exclusion in chapter 2.

Then we present semaphores as a possible solution to this problem in chapter 3.

We continue by discussing the dierent types of semaphores. In the last part of chapter 3 we also present and interpret a conjecture about the strength of dierent types of semaphores, as posed by E.W. Dijkstra.

In chapter 4 we pose the problem of solving the mutual exclusion problem using weak semaphores. From this problem we derive the algorithms as presen- ted by Udding and Morris. We present the exact implementations for these algorithms in chapter 5.

Because we proved the correctness of the algorithms in PVS, a theorem prover, we rst discuss how one goes about proving statements about programs in PVS, in chapter 6. We show how to model a program, formulate invariants, and prove invariants.

Chapters 7, 8, 9, and 10 contain a human-readable version of the proofs that both algorithms conrm to the safety properties: Mutual Exclusion (chapter 7), Freedom from Deadlock (chapter 8), and Freedom from Individual Starvation (chapters 9 and 10). We have proved these properties in PVS, but in these chapters we refrain from going into detail about the implementation of this proof.

In chapter 11 we present a method to compare both algorithms to other algorithms solving mutual exclusion. The goal is to be able to compare both algorithms to a wait-free algorithm as proposed by Wim H. Hesselink and Alex A. Aravind in [4].

In chapter 12 we formulate and informally prove a weakened version of the conjecture by E.W. Dijkstra.

Lastly we present suggestions for future work in chapter 13 and formulate our conclusions in chapter 14.

1.1 Appendices

For the interested reader we oer a few appendices. In appendices A and B we provide a list of all invariants used in the PVS-proof. For each invariant we provide both a reference to the page where it is introduced, and the name of the invariant in the PVS-proof.

In appendix C we oer justication for the choice of type of semaphore used in our model for the algorithms.

(10)

10 Introduction

(11)

11

Chapter 2

Mutual Exclusion

If we have a program with multiple processes we desire a mechanism to ensure that certain parts of the program cannot be executed by multiple processes at the same time[1]. Let us look at this simple fragment for instance:

x := x − 1;

One might expect that, since this is only a single line of code, only one process at the time can execute it. However, in machine instructions this simple line of code translates to several lines of code, and might look something like this:

tmp :=read x;

tmp := tmp − 1;

write tmp to x;

Here the tmp variable is local, that is, each thread has one, whereas the xvariable is shared. When we run this program with multiple processes, the scheduler might decide to switch the active process at any moment, so there is no way to predict in which order the program will be executed if multiple processes are involved. We can therefore think of the following example of an execution, where this program will not give the result we would expect it to.

Suppose we have two processes A and B which execute this code fragment concurrently, both with their own tmp variable (A.tmp and B.tmp), and one shared variable x (initially 7). In the following table we show what line(s) are executed for each process, and to the right the state after execution of those lines is shown:

A executes B executes A.tmp B.tmp x

? ? 7

tmp :=read x; ? 7 7

tmp :=read x; 7 7 7

tmp := tmp − 1; 7 6 7

write tmp to x; 7 6 6

tmp := tmp − 1; 6 6 6

write tmp to x; 6 6 6

Now two processes tried to decrement x, but it is only decremented once.

We frequently encounter situations like this, where we have a certain section of the program which must be executed atomically by one process, before another

(12)

12 Mutual Exclusion

process is allowed to execute the same fragment. Such a section is called a critical section.

It is also important to look at what we call atomic. This means a piece of code is executed by a process without interruptions from other processes.

However, we do not really care about processes which only change non-related variables; therefore we also call an execution atomic when the execution can be reordered in such a way that the outcome is the same, and the lines to be executed atomically are not interspersed with other lines of other processes.

2.1 Solutions to the Mutual Exclusion Problem

Several mechanisms have been invented to ensure that no two processes can ex- ecute the critical section at the same time. Broadly speaking these mechanisms can be divided into two categories: with waiting and without waiting. Recently, there has been more interest in solutions without waiting.

A program which uses waiting has some sort of mechanism which can cause processes to `go to sleep' to be `awoken' later by the scheduler. A program without waiting has no such mechanism, and at each point in time, each process is able to perform a step.

One solution without waiting is presented by Wim H. Hesselink and Alex A.

Aravind in [4]. A referee commented that their solution looked a lot like two solutions with waiting, one by Udding[8] and the other by Morris[7].

In this thesis we concentrate on the latter two algorithms, by proving them rigourously using interactive theorem proving. After that we return to the algorithm by Hesselink and Aravind, and propose a method to compare the algorithms by Udding and Morris to it.

(13)

13

Chapter 3

Semaphores

To implement mutual exclusion with waiting, one of the most obvious mechan- isms to use is the semaphore.

A semaphore allows processes to continue or halts them, depending on whether the semaphore is open. A semaphore consists of two operations, the P and V operation,1 and a variable, say s. The P operation is placed before the critical section. It only allows a process to continue if the semaphore is open, and then it immediately closes the semaphore. The V operation is placed after the critical section, and opens the semaphore, thus allowing processes to pass the semaphore. The section between the P and V operations is called the guarded section. To ensure the critical section is executed by at most one process at a time the critical section is typically placed inside a guarded section.

To ensure that the piece of code from the previous chapter is executed atom- ically, we could enhance it with semaphore operations:

P(s);

x := x − 1;

V(s);

A semaphore is modelled by a natural number s initialized to 1, and by the following denitions of the atomic P and V operations:

procedure P(s) hawait s > 0;

s := s − 1; i procedure V(s)

hs := s + 1; i

Note that this implementation is in fact more general, since s can take on the value of any natural number; one could use this mechanism to allow at most two processes to execute a section, for instance. However, in this document we restrict ourselves to semaphores which only take on the values 1 and 0, the so-called binary semaphores.

1The V stands voor the Dutch word verhogen (increase), and the P stands for the made- up Dutch word prolaag, which is a contraction of probeer and verlaag (respectively try and decrease).

(14)

14 Semaphores

3.1 Safety Properties

When we want to extend the usability of semaphores, we want to ensure that a semaphore satises several properties implying that the program `works as one would expect'. There are three properties which are commonly regarded as the important safety properties for binary semaphores, when used in the context of mutual exclusion.

3.1.1 Mutual Exclusion

The rst property is mutual exclusion: only one process can be in the guarded section at any one time.

3.1.2 Freedom from Deadlock

The second property is that we do not want the program to come to a halt before it is nished. This means we do not want to have a situation where no process is able to perform an action. This property is called freedom from deadlock.

3.1.3 Freedom from Starvation

The third desirable property is having some form of progress for each process;

we do not want it to be possible for some process to wait indenitely, while other processes keep executing the guarded section. This is called freedom from starvation.

We do have some freedom in expressing the exact terms of this property.

We want some progress, so the weakest way to demand this is that there is no process which waits forever at some point. In practice the stronger demand of bounded overtaking is desirable: each process is overtaken at most a certain number of times, for instance, at most twice by any other process. This gives a better worst-case performance, and is often easier to prove.

3.2 Semaphores with a Progress Property

Morris and Udding use dierent descriptions for the semaphores they used, even though they have similar goals. Morris' description is:

If any process is waiting to complete a P(s) operation, and another process performs a V(s), the semaphore is not incremented, and one of the waiting processes completes its P(s)

The advantage of this semaphore is that it conforms to the invariants from section 3.3.1. The only dierence to a semaphore without a progress property is when there are processes waiting, and a V operation is executed. In this case the semaphore stays 0, but some process is directly `pushed' into the guarded section. The disadvantage is that the property is very strong. Not only is the process which may pass explicitly chosen, it also has to make a step immediately, meaning the semaphore inuences the scheduler as well.

Udding uses an apparently weaker property, being:

For weak semaphores, one assumption is made, however, viz. a pro- cess that executes a V-operation on a semaphore will not be the one to perform the next P-operation on that semaphore, if a process has been blocked at that semaphore. One of the waiting processes is allowed to pass the semaphore.

(15)

3.3 Formal Denitions 15

This is only marginally weaker, because the only dierence is when the elected process makes the step into the guarded zone. In Udding's version this might be postponed until the elected process is scheduled again, instead of immedi- ately. On rst reading Udding's denition seems much weaker, because of the (redundant) rst sentence. An interesting question is whether the algorithms would still be correct without the second sentence, that is, only demanding that the last process to execute the V operation cannot be the rst to execute the P operation, if already processes were waiting. We look further into this question in appendix C.

3.3 Formal Denitions

We now present the formal denitions used for semaphores in this document.

We start by showing the invariants we have for semaphores, and then continue by introducing several types of semaphores.

3.3.1 Invariants for Semaphores

We can describe the semaphores we are going to use more formally by dening several invariants which must hold for a binary semaphore:

s ≥ 0 (SEM0)

s ≤ 1 (SEM1)

Additionally, since we only use semaphores in the context of mutual exclusion, we also want the following two invariants to hold:

there is a process in guarded section ⇒ s = 0 (SEM2)

s = 0 ⇒there is a process in guarded section (SEM3)

If we assume the rst invariant to be trivial (since we take s to be a natural number2) we can summarize the invariants as follows:

#(processes in guarded section) + s = 1 (SEM)

3.3.1.1 Split Binary Semaphores

For the algorithms discussed in this document we also need a so-called split binary semaphore[5]. This is a construct consisting of two (or more) actual semaphores. Furthermore, the two semaphores together behave as a semaphore.

This means that if s1+ s2 is a split binary semaphore we get almost the same invariant:

#(processes in guarded sections) + s1+ s2= 1 (SSEM)

In the code, each P on s1or s2is followed by a V on s1or s2. Note that a P on s1does not necessarily require a V on s1as next V operation. This means that both P(s1) . . . V(s1)and P(s1) . . . V(s2)are valid blocks. This allows us to have multiple guarded sections, and to guide which section will be executed next.

3.3.2 Types of Semaphores

We can now introduce the four types of semaphores we use in this document:

the weak semaphore, which doesn't have any progress property, and three types of semaphores which do have a progress property.

2We use natural numbers in the same way PVS does: non-negative integers.

(16)

16 Semaphores

We denote atomicity by the usage of line numbers. One line is one atomic action. Furthermore we introduce the variable `self' which refers to the process identier of the executing process, and remark that −1 is not a valid process identier.

3.3.2.1 Weak Semaphore

The weak semaphore does not oer any guarantees with respect to individual starvation. One important implication is that this semaphore does not make any dierence between processes attempting a P operation for the rst time, and processes which are already blocked on a P operation.

A semaphore is weak if and only if it is logically equivalent to the following one:

Algorithm 3.3.1: Weak Semaphore procedure P(s)

10 if s > 0

then s := s − 1; goto 11;

else goto 10;

11 . . . procedure V(s)

20 s := s + 1;

This semaphore conforms to the aforementioned invariants.

3.3.2.2 Polite Semaphore

The dierence between a weak semaphore and a semaphore which has some progress property becomes apparent when processes are blocked on a P oper- ation. So we consider the situation that process p executes a V operation on semaphore s, while other processes are blocked on the P operation of semaphore s. We propose a new type of semaphore, which forbids process p to be the rst to execute a P operation on semaphore s. We call this type of semaphore the polite semaphore. Note the importance of a second state for processes which have already attempted the P operation but have failed.

The specication of this semaphore might look like this:

Algorithm 3.3.2: Polite Semaphore procedure P(s)

10 if s > 0 ∧ self 6= forbidden then

s := s − 1; forbidden := −1;goto 12;

else goto 11;

11 await s > 0 ∧ self 6= forbidden;

s := s − 1; forbidden := −1;

12 . . . procedure V(s)

20 if proc_at(11) 6= ∅

then forbidden := self;

s := s + 1;

(17)

3.3 Formal Denitions 17

Note that for this semaphore the invariants from section 3.3.1 hold true.

Furthermore we need the following invariant for freedom from deadlock:

forbidden 6= −1 =⇒ ∃p ∈ proc_at(11) : p 6= forbidden (3.1)

It is easy to see that this invariant is initially true (since then there is no process at line 11), and is at no point invalidated.

3.3.2.3 Buered Semaphore

The buered semaphore oers a stronger form of progress: when a process executes a V operation, and there were waiting processes, it is guaranteed that one of these waiting processes is the rst to enter the guarded section.3

This guarantee is obtained in the following way: a process executing P(s) is allowed to continue (and decrement s) if s > 0, but if s = 0 the process is added to a set of waiting processes: the buer. The process is then only allowed to continue when it is not in the set of waiting processes anymore.

Since s = 0, there is still a process in the guarded section. If the set of waiting processes is not empty when this process leaves the guarded section, one of the processes is released by removing it from the set from waiting processes. If the set of waiting processes is empty, s is incremented.

We hereby present a model:

Algorithm 3.3.3: Buffered semaphore procedure P(s)

21 if s > 0 then

s := s − 1;

goto 23;

else

sbuer+= self ; goto 22;

22 await self /∈ sbuer;goto 23;

23 . . . procedure V(s)

40 if sbuer= ∅ then s := s + 1;

else extract some q from sbuer;

An important dierence between this semaphore and the previous ones is that s can remain 0 when a process leaves the guarded section. Therefore it is important that we consider processes on line 22 which are not in sbuer to be in the guarded section. In that way the invariants remain valid.

3.3.2.4 Strong Semaphore

A strong semaphore is dened by the fact that it does not allow for individual starvation. This means that once a process blocks on a P operation, it can be overtaken at most a nite number of times, before it is allowed to enter the guarded section. Note that this is not the case for the previous two semaphores, when there are more than two processes involved.

3This is not the case for the polite semaphore. This semaphore can also allow a later arriving process to pass.

(18)

18 Semaphores

Because this demand is rather imprecise it allows for diverse implementa- tions. One strong version is when we take the buered semaphore, and replace the set by a queue. In that way we enforce a strict order, and in fact guarantee that no other processes will overtake a process once it is blocked.

3.4 Dijkstra's Conjecture

In practice all types of semaphores conform to the rst two safety properties, mutual exclusion and freedom from deadlock. This is not true for freedom from starvation. Therefore it is a good idea to look at the amount of safety the dierent kind of semaphores oer in this aspect. First we consider the strong semaphore.

The problem with strong semaphores is that they are not oered by most hardware and therefore require a non-trivial software eort to implement. Most operating systems do however oer a weak semaphore, which oers no guaran- tees in the sense of freedom from individual starvation. Note that this means that individual starvation can already happen with only two processes.

According to Dijkstra[3] it is not possible to solve the mutual exclusion problem for an undened number of processes using only a xed number of buered semaphores:

A starvation-free solution [to the mutual exclusion problem] using a xed number of weak semaphores is not known for an unbounded number of processes, and can probably be shown not to exist.

Morris claims that this is possible[7], and presents a solution. However, according to Annot et al.[2], this is not a refutal of Dijkstra's Conjecture. They claim the weak semaphores Dijkstra refers to are the ones mentioned above, and not the buered semaphores. This, however, is clearly untrue, as Dijkstra uses the following denition for weak semaphores:

In the weak implementation it is left absolutely undened which of the blocked processes is allowed to proceed when two or more processes are blocked by P(m) when V(m) is executed.

In this denition he implies one of the blocked processes is allowed to proceed, which means it is a buered semaphore. However, the other conjecture, that it is not possible to solve thu mutual exclusion problem with weak semaphores is interesting as well. Therefore we reformulate the weakened version of the conjecture of Dijkstra:

Conjecture 3.1. It is not possible to solve the mutual exclusion problem using only a xed number of weak semaphores.

(19)

19

Chapter 4

Mutual Exclusion using Weak Semaphores

In this chapter we show how the two algorithms implement mutual exclusion using semaphores. We rst give a bird's-eye view version of both algorithms, and then work our way down from there, to a concrete model of each algorithm.

4.1 The Mutual Exclusion Problem

The rst thing we need to do is present the mutual exclusion problem. To this end we look at a setting in which all processes execute some non-critical section, and then the critical section, ad innitum. This is shown in the following algorithm:

Algorithm 4.1.1: Mutual Exclusion Problem for each p : process

while true NCS;

entry;

CS;

exit;

The mutual exclusion problem can be stated as follows: to dene entry and exitin such a way that mutual exclusion is guaranteed. In this chapter we also want that freedom from deadlock and freedom from starvation are guaranteed as well. We require the number of processes to be nite, though the number needs not to be known. In the rest of this chapter we do not look at the entry and exit sections separately, but instead discuss only the full algorithm, encompassing entry, CS and exit.

4.2 General Structure of the Algorithm

We want to solve the mutual exclusion problem using only weak and buered semaphores. Note that we want to prevent individual starvation. To this end both algorithms are set up as a kind of airlock (like ones commonly found when entering buildings in very hot or very cold climates). First all processes enter the airlock one by one, until at a certain moment there are no more processes wanting to enter. Then the entrance is closed, and the processes are released

(20)

20 Mutual Exclusion using Weak Semaphores

through the exit one by one. When everyone has left the airlock, the entrance is once again opened.

When we explain the algorithm like this it is easy to see that this imple- ments mutual exclusion in the same way that a strong semaphore would do.

Because all processes leave the exit one by one, it is safe to place the critical section here, and have guaranteed mutual exclusion. Furthermore freedom from individual starvation is also ensured. A process which has gone through the whole algorithm can never catch up with a process which is still in the middle of it, because the entrance stays closed until all processes have left through the exit. We dene ne to be the number of processes that want to enter the airlock, and nm to be the number of processes in the airlock. This translates into the following piece of code:

Algorithm 4.2.1: General structure

ne := ne + 1; REGISTRATION

P(enter);

nm := nm + 1;

ne := ne − 1;

if ne = 0

then V(mutex);

else V(enter);













ENTER

P(mutex);

nm := nm − 1;

Critical Section;

if nm = 0 then V(enter);

else V(mutex);













MUTEX

Note how directly the story maps to the code. Initially the front door (enter) is open. Then we let processes enter one by one, and if there are more processes waiting (ne > 0), we re-open the front door. When nobody is waiting, we close the front door, and open the exit (mutex). We then keep on re-opening the exit until everyone has left the airlock (nm = 0), at which point we reopen the front door.

4.3 Multiple Models

Up until this point of abstraction, both algorithms are actually the same. How- ever, one more thing is needed. We need to make sure that no two processes concurrently assign to ne. Actually, if we would use an atomic counter, which could also do the decrement-and-test in the block ENTER atomically, we would already be done.

However, since we want to have an algorithm using only semaphores as concurrency technique, we need to ensure that both the increment and the decrement-and-test of ne are protected by the same semaphore, say X, as in algorithm 4.3.1 (for reference, we label the P operations on X with an index, but they are the same operations).

To see how we need to place the semaphore operations on X, we rst consider that we need to ensure freedom from starvation. To this end we look at where starvation can occur. This can appear anywhere where a process can be passed, therefore it can occur anywhere outside the guarded sections. This means that it can only occur on a P operation at the start of a block.

(21)

4.4 Morris' Algoritm 21

Algorithm 4.3.1: Place of P (X)

P1(X); ne := ne + 1; V1(X); REGISTRATION . . .

P2(X);

. . .

ne := ne − 1;

if ne = 0

then . . . V2(X);

else . . . V2(X);

. . .

















ENTER

It is rather easy to see that starvation cannot occur at the start of ENTER, nor at the start of MUTEX. That is, if a process stands at the start of the block ENTER, ENTER wil open until all processes have passed ENTER. This is because as soon as a process has entered the airlock, the entrance of the airlock will stay open until it has passed. The reasoning for the block MUTEX is similar, since ENTER only is opened after all processes between ENTER and MUTEX have passed MUTEX.

Therefore the tricky part is located in the P1(X) operation at the start of REGISTRATION.

To ensure that no starvation can occur at this point, we want to make sure that at the moment the P2(X) operation is executed for the last time (by the process which sets ne to zero, and subsequently opens MUTEX), no processes are blocked at the P1(X)operation. This is ensured by the following two conditions.

Condition 4.1. Whenever a V2(X) operation is executed, no processes are waiting at the P2(X) operation.

Condition 4.2. X is a buered semaphore.

Now we have two cases for the last execution of the P2(X)operation:

1. The process executing P2(X) was blocked on P2(X), and subsequently released. But since it cannot have been released by the V2(X)(because of condition 4.1), it has been released by the V1(X), which means that ne is at least 2. Therefore, this is not the last process.

2. The process executing P2(X)last can immediately continue, therefore, no processes are blocked on P(X), because of condition 4.2.

This shows that no starvation can occur at the P1(X)operation. The only problem remaining is how to ensure that condition 4.1 holds. We discuss two solutions to this problem: the algorithms as presented by Morris and Udding.

We discuss them separately.

4.4 Morris' Algoritm

Morris chooses to introduce a new semaphore for X, named b. This is placed around both the increment and the decrement-and-check. Because both the P2 and V2 operation of the semaphore are placed within the guarded region of enter, no process can wait for P(b) in the block ENTER when V(b) within ENTER is executed. This is exactly condition 4.1.

(22)

22 Mutual Exclusion using Weak Semaphores

Algorithm 4.4.1: Morris' Algorithm P(b); ne := ne + 1; V(b); o

REGISTRATION P(enter);

nm := nm + 1;

P(b); ne := ne − 1;

if ne = 0

then V(b); V(mutex);

else V(b); V(enter);













ENTER

P(mutex);

nm := nm − 1;

Critical Section;

if nm = 0 then V(enter);

else V(mutex);













MUTEX

4.5 Udding's Algorithm

Udding solves the problem by using the semaphore enter for X. This means that both the increment and the decrement-and-check of ne are guarded by the same semaphore. To ensure that condition 4.1 is also fullled, an extra semaphore, queue, is placed around the entire ENTER block. This has as a direct consequence that at V(enter) at the end of the block ENTER, no other process can be blocked on the P(enter) at the start of the ENTER block (because of mutual exclusion, within the guarded region of queue).

Algorithm 4.5.1: Udding's Algorithm

P(enter); ne := ne+1; V(enter); oREGISTRATION

P(queue);

P(enter);

nm := nm + 1;

ne := ne − 1;

if ne = 0

then V(mutex);

else V(enter);

V(queue);





















ENTER

P(mutex);

nm := nm − 1;

Critical Section;

if nm = 0 then V(enter);

else V(mutex);













MUTEX

4.6 Switching between Open and Closed

As seen, Morris' algorithm and Udding's algorithm are very similar. This is also apparent in the proof, which for both algorithms more or less follows along the

(23)

4.6 Switching between Open and Closed 23

same lines. The choice of implementation to solve individual starvation at the start of REGISTRATION does lead to large dierences in the proof of freedom from individual starvation. Where this is straightforward for the algorithm of Udding, we need to use ghost variables to prove this for the algorithm of Morris. This follows from how is seen which block is open. To determine this, we introduce two predicates: pred-enter and pred-mutex. These can be seen as predictors for which block is about to open: if pred-enter is true, ENTER will be the next block to open, or it is already open. If pred-mutex is true, MUTEX will be the next to open, or a process is already in block MUTEX. Because the predicates become true at a certain moment, we need to make this description somewhat more precise:

pred-enter ⇒ mutex = 0;

(4.1)

pred-mutex ⇒ enter = 0;

(4.2)

Note that, since enter + mutex is a split binary semaphore, we also know that pred-enter ⇔ ¬pred-mutex.

Because the two algorithms do dier, they have (subtly) dierent predicates.

The predicates for the algorithm by Udding follow in a straightforward manner from the algorithm:

ne > 0 ∨ nm = 0 (pred-enter)

ne = 0 ∧ nm > 0 (pred-mutex)

One would expect these to be more or less the same for the algorithm of Morris, however, due to placement of the V operations we run into a little trouble here.

If ne = 0 in ENTER the process will enter the rst branch of the if -statement and execute V(b). After this, another process might again increase ne, therefore ensuring `ne > 0', however the rst process will still be executing V(mutex), thereby invalidating our assertion that pred-enter ⇒ mutex = 0. The problem is clear: we are interested in the value of ne at the moment the if was executed.

To solve this problem, we introduce the ghost variable ne0, which is exactly that. We call this variable a ghost variable since it does not really exist in the algorithm itself, and is only needed for the proof. Therefore, when we write out an execution, we can consider assignments to ghost variables to be executed atomically, that is, no other process can intervene. We can even consider one normal statement together with a list of assignments to ghost variables atomic.

We now propose the following code:

Algorithm 4.6.1: Morris' Algorithm (2)

P(b); ne := ne + 1; V(b); o

REGISTRATION P(enter);

nm := nm + 1;

P(b); ne := ne − 1; ne0= ne;

if ne = 0

then V(b); V(mutex);

else V(b); V(enter);













ENTER

P(mutex);

nm := nm − 1;

Critical Section;

if nm = 0 then V(enter);

else V(mutex);













MUTEX

(24)

24 Mutual Exclusion using Weak Semaphores

with the following predicates:

ne0> 0 ∨ nm = 0 (pred-enter)

ne0= 0 ∧ nm > 0 (pred-mutex)

However, this introduces a new problem. Now pred-mutex becomes tempor- arily true if nm is increased from zero to one while ne0 is equal to zero, even though ne itself might be very large. Now pred-mutex will be true until ne0 is set to ne, at which point pred-enter will be true again. Though not technic- ally wrong, this erroneous switching of predicate makes the proof harder, and therefore we introduce nm0 which will only be increased at the moment ne0 is set:

Algorithm 4.6.2: Morris' Algorithm (3)

P(b); ne := ne + 1; V(b); o

REGISTRATION P(enter);

nm := nm + 1;

P(b); ne := ne − 1; ne0= ne; nm0= nm;

if ne = 0

then V(b); V(mutex);

else V(b); V(enter);













ENTER

P(mutex);

nm := nm − 1; nm0= nm;

Critical Section;

if nm = 0 then V(enter);

else V(mutex);













MUTEX

After which we can fully express pred-enter and pred-mutex in ghost vari- ables:

ne0> 0 ∨ nm0= 0 (pred-enter)

ne0= 0 ∧ nm0> 0 (pred-mutex)

(25)

25

Chapter 5

Implementation of the algorithms

To be able to reason formally about the algorithms we need to make a formal model. The most important dierence between this implementation, and the previous presentations is that we make the atomicity explicit, and that we use the more complex semaphore. Each linenumber is executed atomically. To show that assignments are not atomic they are split into two separate statements.

To keep the length of the listings manageable, we have not written out the denition of V for the buered semaphore. We use the following denition (as described in section 3.3.2.3) of the V(s) operation:

Algorithm 5.0.3: Buffered semaphore procedure V(s)

40 if blockeds= ∅ then s := s + 1;

else extract some q from blockeds;

Since the P operation of the buered semaphore has two dierent states, spread over two dierent lines, we indicate those two states splitting the P of a strong semaphore into two operations: P and P2. For each algorithm we indicate which semaphores are weak, and which is the buered semaphore.

5.1 Udding's Algorithm

In Udding's Algorithm enter is the buered semaphore, and queue and mutex are weak semaphores.

The initialization is as follows: semaphores enter and queue are initialized to 1, all processes are at line 10, so ne = nm = 0 and blockedenter= ∅.

The algorithm as presented in Algorithm 5.1.1 follows the control structure as presented by Udding in his paper. This means that the conditions for the if statement is the inverse of the conditions in the algorithm by Morris; and the branches are of course reversed.

To be able to reason formally about the guarded regions, we formally dene those by dening the set of processes which are in the respective guarded regions.

In these denitions we introduce the function pc which returns the program counter for a given process.

(26)

26 Implementation of the algorithms

Algorithm 5.1.1: Udding's Algorithm 10 P(enter) :if enter > 0

then enter := enter − 1; goto 12;

else blockedenter+= self ;

11 P2(enter) :await self /∈ blockedenter; 12 tmp := ne;

13 ne := tmp + 1;

14 V(enter);

















REGISTRATION

20 P(queue);

21 P(enter) :if enter > 0

then enter := enter − 1; goto 23;

else blockedenter+= self ;

22 P2(enter) :await self /∈ blockedenter; 23 tmp := nm;

24 nm := tmp + 1;

25 tmp := ne;

26 ne := tmp − 1;

27 if ne > 0

28 then V(enter);

29 else V(mutex);

30 V(queue);









































EN T ER

40 P(mutex);

41 tmp := nm;

42 nm := tmp − 1;

43 Critical Section;

if nm > 0

44 then V(mutex);

45 else V(enter);

















M U T EX

procenter=

 p

(12 ≤ pc(p) ≤ 14) ∨ (23 ≤ pc(p) ≤ 29)

∨((pc(p) = 11 ∨ pc(p) = 22) ∧ p /∈ blockedenter)



procqueue= {p | 22 ≤ pc(p) ≤ 30}

procmutex= {p | 41 ≤ pc(p) ≤ 45}

Note that we choose the denition of procenter in such a way that a process on line 11 or 22 is included as soon as it is released. We also introduce the notation #S for the number of elements of the set S. With this we present two sets, for which #betweenne= neand #betweennm= nm:

betweenne= {p | 14 ≤ pc(p) ≤ 26}

betweennm= {p | 25 ≤ pc(p) ≤ 42}

5.2 Morris' Algorithm

Up until now we presented a somewhat dierent version of the algorithm by Morris from the version presented by Morris in his paper. The reason for this is that we wanted to show the commonalities between Morris' algorithm and Ud- ding's algorithm. From here on we however conform to the version as presented by Morris himself. This means there are a few dierences:

(27)

5.2 Morris' Algorithm 27

ˆ Semaphore enter is called a

ˆ Semaphore mutex is called m

ˆ variable ne is called na, and ne0 is called na0

In Morris' algorithm both a and m are weak semaphores. Semaphore b is a buered semaphore and use the above-described V. The P operations are written out.

The initialization is as follows: semaphores b and a are initialized to 1, all processes are at line 10, so na = na0= nm = nm0 = 0and blockedb= ∅.

There is one more important thing to note about the program. For proof- technical reasons the line numbers are not totally sequential, and line 31 comes before 29 in the listing (but when executing the line numbers are always increas- ing, since those two lines are from dierent branches from the if -statement).

We present the implementation in Algorithm 5.2.1.

This also means the aforementioned predicates are changed, they become:

na0> 0 ∨ nm0 = 0 (pred-enter)

na0= 0 ∧ nm0 > 0 (pred-mutex)

Algorithm 5.2.1: Morris' Algorithm 10 P(b) :if b > 0

then b := b − 1; goto 12;

else blockedb+= self ; 11 P2(b) :await self /∈ blockedb; 12 tmp := na;

13 na := tmp + 1;

14 V(b);





















REGISTRATION

20 P(a);

21 tmp := nm;

22 nm := tmp + 1;

23 P(b) :if b > 0

then b := b − 1; goto 25;

else blockedb+= self ; 24 P2(b) :await self /∈ blockedb; 25 tmp := na;

26 na := tmp − 1; na0:= tmp − 1; nm0:= nm;

27 if na = 0 28 thenV(b);

31 V(m);

29 elseV(b);

30 V(a);

























































EN T ER

40 P(m);

41 tmp := nm;

42 nm := tmp − 1; nm0:= tmp − 1;

43 Critical Section;

if nm = 0 44 then V(a);

45 else V(m);

















M U T EX

(28)

28 Implementation of the algorithms

Furthermore we introduce the same sets, as for the algorithm by Udding, to be able to reason about the guarded regions, and about na and nm:

procb=

 p

(12 ≤ pc(p) ≤ 14) ∨ (24 ≤ pc(p) ≤ 29)

∨((pc(p) = 11 ∨ pc(p) = 24) ∧ p /∈ blockedb)



proca= {p | 21 ≤ pc(p) ≤ 31}

procm= {p | 41 ≤ pc(p) ≤ 45}

betweenne= {p | 14 ≤ pc(p) ≤ 26}

betweennm= {p | 23 ≤ pc(p) ≤ 42}

(29)

29

Chapter 6

Concurrent Programs in PVS

Before we present the proof of our algorithm, we rst introduce our method of proving statements about concurrent algorithms using the theorem prover PVS. We start gently, by modelling a non-concurrent program in PVS, and formulating one invariant. We then show the extra complications concurrency introduces, and model a simple concurrent program. Finally, we show how one goes about proving invariants and other statements about concurrent programs.

6.1 Model of a Non-Concurrent Program

We start by modelling the following (never-ending) program (initially with i set to zero):

Algorithm 6.1.1: Counter 10 i := i + 1;

11 goto 10;

To be able to prove statements about this program we need to represent it in a mathematical model. To this end we rst introduce the concept of a state.

The state is a record consisting of all the variables the program uses, including internal ones, like the program counter. In this case the state is of the following type:

state : TYPE = [#

pc: nat, i : int

#]

We also dene the initial state, which we call init:

init : state : (# pc := 10, i := 0 #)

Next we introduce the step function. This is a boolean function which gets two states x and y as input, and returns true if there is a valid step from state x to state y. Since one large step function would get unwieldy for large programs, we create a step function for each line, and we take their disjunction as our step function. For our program, the step function becomes:

step10(x,y) : bool = x‘pc = 10 AND y = x WITH [‘pc = 11, ‘i := x‘i + 1]

step11(x,y) : bool = x‘pc = 11 AND y = x WITH [‘pc = 10]

step(x,y) : bool = step10(x,y) OR step11(x,y)

(30)

30 Concurrent Programs in PVS

We have now formally modelled the program. We are now able to dene invariants. Invariants are predicates on the state. They should be true for the initial state, and when they are true in state x and we take a valid step to state y, they should be true in state y as well. Note that this implies that invariants are true for all reachable states. In this case we could for instance formulate the following invariant:

i_geq_zero(x) : bool = x‘i >= 0

For this invariant we then need to prove the following two theorems:

i_gr_zero_init : THEOREM i_gr_zero(init)

i_gr_zero_kept_valid : THEOREM

i_gr_zero(x) AND step(x,y) IMPLIES i_gr_zero(y)

6.2 Model of a Concurrent Program

When we introduce concurrency we have to alter the model in various ways.

First of all, we cannot assume that an increment is atomic, so our program now becomes:

Algorithm 6.2.1: Counter 10 tmp := i;

11 i := tmp + 1;

12 goto 10;

In this program, tmp is a process-private variable. Another variable which is now per process is the program counter. For each variable which we have per process instead of program-wide, we need an array of variables.

However, PVS does not support arrays. We therefore emulate an array using functions of a specic type. For instance, we denote an array of three integers as follows:

arr : [below[3] -> int]

Using this technique, we can now dene our state type:

process : TYPE = below[Nproc]

state : TYPE = [#

%global variables i : int,

%private variables pc : [process -> nat], tmp: [process -> int]

#]

For readability purposes we rst introduced the type process, which is a

nite subset of the natural numbers (because we chooseNProcto be an arbitrary, but nite, positive integer). We then use this type to index our arrays of private variables.

Since we represented our arrays by functions, we also need to initialize them as functions. PVS provides us with a syntax for lambda functions, which we can use for this purpose:

(31)

6.3 Proving an Invariant 31

init : state = (#

i := 0,

pc := (LAMBDA (p) : 10), tmp:= (LAMBDA (p) : 0)

#)

We can now dene the step function for our concurrent algorithm. Strictly speaking the form of our previous function would suce. This function would however be horribly complex, since the allowed changes to process-private vari- ables would become very hard to express.

Instead, we add an additional parameter for the process executing the step.

This means that the step functions return whether process p can make a valid step, which takes the program from state x to state y. For our example program, we now get the following step functions:

step10(p,x,y) : bool =

pc(p) = 10 AND y = x WITH [‘pc(p) = 11, ‘tmp(p) = i]

step11(p,x,y) : bool =

pc(p) = 11 AND y = x WITH [‘pc(p) = 12, ‘i = tmp(p) + 1]

step12(p,x,y) : bool =

pc(p) = 12 AND y = x WITH [‘pc(p) = 10]

step(p,x,y) : bool =

step10(p,x,y) AND step11(p,x,y) AND step12(p,x,y)

Next we can again formulate invariants. But since we now have multiple processes, we can use process parameters in the invariant. However, since this is such a simple example, we cannot actually show any useful invariants. Instead we show the form of some declarations (in whichpandqare processes, andxis the state):

inv(x) : bool = ...

inv(p,x) : bool = ...

inv(p,q,x) : bool = ...

6.3 Proving an Invariant

The process of proving an invariant is quite standard. First one proves the invariant for most steps. Because an invariant is normally kept trivially true by most steps, it is enough to use a brute force command like grindhere.1 To determine for which steps the invariant can be proved using this method, the easiest way is to prove it for all steps:

inv_list : THEOREM

inv(x) AND step(p,x,y) IMPLIES inv(y)

The next step is to execute the proof, and from the unproven branches we can easily deduce which steps were not proved using this method. We can then exclude those steps in the theorem, and by using thelistproofcommand2 we ensure that they are excluded from the proof:

inv_list : THEOREM

inv(x) AND step(p,x,y) IMPLIES inv(y)

1If one otherwise has to prove a lot of steps separately, one might go for a more sophisticated proof here. However, this is almost never needed.

2This is a command which we dened ourselves, and which is discussed in section 6.6.

(32)

32 Concurrent Programs in PVS

The only disadvantage of this method is that we need to wait a relatively long time to determine the non-trivial steps, since grind takes a long time, especially with branches which it cannot prove.

Next we create a theorem for each step that we could not prove trivially:3

inv_A : THEOREM

inv(x) AND addit_invA(x) AND addit_invB(x) AND stepA(p,x,y) IMPLIES inv(y)

This is the interesting part of the proof. The human prover needs to choose what additional invariants are needed to prove the invariant for this step. Ad- ditionally, he needs to think about the structure of the proof, which is typically non-trivial.

When we have nished these interesting steps, we need to return again to some uninteresting manual labour: we need to formulate the theorem which combines the previous ones into one, thereby proving the invariant for all steps:

inv_kept_valid : THEOREM

inv(x) AND addit_invA(x) AND addit_invB(x) AND addit_invC(x) AND ... AND stepA(p,x,y) IMPLIES inv(y)

The formulation of this theorem only requires scraping together the addi- tional invariants needed by the various steps. The proof of the theorem is trivial as well: it consists of the invocation of the previous theorems, and a few trivial proof steps likeassert,flatten, andsplit.

After we have proved all invariants, we need to formulate an iqall, which is basically a list of all invariants:

iqall(x):bool = inv(x) AND inv2(x) AND ...

We then need to prove thatiqallis initially true (usually done usinggrind):

iqall_init : THEOREM iqall(init)

Hereinitis the initial state. Next comes a very extensive and time-consuming, but utterly trivial proof: the proof thatiqallis invariant. This is proved by in- volving allkept_validtheorems, and then initializingFORALLs from the premise as necessary:4

iqall_kept_valid : THEOREM

iqall(x) and step(p,x,y) IMPLIES iqall(y)

When we have proved these last two theorems, we have proved that all invariants included iniqallare indeed invariant.

6.4 Using Invariants on the End-State

When proving an invariant stays true, using another invariant, we normally use the other invariantotherinvlike this:

inv_X : THEOREM

inv(x) AND other_inv(x) AND stepX(p,x,y) IMPLIES inv(y)

However, one might encounter situations where assumingoth_inv(y)would be more helpful than assumingoth_inv(x). Of course, assuming the latter will always suce, since we can simply repeat its own invariance proof for stepX.

However, using oth_inv(y)might make the proof more elegant.

3We can also prove invariance for multiple steps in one theorem, if we know from the context we can prove them in the same way.

4This simple explanation does not feature suchFORALLs.

(33)

6.5 Proving Other Statements 33

6.4.1 Disadvantages

When proving by hand such a practice would result in a spaghetti proof, much like using backwards gotos leads to spaghetti code.

This means it would be possible to use circlular reasoning. Naturally, this is not possible when proving with a theorem prover, since it would just reject the proof. Much in the same way, a program with backwards gotos could end up in an endless loop, and we would notice this when running the program.

Since we want to prevent such errors, and keep our proofs readable for human beings, we propose some guidelines to ensure some sanity in our proofs.

6.4.2 Guidelines

Instead of using just one monolithic iqall, we split this into iqall1, iqall2, . . . . Then foriqall2 we use the followingkept_valid:

iqall2_kept_valid : THEOREM

iqall2(x) AND iqall1(x) AND iqall1(y) IMPLIES iqall2(y)

That way we can only use the invaliantoth_invin the y state when proving invariantinv, if invis in a higheriqall. In this way we keep our proofs sane, in much the same way as forbidding backwards gotos keeps programs sane.

As a last step we of course still need to deneiqall:

iqall(x):bool = iqall1(x) AND iqall2(x) AND ...

The proof ofiqall_kept_validfollows trivially from invokingiqall1_kept_valid,

iqall2_kept_valid, etc. in order.

6.5 Proving Other Statements

Some statements are harder to prove using invariants. Instead we formulate them as seperate theorems, which assume onlyiqall. We then prove statements from this assumption using a normal proof structure of lemmas and theorems.

Examples are the proof of Freedom from Deadlock and Freedom from Starvation, which we present later.

6.6 Interesting Commands we Devised

To make our proofs easier we developed several time-saving proof commands.

We discuss them here, and explain what they do, but not show their denition.

If one is interested, please refer to thepvs-strategiesle.

6.6.1 expand-all-steps

This command expands the denition of step, and then the denitions of the individual steps, and, if applicable, the associated nextvalues5. This allows you to either unpack the whole step-function, or to unpack a single step using only one command.

5Instead of dening the whole step at once, we dene it by taking the conjunction of the condition on the program counter and the assignment of the nextfunction to the end-state.

(34)

34 Concurrent Programs in PVS

6.6.2 expand-only-steps

This commands expandsstepand the individual steps, but not the next-functions.

This is useful when a lot of steps are not possible, and we want to do an as- sume, before expanding the remaining next-functions (very big formulas slow down PVS signicantly).

6.6.3 splitgrind and splitgrind+

Splitgrind takes a sequent-number, splits that sequent and then grinds in each branch. Splitgrind+ takes no parameters, but repeatedly splits, then repeatedly

attens and asserts, and nally grinds.

6.6.4 assume

Assume is a replacement for case. Instead of having to prove the assumption, we grind it. This is really only a convenient notation, but it saves us from proving a lot of simple side-proofs.

(35)

35

Chapter 7

Mechanical Proof of Mutual Exclusion

To prove mutual exclusion we rst need to prove some invariants regarding the semaphores. We rst prove those, and then show how we prove mutual exclusion using those invariants. We describe the proofs of both algorithms simultaneously, as they are very similar.1

7.1 Methodology

To prove statements about the algorithm we use invariants. An invariant is a statement which is always true during every execution of the program. We prove that this is the case by proving that all invariants are initially true, and proving that if the conjunction of all invariants is true, and we take any valid step, then the conjunction of all invariants is again true. From this we can then prove various statements.

7.2 Generic Proof

To prove mutual exclusion we rst need to prove that the semaphores behave as we would expect. In this case we have binary semaphores and split binary semaphores. For the semaphores we need to prove invariant SEM, and for split binary semaphores we need to prove SSEM.

It is easy to see that those invariants indeed are true. They are initially true, since initially all processes are outside the guarded regions, and the semaphore is 1 (or in case of a split semaphore: one of the semaphores is 1). Furthermore, all steps which change the value of the semaphore also change the number of processes in the guarded section, and keep the invariant valid.

The next step is formulating mutual exclusion:

pin guarded section ∧ q in guarded section =⇒ p = q (MUTEX)

It's easy to see that mutual exclusion follows directly from the semaphore invariant, for all guarded sections, since the semaphores are natural numbers.

Lastly, mutual exclusion in the critical section follows since the critical section is embedded in the guarded section.

1This chapter and the following chapters only give an overview for the proof. They provide the essence of the proof, but don't go into all the details needed for an automated proof. For the full proof, read the PVS source with the appendices A and B and these chapters to guide you through the PVS proof.

(36)

36 Mechanical Proof of Mutual Exclusion

In the following two sections we show the invariants used for the two algor- itms.

7.3 Udding's Algorithm

Based on the general description for the proof we can now formulate the specic invariants needed for the algoritm by Udding.

queue + #procqueue= 1 (U:S1)

enter + mutex + #procenter+ #procmutex= 1 (U:S2)

If all the semaphores were regular semaphores this would be directly provable, but since enter is not, and due to the denition of procenter we need an extra invariant to prove that invariant U:S2 stays true when V(enter) is performed:

p ∈ blockedenter=⇒ pc(p) = 11 ∨ pc(p) = 22 (U:B1)

From U:S1 and U:S2 we can directly derive the needed mutual exclusion invari- ants:

p ∈ procqueue∧ q ∈ procqueue=⇒ p = q (U:M1)

(p ∈ procenter∨ p ∈ procmutex)

∧(q ∈ procenter∨ q ∈ procmutex) (U:M2)

=⇒ p = q

7.4 Morris' Algorithm

In the same way we derive the following invariants for the algorithm by Morris b + #procb= 1

(M:S1)

a + m + #proca+ #procm= 1 (M:S2)

Which in their turn need the following additional invariant:

p ∈ blockedb=⇒ pc(p) = 11 ∨ pc(p) = 24 (M:B1)

Then we can again directly prove the mutual exclusion invariants:

p ∈ procb∧ q ∈ procb=⇒ p = q (M:M1)

(p ∈ proca∨ p ∈ procm)

∧(q ∈ proca∨ q ∈ procm) (M:M2)

=⇒ p = q

(37)

37

Chapter 8

Mechanical Proof of Freedom from Deadlock

In this chapter we give the proof of Freedom from Deadlock for both algorithms.

First we give a general overview of the proof, and then we go into specics for both algorithms separately.

8.1 General Idea of the Proof

First note that deadlock can only occur if all processes are blocked on a P operation. Then, we can rene this case to the dierent possible congurations of how the processes are spread over the several P operations. Next we can quickly deduce from invariants that deadlock is not possible. However, the details for Morris' algorithm and Udding's algorithm are quite dierent and we therefore deal with them separately.

Since we use a lot of invariants in this chapter, and the following, we have provided you with appendices A and B which list all invariants used in the PVS proofs. We do not introduce all of them in the text, since some are very trivial.

We also provide a little pointer in the margin to where the invariant was dened, so you can easily look up the place where the invariant was rst introduced.

8.2 Udding's Algorithm

We start by proving that if there is deadlock, then all processes need to be blocked on a P operation:

Theorem 8.1. If there is deadlock, all processes are on line 11, 20, 22, or 40.

Proof. This is of course true, since otherwise one of the processes could make a step, and then there would be no deadlock. We prove this by assuming that each process has a program counter which is set to a valid line of the program, and we then prove that the program counter must be equal to one of the lines where a process is blocked on or about to execute a P operation (lines 11, 20, 22, and 40). This is trivial, because if there would be a process on another line, it would be able to make a step.

Next we prove that it is not possible in case of deadlock that there are both processes blocked on mutex and on enter. Either all processes are blocked on a P operation on mutex or queue, or all processes are blocked on a P operation on enter or queue:

(38)

38 Mechanical Proof of Freedom from Deadlock

Theorem 8.2. If there is deadlock, and all processes are on line 11, 20, 22, or 40, then either all processes are on line 20 or 40, or all processes are on line 11, 20, or 22.

Proof. This is easily seen to be true, when we look at invariant U:S2: if all U:S2 p.36

processes are blocked on a P operation, then both #procenter and #procmutex

are zero, and therefore either enter or mutex is 1.1 However, we do need to prove that if all processes on line 11 and 22 are blocked (if they all are in blockedenter) that then enter must be 0, and therefore mutex is 1. To this end we use the following invariant:

pc(p) = 11 ∨ pc(p) = 22 =⇒ enter = 0 (U:B2)

The invariant holds because a process only goes to line 11 or 22 if enter is zero. Furthermore the V(enter) never releases a process from one of those lines as long as a process is waiting, and does not increase enter until no process is on either of those two lines, and therefore does not invalidate the invariant either.

We now look at the two possible cases of distribution of processes over the P statements, and prove that in neither case we can get deadlock.

8.2.1 No Deadlock on Enter

The rst case is that all processes are blocked on either line 11, 20 or 22. We rst prove that if there are processes blocked on line 20, then there must necessarily be a process blocked on line 22, or there is no deadlock:

Theorem 8.3. If there is deadlock, and there is a process on line 20, there must be a process on line 22.

Proof. This is true because a process can only be blocked on line 20 if there are processes in the guarded section of queue, and a process in the guarded section of queue is either on line 22, or is able to make a step (in which case there is no deadlock).

From this we can directly deduce the following corollary:

Corollary 8.4. If all processes are on line 20, there is no deadlock.

Lastly we prove that if we have deadlock and all processes are on line 11, 20 and 22, then necessarily all processes must be on line 20 (that is: deadlock on enteris not possible):

Theorem 8.5. If there is deadlock, and all processes are on line 11, 20 and 22, then all processes are on line 20.

Proof. We rst note that nm must be zero, since otherwise processes must have increased nm but not yet decreased it, and therefore be between lines 25 and 42. However, all processes are on lines 11, 20 and 22, so this cannot be. To formally express this meaning of nm we introduce the following invariant:

nm = #betweennm

(U:NM1)

Next we use the following invariant to show that then mutex must also be zero:

nm = 0 =⇒ mutex = 0 (U:NM2)

Referenties

GERELATEERDE DOCUMENTEN

However results did not show that the motive of low power people to gossip negatively was anxiety, also the study did not find an increase in anxiety when the personality trait

(Additional degeneracies, such äs the valley degeneracy in Si, are ignored.) The integrand is the product of three terms: ( l ) The classical probability density C ( t ) of return

Dit beteken dus dat die mense wat die gebooie hou of Jesus se woord bewaar (soos dit deur die outeur as verteenwoordiger van die tradisie geformuleer word) ook diegene is wat

The hoping and praying that the situation is only temporary can be readily appreciated and understood by most people. The quahfying statement at the end, however, is

Recently we have established the existence and uniqueness of weak solutions to a two-phase reaction-diffusion system with a free boundary where an aggressive fast reaction

Copyright and moral rights for the publications made accessible in the public portal are retained by the authors and/or other copyright owners and it is a condition of

Consequently, the number of attempts to collect semen was high around the autumn equinox compared to the other study periods, while attempts to collect semen

To stipulate the physical significanee of the reduction of the Zeeman splitting with reducing distance to the cascaded are, we give in figure 15 the pressure