xv6, line by line
test yourself

Test yourself · category 7 of 20

Scheduling and swtch

One scheduler loop per hart, the 14 registers swtch saves, the p->lock handed across every switch, and the intena bookkeeping that keeps interrupts honest.

1warm-upChoose one

swtch saves the registers of the thread that is stopping into a struct context. Which registers are they?

kernel/proc.h
1// Saved registers for kernel context switches.
2struct context {
6 // callee-saved
19};
2warm-upType a number

Hart 1 is running process A. A’s timer tick makes it yield, and hart 1 next runs process B. How many calls to swtch does hart 1 make between “running A” and “running B”?

decimal, 0x hex or 0b binary
3warm-upChoose one

When sched switches away from a process, hart 1 continues in scheduler. Which memory is hart 1’s stack pointer pointing into while the scheduler scans proc[]?

5warm-upClick the line

In swtch, click the instruction at which the hart stops using the old thread’s stack and starts using the new thread’s stack.

kernel/swtch.S
8.globl swtch
10 sd ra, 0(a0)
11 sd sp, 8(a0)
12 sd s0, 16(a0)
13 sd s1, 24(a0)
14 sd s2, 32(a0)
15 sd s3, 40(a0)
16 sd s4, 48(a0)
17 sd s5, 56(a0)
18 sd s6, 64(a0)
19 sd s7, 72(a0)
20 sd s8, 80(a0)
21 sd s9, 88(a0)
22 sd s10, 96(a0)
23 sd s11, 104(a0)
25 ld ra, 0(a1)
26 ld sp, 8(a1)
27 ld s0, 16(a1)
28 ld s1, 24(a1)
29 ld s2, 32(a1)
30 ld s3, 40(a1)
31 ld s4, 48(a1)
32 ld s5, 56(a1)
33 ld s6, 64(a1)
34 ld s7, 72(a1)
35 ld s8, 80(a1)
36 ld s9, 88(a1)
37 ld s10, 96(a1)
38 ld s11, 104(a1)
40 ret

Your pick: none yet (click a line in the code)

6warm-upChoose one

A process created by fork has never run. When a scheduler first calls swtch(&c->context, &p->context) for it, where does swtch’s ret jump?

kernel/proc.c
143 // Set up new context to start executing at forkret,
144 // which returns to user space.
145 memset(&p->context, 0, sizeof(p->context));
149 return p;
7warm-upType a number

Lines 10–23 of swtch store the old thread’s registers into its context. How many bytes do they write in total?

kernel/swtch.S
8.globl swtch
10 sd ra, 0(a0)
11 sd sp, 8(a0)
12 sd s0, 16(a0)
13 sd s1, 24(a0)
14 sd s2, 32(a0)
15 sd s3, 40(a0)
16 sd s4, 48(a0)
17 sd s5, 56(a0)
18 sd s6, 64(a0)
19 sd s7, 72(a0)
20 sd s8, 80(a0)
21 sd s9, 88(a0)
22 sd s10, 96(a0)
23 sd s11, 104(a0)
decimal, 0x hex or 0b binary
8warm-upTrue or false, and why

True or false: swtch ought to save and restore tp as well, and leaving it out means a process resumed on a different hart will compute mycpu wrongly.

Why?

9solidChoose one

yield acquires p->lock, sets p->state = RUNNABLE, and calls sched still holding the lock. The lock is released by the scheduler only after swtch. Why must it stay held across the switch?

kernel/proc.c
499// Give up the CPU for one scheduling round.
500void
501yield(void)
503 struct proc *p = myproc();
10solidChoose one

Each pass of scheduler's outer loop executes intr_on() immediately followed by intr_off(). What is the point of turning interrupts on for a single instruction?

