xv6, line by line
tour 8
Tours8 Traps taken inside the kernel

Tour 8 · Traps and system calls · about 36 minutes · 21 steps

Traps taken inside the kernel

In Tour 5: Life of a system call a trap came from user mode: the program asked for help, and the trampoline page switched page tables, saved 31 registers and found a kernel stack. This tour is about the other kind of trap: one that hits the kernel itself, in the middle of kernel code, at an instruction the kernel did not choose.

The scene: the user typed zombie & ; cat README. cat (pid 3) is inside its read system call on hart 2, copying file bytes into its user buffer, when hart 2’s timer goes off. You will watch the hardware divert hart 2 into kernelvec, see why it saves only some registers, follow kerneltrap and clockintr, and then watch cat give up hart 2 from inside the kernel with yield, and come back later on hart 0, in the middle of the same memmove.

Along the way you meet the rule that makes kernel interrupts safe at all: a hart holding a spinlock never takes an interrupt. You will see what goes wrong without it (a hart that deadlocks against itself), and the bookkeeping (noff, intena) that makes the rule work even across a context switch.

Best after: 5. Life of a system call, 7. The trampoline and the trapframe

Who is running where

The machine has three harts. When the tour starts:

Hart What it is doing
0 Running zombie (pid 5), which has just forked its child (pid 6) and is about to call pause(5)
1 Idle in its scheduler, waiting in wfi for something to happen
2 Running cat README (pid 3) in the kernel, inside read: the thread this tour follows

Both came from one command line, the first typed after boot: zombie & ; cat README. The shell (pid 2) forked pid 3 to run the list; pid 3 forked pid 4 for zombie &, which forked pid 5 to exec zombie and exited; pid 3 reaped it and then exec’d cat itself.

The shell (pid 2) is asleep in kwait, waiting for cat.

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. 1cat is in the kernel, reading README kernel/file.c
  2. 2Deep inside copyout, in the middle of memmove kernel/vm.c
  3. 3Where a kernel trap will land kernel/trap.c
  4. 4Hart 2's timer is armed kernel/start.c
  5. 5The hardware diverts hart 2 into kernelvec kernel/kernelvec.S
  6. 6Saving only the caller-saved registers kernel/kernelvec.S
  7. 7kerneltrap copies sepc, sstatus and scause first kernel/trap.c
  8. 8Two sanity checks kernel/trap.c
  9. 9devintr decodes scause kernel/trap.c
  10. 10clockintr on hart 2 re-arms the timer, and nothing else kernel/trap.c
  11. 11Meanwhile on hart 0: zombie takes tickslock kernel/sysproc.c
  12. 12acquire turns interrupts off first kernel/spinlock.c
  13. 13push_off and pop_off count the nesting kernel/spinlock.c
  14. 14Back on hart 2, kerneltrap decides to yield kernel/trap.c
  15. 15yield takes cat's p->lock kernel/proc.c
  16. 16sched's checks catch a spinlock held across the switch kernel/proc.c
  17. 17Hart 2's scheduler lets go, hart 0's picks cat up kernel/proc.c
  18. 18cat wakes up inside sched, on hart 0 kernel/proc.c
  19. 19Why sepc and sstatus must be put back kernel/trap.c
  20. 20kernelvec restores registers, but not tp, and executes sret kernel/kernelvec.S
  21. 21memmove carries on, none the wiser kernel/vm.c

Keys: ← → step · Home start