Subversion Repositories planix.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
.HTML "Process Sleep and Wakeup on a Shared-memory Multiprocessor
2
.TL
3
Process Sleep and Wakeup on a Shared-memory Multiprocessor
4
.AU
5
Rob Pike
6
Dave Presotto
7
Ken Thompson
8
Gerard Holzmann
9
.sp
10
rob,presotto,ken,gerard@plan9.bell-labs.com
11
.AB
12
.FS
13
Appeared in a slightly different form in
14
.I
15
Proceedings of the Spring 1991 EurOpen Conference,
16
.R
17
Tromsø, Norway, 1991, pp. 161-166.
18
.FE
19
The problem of enabling a `sleeping' process on a shared-memory multiprocessor
20
is a difficult one, especially if the process is to be awakened by an interrupt-time
21
event.  We present here the code
22
for sleep and wakeup primitives that we use in our multiprocessor system.
23
The code has been exercised by years of active use and by a verification
24
system.
25
.AE
26
.LP
27
Our problem is to synchronise processes on a symmetric shared-memory multiprocessor.
28
Processes suspend execution, or
29
.I sleep,
30
while awaiting an enabling event such as an I/O interrupt.
31
When the event occurs, the process is issued a
32
.I wakeup
33
to resume its execution.
34
During these events, other processes may be running and other interrupts
35
occurring on other processors.
36
.LP
37
More specifically, we wish to implement subroutines called
38
.CW sleep ,
39
callable by a process to relinquish control of its current processor,
40
and
41
.CW wakeup ,
42
callable by another process or an interrupt to resume the execution
43
of a suspended process.
44
The calling conventions of these subroutines will remain unspecified
45
for the moment.
46
.LP
47
We assume the processors have an atomic test-and-set or equivalent
48
operation but no other synchronisation method.  Also, we assume interrupts
49
can occur on any processor at any time, except on a processor that has
50
locally inhibited them.
51
.LP
52
The problem is the generalisation to a multiprocessor of a familiar
53
and well-understood uniprocessor problem.  It may be reduced to a
54
uniprocessor problem by using a global test-and-set to serialise the
55
sleeps and wakeups,
56
which is equivalent to synchronising through a monitor.
57
For performance and cleanliness, however,
58
we prefer to allow the interrupt handling and process control to be multiprocessed.
59
.LP
60
Our attempts to solve the sleep/wakeup problem in Plan 9
61
[Pik90]
62
prompted this paper.
63
We implemented solutions several times over several months and each
64
time convinced ourselves \(em wrongly \(em they were correct.
65
Multiprocessor algorithms can be
66
difficult to prove correct by inspection and formal reasoning about them
67
is impractical.  We finally developed an algorithm we trust by
68
verifying our code using an
69
empirical testing tool.
70
We present that code here, along with some comments about the process by
71
which it was designed.
72
.SH
73
History
74
.LP
75
Since processes in Plan 9 and the UNIX
76
system have similar structure and properties, one might ask if
77
UNIX
78
.CW sleep
79
and
80
.CW wakeup
81
[Bac86]
82
could not easily be adapted from their standard uniprocessor implementation
83
to our multiprocessor needs.
84
The short answer is, no.
85
.LP
86
The
87
UNIX
88
routines
89
take as argument a single global address
90
that serves as a unique
91
identifier to connect the wakeup with the appropriate process or processes.
92
This has several inherent disadvantages.
93
From the point of view of
94
.CW sleep
95
and
96
.CW wakeup ,
97
it is difficult to associate a data structure with an arbitrary address;
98
the routines are unable to maintain a state variable recording the
99
status of the event and processes.
100
(The reverse is of course easy \(em we could
101
require the address to point to a special data structure \(em
102
but we are investigating
103
UNIX
104
.CW sleep
105
and
106
.CW wakeup ,
107
not the code that calls them.)
108
Also, multiple processes sleep `on' a given address, so
109
.CW wakeup
110
must enable them all, and let process scheduling determine which process
111
actually benefits from the event.
112
This is inefficient;
113
a queueing mechanism would be preferable
114
but, again, it is difficult to associate a queue with a general address.
115
Moreover, the lack of state means that
116
.CW sleep
117
and
118
.CW wakeup
119
cannot know what the corresponding process (or interrupt) is doing;
120
.CW sleep
121
and
122
.CW wakeup
123
must be executed atomically.
124
On a uniprocessor it suffices to disable interrupts during their
125
execution.
126
On a multiprocessor, however,
127
most processors
128
can inhibit interrupts only on the current processor,
129
so while a process is executing
130
.CW sleep
131
the desired interrupt can come and go on another processor.
132
If the wakeup is to be issued by another process, the problem is even harder.
133
Some inter-process mutual exclusion mechanism must be used,
134
which, yet again, is difficult to do without a way to communicate state.
135
.LP
136
In summary, to be useful on a multiprocessor,
137
UNIX
138
.CW sleep
139
and
140
.CW wakeup
141
must either be made to run atomically on a single
142
processor (such as by using a monitor)
143
or they need a richer model for their communication.
144
.SH
145
The design
146
.LP
147
Consider the case of an interrupt waking up a sleeping process.
148
(The other case, a process awakening a second process, is easier because
149
atomicity can be achieved using an interlock.)
150
The sleeping process is waiting for some event to occur, which may be
151
modeled by a condition coming true.
152
The condition could be just that the event has happened, or something
153
more subtle such as a queue draining below some low-water mark.
154
We represent the condition by a function of one
155
argument of type
156
.CW void* ;
157
the code supporting the device generating the interrupts
158
provides such a function to be used by
159
.CW sleep
160
and
161
.CW wakeup
162
to synchronise.  The function returns
163
.CW false
164
if the event has not occurred, and
165
.CW true
166
some time after the event has occurred.
167
The
168
.CW sleep
169
and
170
.CW wakeup
171
routines must, of course, work correctly if the
172
event occurs while the process is executing
173
.CW sleep .
174
.LP
175
We assume that a particular call to
176
.CW sleep
177
corresponds to a particular call to
178
.CW wakeup ,
179
that is,
180
at most one process is asleep waiting for a particular event.
181
This can be guaranteed in the code that calls
182
.CW sleep
183
and
184
.CW wakeup
185
by appropriate interlocks.
186
We also assume for the moment that there will be only one interrupt
187
and that it may occur at any time, even before
188
.CW sleep
189
has been called.
190
.LP
191
For performance,
192
we desire that multiple instances of
193
.CW sleep
194
and
195
.CW wakeup
196
may be running simultaneously on our multiprocessor.
197
For example, a process calling
198
.CW sleep
199
to await a character from an input channel need not
200
wait for another process to finish executing
201
.CW sleep
202
to await a disk block.
203
At a finer level, we would like a process reading from one input channel
204
to be able to execute
205
.CW sleep
206
in parallel with a process reading from another input channel.
207
A standard approach to synchronisation is to interlock the channel `driver'
208
so that only one process may be executing in the channel code at once.
209
This method is clearly inadequate for our purposes; we need
210
fine-grained synchronisation, and in particular to apply
211
interlocks at the level of individual channels rather than at the level
212
of the channel driver.
213
.LP
214
Our approach is to use an object called a
215
.I rendezvous ,
216
which is a data structure through which
217
.CW sleep
218
and
219
.CW wakeup
220
synchronise.
221
(The similarly named construct in Ada is a control structure;
222
ours is an unrelated data structure.)
223
A rendezvous
224
is allocated for each active source of events:
225
one for each I/O channel,
226
one for each end of a pipe, and so on.
227
The rendezvous serves as an interlockable structure in which to record
228
the state of the sleeping process, so that
229
.CW sleep
230
and
231
.CW wakeup
232
can communicate if the event happens before or while
233
.CW sleep
234
is executing.
235
.LP
236
Our design for
237
.CW sleep
238
is therefore a function
239
.P1
240
void sleep(Rendezvous *r, int (*condition)(void*), void *arg)
241
.P2
242
called by the sleeping process.
243
The argument
244
.CW r
245
connects the call to
246
.CW sleep
247
with the call to
248
.CW wakeup ,
249
and is part of the data structure for the (say) device.
250
The function
251
.CW condition
252
is described above;
253
called with argument
254
.CW arg ,
255
it is used by
256
.CW sleep
257
to decide whether the event has occurred.
258
.CW Wakeup
259
has a simpler specification:
260
.P1
261
void wakeup(Rendezvous *r).
262
.P2
263
.CW Wakeup
264
must be called after the condition has become true.
265
.SH
266
An implementation
267
.LP
268
The
269
.CW Rendezvous
270
data type is defined as
271
.P1
272
typedef struct{
273
	Lock	l;
274
	Proc	*p;
275
}Rendezvous;
276
.P2
277
Our
278
.CW Locks
279
are test-and-set spin locks.
280
The routine
281
.CW lock(Lock\ *l)
282
returns when the current process holds that lock;
283
.CW unlock(Lock\ *l)
284
releases the lock.
285
.LP
286
Here is our implementation of
287
.CW sleep .
288
Its details are discussed below.
289
.CW Thisp
290
is a pointer to the current process on the current processor.
291
(Its value differs on each processor.)
292
.P1
293
void
294
sleep(Rendezvous *r, int (*condition)(void*), void *arg)
295
{
296
	int s;
297
 
298
	s = inhibit();		/* interrupts */
299
	lock(&r->l);
300
 
301
	/*
302
	 * if condition happened, never mind
303
	 */
304
	if((*condition)(arg)){	
305
		unlock(&r->l);
306
		allow();	/* interrupts */
307
		return;
308
	}
309
 
310
	/*
311
	 * now we are committed to
312
	 * change state and call scheduler
313
	 */
314
	if(r->p)
315
		error("double sleep %d %d", r->p->pid, thisp->pid);
316
	thisp->state = Wakeme;
317
	r->p = thisp;
318
	unlock(&r->l);
319
	allow(s);	/* interrupts */
320
	sched();	/* relinquish CPU */
321
}
322
.P2
323
.ne 3i
324
Here is
325
.CW wakeup.
326
.P1
327
void
328
wakeup(Rendezvous *r)
329
{
330
	Proc *p;
331
	int s;
332
 
333
	s = inhibit();	/* interrupts; return old state */
334
	lock(&r->l);
335
	p = r->p;
336
	if(p){
337
		r->p = 0;
338
		if(p->state != Wakeme)
339
			panic("wakeup: not Wakeme");
340
		ready(p);
341
	}
342
	unlock(&r->l);
343
	if(s)
344
		allow();
345
}
346
.P2
347
.CW Sleep
348
and
349
.CW wakeup
350
both begin by disabling interrupts
351
and then locking the rendezvous structure.
352
Because
353
.CW wakeup
354
may be called in an interrupt routine, the lock must be set only
355
with interrupts disabled on the current processor,
356
so that if the interrupt comes during
357
.CW sleep
358
it will occur only on a different processor;
359
if it occurred on the processor executing
360
.CW sleep ,
361
the spin lock in
362
.CW wakeup
363
would hang forever.
364
At the end of each routine, the lock is released and processor priority
365
returned to its previous value.
366
.CW Wakeup "" (
367
needs to inhibit interrupts in case
368
it is being called by a process;
369
this is a no-op if called by an interrupt.)
370
.LP
371
.CW Sleep
372
checks to see if the condition has become true, and returns if so.
373
Otherwise the process posts its name in the rendezvous structure where
374
.CW wakeup
375
may find it, marks its state as waiting to be awakened
376
(this is for error checking only) and goes to sleep by calling
377
.CW sched() .
378
The manipulation of the rendezvous structure is all done under the lock,
379
and
380
.CW wakeup
381
only examines it under lock, so atomicity and mutual exclusion
382
are guaranteed.
383
.LP
384
.CW Wakeup
385
has a simpler job.  When it is called, the condition has implicitly become true,
386
so it locks the rendezvous, sees if a process is waiting, and readies it to run.
387
.SH
388
Discussion
389
.LP
390
The synchronisation technique used here
391
is similar to known methods, even as far back as Saltzer's thesis
392
[Sal66].
393
The code looks trivially correct in retrospect: all access to data structures is done
394
under lock, and there is no place that things may get out of order.
395
Nonetheless, it took us several iterations to arrive at the above
396
implementation, because the things that
397
.I can
398
go wrong are often hard to see.  We had four earlier implementations
399
that were examined at great length and only found faulty when a new,
400
different style of device or activity was added to the system.
401
.LP
402
.ne 3i
403
Here, for example, is an incorrect implementation of wakeup,
404
closely related to one of our versions.
405
.P1
406
void
407
wakeup(Rendezvous *r)
408
{
409
	Proc *p;
410
	int s;
411
 
412
	p = r->p;
413
	if(p){
414
		s = inhibit();
415
		lock(&r->l);
416
		r->p = 0;
417
		if(p->state != Wakeme)
418
			panic("wakeup: not Wakeme");
419
		ready(p);
420
		unlock(&r->l);
421
		if(s)
422
			allow();
423
	}
424
}
425
.P2
426
The mistake is that the reading of
427
.CW r->p
428
may occur just as the other process calls
429
.CW sleep ,
430
so when the interrupt examines the structure it sees no one to wake up,
431
and the sleeping process misses its wakeup.
432
We wrote the code this way because we reasoned that the fetch
433
.CW p
434
.CW =
435
.CW r->p
436
was inherently atomic and need not be interlocked.
437
The bug was found by examination when a new, very fast device
438
was added to the system and sleeps and interrupts were closely overlapped.
439
However, it was in the system for a couple of months without causing an error.
440
.LP
441
How many errors lurk in our supposedly correct implementation above?
442
We would like a way to guarantee correctness; formal proofs are beyond
443
our abilities when the subtleties of interrupts and multiprocessors are
444
involved.
445
With that in mind, the first three authors approached the last to see
446
if his automated tool for checking protocols
447
[Hol91]
448
could be
449
used to verify our new
450
.CW sleep
451
and
452
.CW wakeup
453
for correctness.
454
The code was translated into the language for that system
455
(with, unfortunately, no way of proving that the translation is itself correct)
456
and validated by exhaustive simulation.
457
.LP
458
The validator found a bug.
459
Under our assumption that there is only one interrupt, the bug cannot
460
occur, but in the more general case of multiple interrupts synchronising
461
through the same condition function and rendezvous,
462
the process and interrupt can enter a peculiar state.
463
A process may return from
464
.CW sleep
465
with the condition function false
466
if there is a delay between
467
the condition coming true and
468
.CW wakeup
469
being called,
470
with the delay occurring
471
just as the receiving process calls
472
.CW sleep .
473
The condition is now true, so that process returns immediately,
474
does whatever is appropriate, and then (say) decides to call
475
.CW sleep
476
again.  This time the condition is false, so it goes to sleep.
477
The wakeup process then finds a sleeping process,
478
and wakes it up, but the condition is now false.
479
.LP
480
There is an easy (and verified) solution: at the end of
481
.CW sleep
482
or after
483
.CW sleep
484
returns,
485
if the condition is false, execute
486
.CW sleep
487
again.  This re-execution cannot repeat; the second synchronisation is guaranteed
488
to function under the external conditions we are supposing.
489
.LP
490
Even though the original code is completely
491
protected by interlocks and had been examined carefully by all of us
492
and believed correct, it still had problems.
493
It seems to us that some exhaustive automated analysis is
494
required of multiprocessor algorithms to guarantee their safety.
495
Our experience has confirmed that it is almost impossible to
496
guarantee by inspection or simple testing the correctness
497
of a multiprocessor algorithm.  Testing can demonstrate the presence
498
of bugs but not their absence
499
[Dij72].
500
.LP
501
We close by claiming that the code above with
502
the suggested modification passes all tests we have for correctness
503
under the assumptions used in the validation.
504
We would not, however, go so far as to claim that it is universally correct.
505
.SH
506
References
507
.LP
508
[Bac86] Maurice J. Bach,
509
.I "The Design of the UNIX Operating System,
510
Prentice-Hall,
511
Englewood Cliffs,
512
1986.
513
.LP
514
[Dij72] Edsger W. Dijkstra,
515
``The Humble Programmer \- 1972 Turing Award Lecture'',
516
.I "Comm. ACM,
517
15(10), pp. 859-866, 
518
October 1972.
519
.LP
520
[Hol91] Gerard J. Holzmann,
521
.I "Design and Validation of Computer Protocols,
522
Prentice-Hall,
523
Englewood Cliffs,
524
1991.
525
.LP
526
[Pik90]
527
Rob Pike,
528
Dave Presotto,
529
Ken Thompson,
530
Howard Trickey,
531
``Plan 9 from Bell Labs'',
532
.I "Proceedings of the Summer 1990 UKUUG Conference,
533
pp. 1-9,
534
London,
535
July, 1990.
536
.LP
537
[Sal66] Jerome H. Saltzer,
538
.I "Traffic Control in a Multiplexed Computer System
539
MIT,
540
Cambridge, Mass.,
541
1966.