forked from mit-pdos/xv6-book
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sched.t
1744 lines (1742 loc) · 43.8 KB
/
sched.t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
.ig
cox and mullender, semaphores.
process has one thread with two stacks
pike et al, sleep and wakeup
..
.chapter CH:SCHED "Scheduling"
.PP
Any operating system is likely to run with more processes than the
computer has processors, so a plan is needed to time-share the
processors among the processes. Ideally the sharing would be transparent to user
processes. A common approach is to provide each process
with the illusion that it has its own virtual processor by
.italic-index multiplexing
the processes onto the hardware processors.
This chapter explains how xv6 achieves this multiplexing.
.\"
.section "Multiplexing"
.\"
.PP
Xv6 multiplexes by switching each processor from one process to
another in two situations. First, xv6's
.code sleep
and
.code wakeup
mechanism switches when a process waits
for device or pipe I/O to complete, or waits for a child
to exit, or waits in the
.code sleep
system call.
Second, xv6 periodically forces a switch when a process
is executing user instructions.
This multiplexing creates the illusion that
each process has its own CPU, just as xv6 uses the memory allocator and hardware
page tables to create the illusion that each process has its own memory.
.PP
Implementing multiplexing poses a few challenges. First, how to switch from one
process to another?
Although the idea of context switching
is simple, the implementation is some of the most opaque code
in xv6. Second, how to switch transparently to user processes? Xv6 uses the
standard technique of driving context switches with timer interrupts.
Third, many CPUs may be switching among processes concurrently, and a
locking plan is necessary to avoid races. Fourth, a process's
memory and other resources must be freed when the process exits,
but it cannot do all of this itself
because (for example) it can't free its own kernel stack while still using it.
Finally, each core of a multi-core machine must remember which
process it is executing so that system calls affect the correct
process's kernel state.
Xv6 tries to solve these problems as
simply as possible, but nevertheless the resulting code is
tricky.
.PP
xv6 must provide
ways for processes to coordinate among themselves. For example,
a parent process may need to wait for one of its children to
exit, or a process reading a pipe may need to wait for
some other process to write the pipe.
Rather than make the waiting process waste CPU by repeatedly checking
whether the desired event has happened, xv6 allows a process to give
up the CPU and sleep
waiting for an event, and allows another process to wake the first
process up. Care is needed to avoid races that result in
the loss of event notifications.
As an example of these problems and their solution, this
chapter examines the implementation of pipes.
.\"
.section "Code: Context switching"
.\"
.figure switch
.PP
.figref switch
outlines the steps involved in switching from one
user process to another:
a user-kernel transition (system
call or interrupt) to the old process's kernel thread,
a context switch to the current CPU's scheduler thread, a context
switch to a new process's kernel thread, and a trap return
to the user-level process.
The xv6 scheduler has its own thread (saved registers and stack) because
it is sometimes not safe for it execute on
any process's kernel stack;
we'll see an example in
.code exit .
In this section we'll examine the mechanics of switching
between a kernel thread and a scheduler thread.
.PP
Switching from one thread to another involves saving the old thread's
CPU registers, and restoring the previously-saved registers of the
new thread; the fact that
.register esp
and
.register eip
are saved and restored means that the CPU will switch stacks and
switch what code it is executing.
.PP
The function
.code-index swtch
performs the saves and restores for a thread switch.
.code swtch
doesn't directly know about threads; it just saves and
restores register sets, called
.italic-index "contexts" .
When it is time for a process to give up the CPU,
the process's kernel thread calls
.code swtch
to save its own context and return to the scheduler context.
Each context is represented by a
.code struct
.code context* ,
a pointer to a structure stored on the kernel stack involved.
.code Swtch
takes two arguments:
.code-index "struct context"
.code **old
and
.code struct
.code context
.code *new .
It pushes the current registers onto the stack
and saves the stack pointer in
.code *old .
Then
.code swtch
copies
.code new
to
.register esp,
pops previously saved registers, and returns.
.PP
Let's follow a user process through
.code swtch
into the scheduler.
We saw in Chapter \*[CH:TRAP]
that one possibility at the end of each interrupt
is that
.code-index trap
calls
.code-index yield .
.code Yield
in turn calls
.code-index sched ,
which calls
.code-index swtch
to save the current context in
.code proc->context
and switch to the scheduler context previously saved in
.code-index cpu->scheduler
.line proc.c:/swtch..p/ .
.PP
.code Swtch
.line swtch.S:/swtch/
starts by copying its arguments from the stack
to the caller-saved registers
.register eax
and
.register edx
.lines swtch.S:/movl/,/movl/ ;
.code-index swtch
must do this before it
changes the stack pointer
and can no longer access the arguments
via
.register esp.
Then
.code swtch
pushes the register state, creating a context structure
on the current stack.
Only the callee-saved registers need to be saved;
the convention on the x86 is that these are
.register ebp,
.register ebx,
.register esi,
.register edi,
and
.register esp.
.code Swtch
pushes the first four explicitly
.lines swtch.S:/pushl..ebp/,/pushl..edi/ ;
it saves the last implicitly as the
.code struct
.code context*
written to
.code *old
.line swtch.S:/movl..esp/ .
There is one more important register:
the program counter
.register eip .
It has already been saved on the stack by the
.code call
instruction that invoked
.code swtch .
Having saved the old context,
.code swtch
is ready to restore the new one.
It moves the pointer to the new context
into the stack pointer
.line swtch.S:/movl..edx/ .
The new stack has the same form as the old one that
.code-index swtch
just left—the new stack
.italic was
the old one in a previous call to
.code swtch —\c
so
.code swtch
can invert the sequence to restore the new context.
It pops the values for
.register edi,
.register esi,
.register ebx,
and
.register ebp
and then returns
.lines swtch.S:/popl/,/ret/ .
Because
.code swtch
has changed the stack pointer, the values restored
and the instruction address returned to
are the ones from the new context.
.PP
In our example,
.code-index sched
called
.code-index swtch
to switch to
.code-index cpu->scheduler ,
the per-CPU scheduler context.
That context had been saved by
.code scheduler 's
call to
.code swtch
.line 'proc.c:/swtch.&.c/' .
When the
.code-index swtch
we have been tracing returns,
it returns not to
.code sched
but to
.code-index scheduler ,
and its stack pointer points at the current CPU's
scheduler stack.
.\"
.section "Code: Scheduling"
.\"
.PP
The last section looked at the low-level details of
.code-index swtch ;
now let's take
.code swtch
as a given and examine
switching from a process through the scheduler to another process.
A process
that wants to give up the CPU must
acquire the process table lock
.code-index ptable.lock ,
release any other locks it is holding,
update its own state
.code proc->state ), (
and then call
.code-index sched .
.code Yield
.line proc.c:/^yield/
follows this convention, as do
.code-index sleep
and
.code-index exit ,
which we will examine later.
.code Sched
double-checks those conditions
.lines "'proc.c:/if..holding/,/running/'"
and then an implication of those conditions:
since a lock is held, the CPU should be
running with interrupts disabled.
Finally,
.code-index sched
calls
.code-index swtch
to save the current context in
.code proc->context
and switch to the scheduler context in
.code-index cpu->scheduler .
.code Swtch
returns on the scheduler's stack
as though
.code-index scheduler 's
.code swtch
had returned
.line 'proc.c:/swtch.&.c/' .
The scheduler continues the
.code for
loop, finds a process to run,
switches to it, and the cycle repeats.
.PP
We just saw that xv6 holds
.code-index ptable.lock
across calls to
.code swtch :
the caller of
.code-index swtch
must already hold the lock, and control of the lock passes to the
switched-to code. This convention is unusual with locks; usually
the thread that acquires a lock is also responsible for
releasing the lock, which makes it easier to reason about correctness.
For context switching it is necessary to break this convention because
.code-index ptable.lock
protects invariants on the process's
.code state
and
.code context
fields that are not true while executing in
.code swtch .
One example of a problem that could arise if
.code ptable.lock
were not held during
.code-index swtch :
a different CPU might decide
to run the process after
.code-index yield
had set its state to
.code RUNNABLE ,
but before
.code swtch
caused it to stop using its own kernel stack.
The result would be two CPUs running on the same stack,
which cannot be right.
.PP
A kernel thread always gives up its
processor in
.code sched
and always switches to the same location in the scheduler, which
(almost) always switches to some kernel thread that previously called
.code sched .
Thus, if one were to print out the line numbers where xv6 switches
threads, one would observe the following simple pattern:
.line 'proc.c:/swtch.&.c/' ,
.line proc.c:/swtch..p/ ,
.line 'proc.c:/swtch.&.c/' ,
.line proc.c:/swtch..p/ ,
and so on. The procedures in which this stylized switching between
two threads happens are sometimes referred to as
.italic-index coroutines ;
in this example,
.code-index sched
and
.code-index scheduler
are co-routines of each other.
.PP
There is one case when the scheduler's call to
.code-index swtch
does not end up in
.code-index sched .
We saw this case in Chapter \*[CH:MEM]: when a
new process is first scheduled, it begins at
.code-index forkret
.line proc.c:/^forkret/ .
.code Forkret
exists to release the
.code-index ptable.lock ;
otherwise, the new process could start at
.code trapret .
.PP
.code Scheduler
.line proc.c:/^scheduler/
runs a simple loop:
find a process to run, run it until it yields, repeat.
.code-index scheduler
holds
.code-index ptable.lock
for most of its actions,
but releases the lock (and explicitly enables interrupts)
once in each iteration of its outer loop.
This is important for the special case in which this CPU
is idle (can find no
.code RUNNABLE
process).
If an idling scheduler looped with
the lock continuously held, no other CPU that
was running a process could ever perform a context
switch or any process-related system call,
and in particular could never mark a process as
.code-index RUNNABLE
so as to break the idling CPU out of its scheduling loop.
The reason to enable interrupts periodically on an idling
CPU is that there might be no
.code RUNNABLE
process because processes (e.g., the shell) are
waiting for I/O;
if the scheduler left interrupts disabled all the time,
the I/O would never arrive.
.PP
The scheduler
loops over the process table
looking for a runnable process, one that has
.code p->state
.code ==
.code RUNNABLE .
Once it finds a process, it sets the per-CPU current process
variable
.code proc ,
switches to the process's page table with
.code-index switchuvm ,
marks the process as
.code RUNNING ,
and then calls
.code-index swtch
to start running it
.lines proc.c:/Switch.to/,/swtch/ .
.PP
One way to think about the structure of the scheduling code is
that it arranges to enforce a set of invariants about each process,
and holds
.code-index ptable.lock
whenever those invariants are not true.
One invariant is that if a process is
.code RUNNING ,
a timer interrupt's
.code-index yield
must be able to switch away from the process;
this means that the CPU registers must hold the process's register values
(i.e. they aren't actually in a
.code context ),
.register cr3
must refer to the process's pagetable,
.register esp
must refer to the process's kernel stack so that
.code swtch
can push registers correctly, and
.code proc
must refer to the process's
.code proc[]
slot.
Another invariant is that if a process is
.code-index RUNNABLE ,
an idle CPU's
.code-index scheduler
must be able to run it;
this means that
.code-index p->context
must hold the process's kernel thread variables,
that no CPU is executing on the process's kernel stack,
that no CPU's
.register cr3
refers to the process's page table,
and that no CPU's
.code proc
refers to the process.
.PP
Maintaining the above invariants is the reason why xv6 acquires
.code-index ptable.lock
in one thread (often in
.code yield)
and releases the lock in a different thread
(the scheduler thread or another next kernel thread).
Once the code has started to modify a running process's state
to make it
.code RUNNABLE ,
it must hold the lock until it has finished restoring
the invariants: the earliest correct release point is after
.code scheduler
stops using the process's page table and clears
.code proc .
Similarly, once
.code scheduler
starts to convert a runnable process to
.code RUNNING ,
the lock cannot be released until the kernel thread
is completely running (after the
.code swtch ,
e.g. in
.code yield ).
.PP
.code-index ptable.lock
protects other things as well:
allocation of process IDs and free process table slots,
the interplay between
.code-index exit
and
.code-index wait ,
the machinery to avoid lost wakeups (see next section),
and probably other things too.
It might be worth thinking about whether the
different functions of
.code ptable.lock
could be split up, certainly for clarity and perhaps
for performance.
.\"
.section "Code: mycpu and myproc"
.\"
.PP
xv6 maintains a
.code-index "struct cpu"
for each processor, which records
the process currently running
on the processor (if any),
the processor's unique hardware identifier
.code apicid ), (
and some other information.
The function
.code-index mycpu
.line proc.c:/^mycpu/
returns the current processor's
.code "struct cpu" .
.code mycpu
does this by reading the processor
identifier from the local APIC hardware and looking through
the array of
.code "struct cpu"
for an entry with that identifier.
The return value of
.code mycpu
is fragile: if the timer were to interrupt and cause
the thread to be moved to a different processor, the
return value would no longer be correct.
To avoid this problem, xv6 requires that callers of
.code mycpu
disable interrupts, and only enable
them after they finish using the returned
.code "struct cpu" .
.PP
The function
.code-index myproc
.line proc.c:/^myproc/
returns the
.code "struct proc"
pointer
for the process that is running on the current processor.
.code myproc
disables interrupts, invokes
.code mycpu ,
fetches the current process pointer
.code "c->proc" ) (
out of the
.code "struct cpu" ,
and then enables interrupts.
If there is no process running, because the the caller is
executing in
.code scheduler ,
.code myproc
returns zero.
The return value of
.code myproc
is safe to use even if interrupts are enabled:
if a timer interrupt moves the calling process to a
different processor, its
.code "struct proc"
pointer will stay the same.
.\"
.section "Sleep and wakeup"
.\"
.PP
Scheduling and locks help conceal the existence of one process
from another,
but so far we have no abstractions that help
processes intentionally interact.
Sleep and wakeup fill that void, allowing one process to
sleep waiting for an event and another process to wake it up
once the event has happened.
Sleep and wakeup are often called
.italic-index "sequence coordination"
or
.italic-index "conditional synchronization"
mechanisms, and there are many other similar mechanisms
in the operating systems literature.
.PP
To illustrate what we mean, let's consider a
simple producer/consumer queue.
This queue is similar to the one that feeds commands from processes
to the IDE driver
(see Chapter \*[CH:TRAP]), but abstracts away all
IDE-specific code.
The queue allows one process to send a nonzero pointer
to another process.
If there were only one sender and one receiver,
and they executed on different CPUs,
and the compiler didn't optimize too agressively,
this implementation would be correct:
.P1
100 struct q {
101 void *ptr;
102 };
103
104 void*
105 send(struct q *q, void *p)
106 {
107 while(q->ptr != 0)
108 ;
109 q->ptr = p;
110 }
111
112 void*
113 recv(struct q *q)
114 {
115 void *p;
116
117 while((p = q->ptr) == 0)
118 ;
119 q->ptr = 0;
120 return p;
121 }
.P2
.code Send
loops until the queue is empty
.code ptr "" (
.code ==
.code 0)
and then puts the pointer
.code p
in the queue.
.code Recv
loops until the queue is non-empty
and takes the pointer out.
When run in different processes,
.code send
and
.code recv
both modify
.code q->ptr ,
but
.code send
only writes the pointer when it is zero
and
.code recv
only writes the pointer when it is nonzero,
so no updates are lost.
.PP
The implementation above
is expensive. If the sender sends
rarely, the receiver will spend most
of its time spinning in the
.code while
loop hoping for a pointer.
The receiver's CPU could find more productive work than
.italic-index "busy waiting"
by repeatedly
.italic-index polling
.code q->ptr .
Avoiding busy waiting requires
a way for the receiver to yield the CPU
and resume only when
.code send
delivers a pointer.
.PP
Let's imagine a pair of calls,
.code-index sleep
and
.code-index wakeup ,
that work as follows.
.code Sleep(chan)
sleeps on the arbitrary value
.code-index chan ,
called the
.italic-index "wait channel" .
.code Sleep
puts the calling process to sleep, releasing the CPU
for other work.
.code Wakeup(chan)
wakes all processes sleeping on
.code chan
(if any), causing their
.code sleep
calls to return.
If no processes are waiting on
.code chan ,
.code wakeup
does nothing.
We can change the queue implementation to use
.code sleep
and
.code wakeup :
\X'P1 again'
.P1
201 void*
202 send(struct q *q, void *p)
203 {
204 while(q->ptr != 0)
205 ;
206 q->ptr = p;
207 wakeup(q); /* wake recv */
208 }
209
210 void*
211 recv(struct q *q)
212 {
213 void *p;
214
215 while((p = q->ptr) == 0)
216 sleep(q);
217 q->ptr = 0;
218 return p;
219 }
.P2
.figure deadlock
.PP
.code Recv
now gives up the CPU instead of spinning, which is nice.
However, it turns out not to be straightforward to design
.code sleep
and
.code wakeup
with this interface without suffering
from what is known as the ``lost wake-up'' problem (see
.figref deadlock ).
Suppose that
.code recv
finds that
.code q->ptr
.code ==
.code 0
on line 215.
While
.code recv
is between lines 215 and 216,
.code send
runs on another CPU:
it changes
.code q->ptr
to be nonzero and calls
.code wakeup ,
which finds no processes sleeping and thus does nothing.
Now
.code recv
continues executing at line 216:
it calls
.code sleep
and goes to sleep.
This causes a problem:
.code recv
is asleep waiting for a pointer
that has already arrived.
The next
.code send
will wait for
.code recv
to consume the pointer in the queue,
at which point the system will be
.italic-index "deadlocked" .
.PP
The root of this problem is that the
invariant that
.code recv
only sleeps when
.code q->ptr
.code ==
.code 0
is violated by
.code send
running at just the wrong moment.
One incorrect way of protecting the invariant would be to modify the code for
.code recv
as follows:
.P1
300 struct q {
301 struct spinlock lock;
302 void *ptr;
303 };
304
305 void*
306 send(struct q *q, void *p)
307 {
308 acquire(&q->lock);
309 while(q->ptr != 0)
310 ;
311 q->ptr = p;
312 wakeup(q);
313 release(&q->lock);
314 }
315
316 void*
317 recv(struct q *q)
318 {
319 void *p;
320
321 acquire(&q->lock);
322 while((p = q->ptr) == 0)
323 sleep(q);
324 q->ptr = 0;
325 release(&q->lock);
326 return p;
327 }
.P2
One might hope that this version of
.code recv
would avoid the lost wakeup because the lock prevents
.code send
from executing between lines 322 and 323.
It does that, but it also deadlocks:
.code recv
holds the lock while it sleeps,
so the sender will block forever waiting for the lock.
.PP
We'll fix the preceding scheme by changing
.code sleep 's
interface:
the caller must pass the lock to
.code sleep
so it can release the lock after
the calling process is marked as asleep and waiting on the
sleep channel.
The lock will force a concurrent
.code send
to wait until the receiver has finished putting itself to sleep,
so that the
.code wakeup
will find the sleeping receiver and wake it up.
Once the receiver is awake again
.code-index sleep
reacquires the lock before returning.
Our new correct scheme is useable as follows:
\X'P1 coming up'
.P1
400 struct q {
401 struct spinlock lock;
402 void *ptr;
403 };
404
405 void*
406 send(struct q *q, void *p)
407 {
408 acquire(&q->lock);
409 while(q->ptr != 0)
410 ;
411 q->ptr = p;
412 wakeup(q);
413 release(&q->lock);
414 }
415
416 void*
417 recv(struct q *q)
418 {
419 void *p;
420
421 acquire(&q->lock);
422 while((p = q->ptr) == 0)
423 sleep(q, &q->lock);
424 q->ptr = 0;
425 release(&q->lock);
426 return p;
427 }
.P2
.PP
The fact that
.code recv
holds
.code q->lock
prevents
.code send
from trying to wake it up between
.code recv 's
check of
.code q->ptr
and its call to
.code sleep .
We need
.code sleep
to atomically release
.code q->lock
and put the receiving process to sleep.
.PP
A complete sender/receiver implementation would also sleep
in
.code send
when waiting for a receiver to consume
the value from a previous
.code send .
.\"
.section "Code: Sleep and wakeup"
.\"
.PP
Let's look at the implementation of
.code-index sleep
.line proc.c:/^sleep/
and
.code-index wakeup
.line proc.c:/^wakeup/ .
The basic idea is to have
.code sleep
mark the current process as
.code-index SLEEPING
and then call
.code-index sched
to release the processor;
.code wakeup
looks for a process sleeping on the given wait channel
and marks it as
.code-index RUNNABLE .
Callers of
.code sleep
and
.code wakeup
can use any mutually convenient number as the channel.
Xv6 often uses the address
of a kernel data structure involved in the waiting.
.PP
.code Sleep
.line proc.c:/^sleep/
begins with a few sanity checks:
there must be a current process
.line proc.c:/p.==.0/
and
.code sleep
must have been passed a lock
.lines "'proc.c:/lk == 0/,/sleep.without/'" .
Then
.code sleep
acquires
.code-index ptable.lock
.line proc.c:/sleeplock1/ .
Now the process going to sleep holds both
.code ptable.lock
and
.code lk .
Holding
.code lk
was necessary in the caller (in the example,
.code recv ):
it
ensured that no other process (in the example,
one running
.code send )
could start a call to
.code wakeup(chan) .
Now that
.code sleep
holds
.code ptable.lock ,
it is safe to release
.code lk :
some other process may start a call to
.code wakeup(chan) ,
but
.code-index wakeup
will not run until it can acquire
.code-index ptable.lock ,
so it must wait until
.code sleep
has finished putting the process to sleep,
keeping the
.code wakeup
from missing the
.code sleep .
.PP
There is a minor complication: if
.code lk
is equal to
.code &ptable.lock ,
then
.code sleep
would deadlock trying to acquire it as
.code &ptable.lock
and then release it as
.code lk .
In this case,
.code sleep
considers the acquire and release
to cancel each other out
and skips them entirely
.line proc.c:/sleeplock0/ .
For example,
.code wait
.line 'proc.c:/^wakeup!(/'
calls
.code sleep
with
.code &ptable.lock .
.PP
Now that
.code sleep
holds
.code ptable.lock
and no others,
it can put the process to sleep by recording
the sleep channel,
changing the process state,
and calling
.code sched
.line proc.c:/chan.=.chan/,/sched/ .
.PP
At some point later, a process will call
.code wakeup(chan) .
.code Wakeup
.line 'proc.c:/^wakeup!(/'
acquires
.code-index ptable.lock
and calls
.code-index wakeup1 ,
which does the real work.
It is important that
.code-index wakeup
hold the
.code ptable.lock
both because it is manipulating process states
and because, as we just saw,
.code ptable.lock
makes sure that
.code sleep
and
.code wakeup