A Formal Specification A
Total Page:16
File Type:pdf, Size:1020Kb
Synchronization Primitives for a Multiprocessor: A Formal Specification A. D. Birrell, J. V. Guttag,* J. J. Horning, R. Levin Digital Equipment Corporation, Systems Research Center 180 Lytton Avenue, Palo Alto, CA 94801, U.S.A. Abstract descriptions. It can be made more precise and complete, and is more likely to be interpreted consistently by various Formal specifications of operating system interfaces can readers. Our experience in specifying and documenting the be a useful part of their documentation. We illustrate this synchronization facilities of DEC/SRC's Threads package by documenting the Threads synchronization primitives of supports this view. the Taos operating system. We start with an informal description, present a way to formally specify interfaces The Threads package implements concurrent threads in concurrent systems, give a formal specification of the of controlt for the Taos operating system [McJones 87] synchronization primitives, briefly discuss the implemen- and application programs running on our Firefly multi- tation, and conclude with a discussion of what we have processor workstation [Thacker 87]. The package supports learned from using the specification for more than a year. large numbers of concurrent threads, and permits multiple concurrent threads within an address space. The synchro- Introduction nization facilities we describe permit threads to cooperate in the use of shared memory. The careful documentation of interfaces is an important step in the production of software upon which other soft- We begin with an informal description of the Threads ware is to be built. If people are to use software without synchronization primitives. These are similar to those in having to understand its implementation, documentation many other systems, but their use on a multiprocessor must convey semantic as well as syntactic information. raises questions about their precise semantics that are When the software involves concurrency, adequate docu- difficult to answer using informal descriptions. mentation is particularly hard to produce, since the range We briefly describe the formal language we use to of possible behaviors is likely to be large and difficult to specify interfaces involving concurrency, and then present characterize [Jones 83]. the specification itself. We intend to give the reader We believe that operating system documentation con- a complete and precise understanding of the properties taining formal specifications can be significantly better that client programmers may rely on when using the than documentation restricted to informal or semi-formal Threads synchronization primitives. The specification should answer any questions about how our primitives * Permanent address: MIT Laboratory for Computer differ from others with which the reader is familiar. Science, 545 Technology Square, Cambridge, MA 02139. We next discuss the implementation of Threads. It is This research was supported in part by the Advanced chiefly interesting for the way efficiency is obtained despite Research Projects Agency of the Department of Defense, limited hardware support. The need for efficiency led to monitored by the Office of Naval Research under contract an implementation that has a rather different structure N00014-83-K-0125, and by the National Science Founda- than the specification might suggest. This illustrates how tion under grant DCR-8411639. specifications can protect clients from needing to know implementation details. Finally, we discuss how the formal specification has been and is being used by programmers. t ~Lightweight processes"; we avoid the word "process" because of its connotation of "address space" in some operating systems. [Saltzer 66] credits the term "thread" to V. Vyssotsky. Permission to copy without fee all or part of this material is granted provided that the copies are not made or distributed for direct commercial advantage, the ACM copyright notice and the title of the publication and its date appear, and notice is given that copying is by permission of the Association for Computing Machinery. To copy otherwise, or to republish, requires a fee and/or specfic permission. © 1987 ACM 089791-242-X/87/0011/0094 $1.50 94 Informal Description This syntax encourages the use of correctly bracketed occurrences of Acquire and Release. It also makes it easy The Threads package provides a Modula-2+ [Rovner 86] for the compiler to produce highly optimized code for such interface for creating and controlling a virtually unlimited occurrences, (The TRY... FINALLY construct ensures that number of threads, which may or may not share memory. Release will be called regardless of whether control leaves This paper is concerned only with its synchronization the statement-sequence because it has been completed or facilities. These are rather simple, and are derived from the because an exception has been raised.) Other uses of concepts of monitors and condition variables first outlined Acquire and Release are generally discouraged. by Hoare [Hoare 74]. Their semantics are similar to those provided by Mesa [Lampson 80]. The synchronization Condition Variables: Wait, Signal, Broadcast facilities use three main types: Mutex, Condition, and Condition variables make it possible for a thread to Semaphore. suspend its execution while awaiting an action by some As far as clients of Threads are concerned, all threads other thread. For example, the consumer in a producer- can execute concurrently. The Threads implementation is consumer algorithm might need to wait for the producer. responsible for assigning threads to real processors. The Or the implementation of a higher level locking scheme way in which this assignment is made affects performance, might require that some threads wait until a lock is but does not affect the semantics of the synchronization available. primitives. The programmer can reason as if there were The normal paradigm for using condition variables is as many processors as threads. The Threads package also as follows. A condition variable "c" is always associated includes facilities for affecting the assignment of threads with some shared variables protected by a mutex "m" and to real processors (for example, a simple priority scheme), a predicate based on those shared variables. A thread but our specification is independent of these facilities. acquires m (i.e., enters a critical section) and evaluates Mutual Exclusion: Acquire, Release the predicate to see if it should call Wait(m, c) to suspend its execution. This call atomically releases the mutex (i.e., A mutex [Dijkstra 68] is the basic tool enabling threads ends the critical section) and suspends execution of that to cooperate on access to shared variables. A mutex is thread. normally used to achieve an effect similar to monitors, After any thread changes the shared variables so that ensuring that a set of actions on a group of variables can o's predicate might be satisfied, it calls Signal(c) or be made atomic relative to any other thread's actions on Broadcast(c). (Although the changes may be made only these variables. Atomicity can be achieved by arranging within a critical section, the thread may exit the critical that: section before this call.) Signal and Broadcast allow • All reads and writes of the shared variables occur blocked threads to resume execution and re-acquire the within critical sections associated with these vari- mutex. When a thread returns from Wait it is in a ables. new critical section. It re-evaluates the predicate and • Each critical section is executed from start to finish determines whether to proceed or to call Wait again. without any other thread entering a critical section There are several subtleties in the semantics of these associated with these variables. procedures, and it is difficult to express them correctly in • Each action that is to be atomic is entirely con- an informal description. This is the area where we have tained within a single critical section. found the formal specification most useful. Such mutual exclusion completely serializes the critical • Even if threads take care to call Signal only when sections, and hence the atomic actions they contain. It can the predicate is true, it may become false before be implemented using the procedures Acquire and Release. a waiting thread resumes execution. Some other A mutex "m" is associated with the set of variables, thread may enter a critical section first and in- and each critical section is bracketed by Acquire(m) and validate the predicate. Therefore when a thread Release(m) actions. The semantics of Acquire and Release returns from a Wait it must re-evaluate its predicate ensure that these bracketed sections are indeed critical and be prepared to call Wait again. Return from sections. Wait is only a hint [Lampson 84] that must be Critical sections are so frequently useful that Modula-2+ confirmed. By contrast, with Hoare's condition includes syntactic sugar for them. The statement: variables threads are guaranteed that the predicate LOCK e DO statement-sequence END is true on return from Wait. Our looser specification is equivalent to: reduces the obligations of the signalling thread and LET m=e; leads to a more efficient implementation on our Acquire (m); multiprocessor. TRY statement-sequence The two things that Wait(m, c) must do first--leave FINALLY Release(m) the critical section and suspend execution of the END thread--must be in one atomic action relative to 95 any call of Signal. The following sequence would It allows a thread to request that another thread desist be incorrect: one thread leaves its critical section; from a computation. It is generally used in situations then another thread enters a critical section, mod- where the decision to make this request happens at an ifies the shared variables, and calls Signal (which abstraction level higher than that in which the thread finds nothing to be unblocked); and then the first is blocked, so that the appropriate condition variable or thread suspends execution. This is familiar to most semaphore is not readily accessible. operating system designers as a ~vakeup-waiting Calling Alert(t) is a request that the thread t raise race" [Saltzer 66].