]> git.lizzy.rs Git - plan9front.git/blob - sys/doc/sleep.ms
kernel: fix multiple devproc bugs and pid reuse issues
[plan9front.git] / sys / doc / sleep.ms
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.