sleep.ms 15 KB

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