xv6, line by line
tour 50
Tours50 noff and intena through a sleep, a yield and an interrupt

Tour 50 · Locks and interrupt state · about 37 minutes · 22 steps

noff and intena through a sleep, a yield and an interrupt

Every hart keeps three small facts about interrupts. SIE is one bit in sstatus: may an interrupt be taken right now? noff (mycpu()->noff) counts how many push_off calls are still open. intena (mycpu()->intena) remembers whether SIE was on just before the outermost push_off. Together they decide, at every release, whether interrupts come back on (Locks and interrupt state, Locks and interrupt state).

This tour watches those three values on hart 0, instruction by instruction, through one real stretch of time. The shell goes to sleep in read. The scheduler takes over and resets intena. A timer tick lands in the scheduler. A process that was preempted on another hart resumes here, inside a trap handler, and leaves with interrupts still off, as it must. A brand-new process starts in forkret. Finally a tick lands inside that process’s kernel code and shows how the preempted process got into its state in the first place.

Nothing here is invented. We booted this build on three harts, typed grind & (a test program that runs random system calls in several processes forever), and attached gdb to QEMU. Breakpoints on hart 0 recorded sstatus, cpus[0].noff (offset 120 of struct cpu), cpus[0].intena (offset 124), sp and the relevant registers at each instruction we quote. Every number below comes from that run, or from the build’s kernel/kernel.asm. Tour 13: swtch and the lock handed across a context switch and Tour 15: Spinlocks from the hardware up explained the code; this tour is about the values.

Best after: 8. Traps taken inside the kernel, 11. From a timer tick to a context switch, 13. swtch and the lock handed across a context switch, 15. Spinlocks from the hardware up, 16. sleep and wakeup, and the lost-wakeup problem

Who is running where

The run starts at tick 12, about 1.2 s after boot. grind & has just been typed.

Hart What it is doing
0 Running sh (pid 2, slot proc[1]): it printed $ and is about to call read(0, &c, 1)
1 In its scheduler loop, idle
2 Running pid 4 (slot proc[3]), the shell’s background grandchild, inside exec("grind"): still named sh (pid 3, the forked copy of sh that started it, has already exited)

sh uses KSTACK(1) (top 0x3fffffc000), pid 4 uses KSTACK(3) (top 0x3fffff8000). Hart 0’s scheduler stack is its slice of stack0, 0x80007890–0x80008890.

In the state strip, intena is shown only while noff > 0. When noff is 0 the field still holds a stale value; we mention it where it matters.

Three harts are running. This tour follows one path through the code, but the machine has three CPUs executing at the same time. Watch the locks held display at the top of each step, and read the Meanwhile, on other harts boxes: they show what the other CPUs could be doing at that very moment.
The route
  1. 1read enters the kernel: usertrap turns SIE on kernel/trap.c
  2. 2The first push_off that is not a lock kernel/proc.c
  3. 3consoleread takes cons.lock: noff 1, intena 1 kernel/spinlock.c
  4. 4noff 2: a myproc inside the critical section kernel/console.c
  5. 5sleep_prepare: two locks, noff 2 kernel/proc.c
  6. 6release(&cons.lock): the pop_off that turns SIE back on kernel/spinlock.c
  7. 7sleep(): intena is re-recorded, noff 1 kernel/proc.c
  8. 8sched's checks read noff and SIE kernel/proc.c
  9. 9sched saves intena in s3: the value is 1 kernel/proc.c
  10. 10The scheduler inherits intena 1 and erases it (line 456) kernel/proc.c
  11. 11The scheduler releases sh's lock with SIE staying off kernel/proc.c
  12. 12One instruction with interrupts on, then wfi kernel/proc.c
  13. 13A tick lands in the window: tickslock at noff 1, intena 0 kernel/trap.c
  14. 14The scheduler picks pid 4 and records intena 0 kernel/proc.c
  15. 15Pid 4 resumes in sched: s3 = 0 restores intena 0 kernel/proc.c
  16. 16yield's release leaves SIE off; kerneltrap restores sstatus kernel/trap.c
  17. 17sret: pid 4 continues on hart 0, right after a csrsi kernel/kernelvec.S
  18. 18forkret: the first release leaves SIE off kernel/proc.c
  19. 19Tick 17: a timer lands in pid 7's kernel code at noff 0 kernel/trap.c
  20. 20clockintr: intena 0, then noff 2 inside wakeup kernel/trap.c
  21. 21yield and sched: s3 = 0, the state pid 4 was frozen in kernel/proc.c
  22. 22noff and intena at every step kernel/proc.c

Keys: ← → step · Home start