kernel/proc.c
435 for (;;) {
436 // The most recent process to run may have had interrupts
437 // turned off; enable them to avoid a deadlock if all
438 // processes are waiting. Then turn them back off
439 // to avoid a possible race between an interrupt
440 // and wfi.
11solidChoose one

Hart 2 is idle. A timer interrupt has been pending, and it is taken right after intr_on() at line 441. What does kerneltrap do with it?

kernel/trap.c
136void
139 int which_dev = 0;
144 if ((sstatus & SSTATUS_SPP) == 0)
145 panic("kerneltrap: not from supervisor mode");
146 if (intr_get() != 0)
147 panic("kerneltrap: interrupts enabled");
149 if ((which_dev = devintr()) == 0) {
150 // interrupt or trap from an unknown source
151 printk("scause=0x%lx sepc=0x%lx stval=0x%lx\n", scause, r_sepc(),
153 panic("kerneltrap");
154 }
156 // give up the CPU if this is a timer interrupt.
157 if (which_dev == 2 && myproc() != 0)
160 // the yield() may have caused some traps to occur,
161 // so restore trap registers for use by kernelvec.S's sepc instruction.
12solidType a number

An idle hart runs one full pass of scheduler's inner for loop and finds nothing RUNNABLE. How many times does it call acquire during that pass?

kernel/proc.c
444 int found = 0;
445 for (p = proc; p < &proc[NPROC]; p++) {
447 if (p->state == RUNNABLE) {
448 // Switch to chosen process. It is the process's job
449 // to release its lock and then reacquire it
450 // before jumping back to us.
452 c->proc = p;
455 // Don't re-enable interrupts on release.
456 mycpu()->intena = 0;
458 // Process is done running for now.
459 // It should have changed its p->state before coming back.
460 c->proc = 0;
461 found = 1;
462 }
464 }
465 if (found == 0) {
466 // nothing to run; stop running on this core until an interrupt.
467 asm volatile("wfi");
468 }
decimal, 0x hex or 0b binary
13solidFill in the machine state

Pid 5 was spinning in user mode on hart 1 when a timer interrupt arrived. usertrap called yield, which called sched, which called swtch. Hart 1 has just executed line 26, ld sp, 8(a1). What is hart 1’s state?

kernel/swtch.S
8.globl swtch
10 sd ra, 0(a0)
11 sd sp, 8(a0)
12 sd s0, 16(a0)
13 sd s1, 24(a0)
14 sd s2, 32(a0)
15 sd s3, 40(a0)
16 sd s4, 48(a0)
17 sd s5, 56(a0)
18 sd s6, 64(a0)
19 sd s7, 72(a0)
20 sd s8, 80(a0)
21 sd s9, 88(a0)
22 sd s10, 96(a0)
23 sd s11, 104(a0)
25 ld ra, 0(a1)
26 ld sp, 8(a1)
27 ld s0, 16(a1)
28 ld s1, 24(a1)
29 ld s2, 32(a1)
30 ld s3, 40(a1)
31 ld s4, 48(a1)
32 ld s5, 56(a1)
33 ld s6, 64(a1)
34 ld s7, 72(a1)
35 ld s8, 80(a1)
36 ld s9, 88(a1)
37 ld s10, 96(a1)
38 ld s11, 104(a1)
40 ret
14solidFill in the machine state

Hart 0’s scheduler has just returned from acquire(&p->lock) at line 446, during a scan. What is hart 0’s state?

kernel/proc.c
435 for (;;) {
436 // The most recent process to run may have had interrupts
437 // turned off; enable them to avoid a deadlock if all
438 // processes are waiting. Then turn them back off
439 // to avoid a possible race between an interrupt
440 // and wfi.
444 int found = 0;
445 for (p = proc; p < &proc[NPROC]; p++) {
447 if (p->state == RUNNABLE) {
448 // Switch to chosen process. It is the process's job
449 // to release its lock and then reacquire it
450 // before jumping back to us.
452 c->proc = p;
455 // Don't re-enable interrupts on release.
456 mycpu()->intena = 0;
458 // Process is done running for now.
459 // It should have changed its p->state before coming back.
460 c->proc = 0;
461 found = 1;
462 }
464 }
15solidChoose all that apply

Which of these are true at the instant scheduler executes the call swtch(&c->context, &p->context) on line 453?

kernel/proc.c
444 int found = 0;
445 for (p = proc; p < &proc[NPROC]; p++) {
447 if (p->state == RUNNABLE) {
448 // Switch to chosen process. It is the process's job
449 // to release its lock and then reacquire it
450 // before jumping back to us.
452 c->proc = p;
455 // Don't re-enable interrupts on release.
456 mycpu()->intena = 0;
458 // Process is done running for now.
459 // It should have changed its p->state before coming back.
460 c->proc = 0;
461 found = 1;
462 }
464 }
16solidPut in order

Pid 5 is preempted by a timer tick on hart 1 and later resumed by hart 0. Put these events in the order they must happen.

  1. Hart 1’s scheduler releases pid 5’s p->lock (line 463)
  2. Hart 0’s swtch loads pid 5’s context, and yield releases the lock
  3. usertrap sees which_dev == 2 and calls yield
  4. sched checks its four rules and copies intena into a local
  5. Hart 0’s scheduler acquires pid 5’s p->lock, sees RUNNABLE and sets RUNNING
  6. swtch saves pid 5’s registers and loads hart 1’s scheduler context
  7. yield acquires pid 5’s p->lock and sets RUNNABLE
17solidChoose one

Hart 1’s scheduler switched to the process in proc[4]. A tick later that process yields, and the scheduler returns from the swtch on line 453. Which slot does it examine next?

kernel/proc.c
444 int found = 0;
445 for (p = proc; p < &proc[NPROC]; p++) {
447 if (p->state == RUNNABLE) {
448 // Switch to chosen process. It is the process's job
449 // to release its lock and then reacquire it
450 // before jumping back to us.
452 c->proc = p;
455 // Don't re-enable interrupts on release.
456 mycpu()->intena = 0;
458 // Process is done running for now.
459 // It should have changed its p->state before coming back.
460 c->proc = 0;
461 found = 1;
462 }
464 }
465 if (found == 0) {
466 // nothing to run; stop running on this core until an interrupt.
467 asm volatile("wfi");
468 }
469 }
18solidMatch the pairs

Each release(&p->lock) below releases a lock that was acquired somewhere else. Match each release with the code that acquired that lock.

19solidClick the line

Once a process is running, its p->lock is free: any scheduler can lock the slot and look at it. Click the line that makes those schedulers leave the process alone.

kernel/proc.c
444 int found = 0;
445 for (p = proc; p < &proc[NPROC]; p++) {
447 if (p->state == RUNNABLE) {
448 // Switch to chosen process. It is the process's job
449 // to release its lock and then reacquire it
450 // before jumping back to us.
452 c->proc = p;
455 // Don't re-enable interrupts on release.
456 mycpu()->intena = 0;
458 // Process is done running for now.
459 // It should have changed its p->state before coming back.
460 c->proc = 0;
461 found = 1;
462 }
464 }

Your pick: none yet (click a line in the code)

20deepChoose one

Why does scheduler turn interrupts off (line 442) before scanning, instead of leaving them on for the scan?

kernel/proc.c
435 for (;;) {
436 // The most recent process to run may have had interrupts
437 // turned off; enable them to avoid a deadlock if all
438 // processes are waiting. Then turn them back off
439 // to avoid a possible race between an interrupt
440 // and wfi.
444 int found = 0;
445 for (p = proc; p < &proc[NPROC]; p++) {
447 if (p->state == RUNNABLE) {
448 // Switch to chosen process. It is the process's job
449 // to release its lock and then reacquire it
450 // before jumping back to us.
452 c->proc = p;
455 // Don't re-enable interrupts on release.
456 mycpu()->intena = 0;
458 // Process is done running for now.
459 // It should have changed its p->state before coming back.
460 c->proc = 0;
461 found = 1;
462 }
464 }
465 if (found == 0) {
466 // nothing to run; stop running on this core until an interrupt.
467 asm volatile("wfi");
468 }
469 }
21deepChoose one

Right after its swtch returns, scheduler executes mycpu()->intena = 0 (line 456). What would go wrong without that line?

kernel/proc.c
444 int found = 0;
445 for (p = proc; p < &proc[NPROC]; p++) {
447 if (p->state == RUNNABLE) {
448 // Switch to chosen process. It is the process's job
449 // to release its lock and then reacquire it
450 // before jumping back to us.
452 c->proc = p;
455 // Don't re-enable interrupts on release.
456 mycpu()->intena = 0;
458 // Process is done running for now.
459 // It should have changed its p->state before coming back.
460 c->proc = 0;
461 found = 1;
462 }
464 }
22deepChoose all that apply

Process P stops on hart 1 through sched and is later resumed on hart 2. Which of these are carried from hart 1 to hart 2 with P, so that P finds its own values after the switch?

23deepTrue or false, and why

True or false: when an idle hart executes wfi on line 467, its timer interrupt can still end the wait, even though sstatus.SIE is 0 at that point.

kernel/proc.c
435 for (;;) {
436 // The most recent process to run may have had interrupts
437 // turned off; enable them to avoid a deadlock if all
438 // processes are waiting. Then turn them back off
439 // to avoid a possible race between an interrupt
440 // and wfi.
444 int found = 0;
445 for (p = proc; p < &proc[NPROC]; p++) {
447 if (p->state == RUNNABLE) {
448 // Switch to chosen process. It is the process's job
449 // to release its lock and then reacquire it
450 // before jumping back to us.
452 c->proc = p;
455 // Don't re-enable interrupts on release.
456 mycpu()->intena = 0;
458 // Process is done running for now.
459 // It should have changed its p->state before coming back.
460 c->proc = 0;
461 found = 1;
462 }
464 }
465 if (found == 0) {
466 // nothing to run; stop running on this core until an interrupt.
467 asm volatile("wfi");
468 }
469 }

Why?

24deepChoose all that apply

Suppose release(&p->lock) on line 520 were moved to become the first statement inside the if (first) block, so that init still releases it but no later fork child does. A new child, pid 9, reaches user mode on hart 1. Which of these would then happen?

kernel/proc.c
510// A fork child's very first scheduling by scheduler()
511// will swtch to forkret.
512void
515 extern char userret[];
516 static int first = 1;
517 struct proc *p = myproc();
519 // Still holding p->lock from scheduler.
522 if (first) {
523 first = 0;
525 // File system initialization must be run in the context of a
526 // regular process (e.g., because it calls sleep), and thus cannot
527 // be run from main().
530 // We can invoke kexec() now that file system is initialized.
531 // Put the return value (argc) of kexec into a0.
532 p->trapframe->a0 = kexec("/init", (char *[]){"/init", 0});
533 if (p->trapframe->a0 == -1) {
534 panic("exec");
535 }
536 }
538 // return to user space, mimicing usertrap()'s return.
25deepChoose one

cat called sleep from inside read, with interrupts on, on hart 1. It is woken and resumed by hart 2’s scheduler. Suppose line 496 of sched, mycpu()->intena = intena;, were deleted (line 494 kept). What changes for cat?

kernel/proc.c
479void
480sched(void)
482 int intena;
483 struct proc *p = myproc();
485 if (!holding(&p->lock))
486 panic("sched p->lock");
487 if (mycpu()->noff != 1)
488 panic("sched locks");
489 if (p->state == RUNNING)
490 panic("sched RUNNING");
491 if (intr_get())
492 panic("sched interruptible");