xv6, line by line
tour 11
Tours11 From a timer tick to a context switch

Tour 11 · Time and scheduling · about 34 minutes · 20 steps

From a timer tick to a context switch

A program that never makes a system call never asks the kernel for anything. So how does the kernel ever get the CPU back from it? This tour answers that by following one tick of the clock on hart 1, from the moment the hart’s timer fires, through the kernel, until a different process is running on hart 1 and the interrupted program has been picked up by hart 0.

The victim is pid 5, a child created by usertests preempt (user/usertests.c:856; its comment says it is meant for at most two CPUs, and with three harts it still passes, because every tick sends one spinner back to the table). Its whole program is for (;;) ;: one 2-byte jump instruction that jumps to itself. Without a timer it would own hart 1 forever.

You will see the timer interrupt arrive through the same trampoline page as a system call (Tour 5: Life of a system call), clockintr counting time on hart 0 only, and yield handing the CPU to scheduler through sched and swtch. Watch one lock in particular: pid 5’s p->lock. It is acquired by pid 5’s kernel thread on hart 1 and released by hart 1’s scheduler, and while it is held no other hart may touch pid 5. That hand-off is what makes preemption safe on a machine with three CPUs.

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

Who is running where

The machine has three harts. usertests preempt has been typed at the shell (pid 2), which started usertests (pid 3), which forked the test process (pid 4), which forked three children. On a fresh boot they occupy process-table slots in order: proc[4] is pid 5, proc[5] pid 6, proc[6] pid 7. When the tour starts:

Hart What it is doing
0 Running pid 6 in user mode: another for (;;) ; spinner
1 Running pid 5 in user mode, spinning: the process this tour follows
2 Running pid 7 in user mode: it wrote one byte into a pipe and now spins too

Pid 4 (in proc[3]) was asleep in read on that pipe. Pid 7’s write woke it, so it is RUNNABLE, but all three harts are busy with spinners. It can run only if one of them is taken off its CPU.

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. 1A program that never gives up the CPU user/usertests.c
  2. 2Flashback: each hart set its own alarm at boot kernel/start.c
  3. 3The tick arrives, and hart 1 lands in the trampoline kernel/trampoline.S
  4. 4usertrap saves the interrupted pc kernel/trap.c
  5. 5Not a system call, so ask devintr kernel/trap.c
  6. 6devintr recognizes the timer kernel/trap.c
  7. 7clockintr on hart 1 does not count kernel/trap.c
  8. 8Re-arm the alarm, or drown in interrupts kernel/trap.c
  9. 9Back in usertrap, the decision to yield kernel/trap.c
  10. 10yield takes pid 5's lock and marks it RUNNABLE kernel/proc.c
  11. 11sched checks the rules kernel/proc.c
  12. 12swtch saves pid 5 kernel/swtch.S
  13. 13swtch loads hart 1's scheduler kernel/swtch.S
  14. 14Hart 1's scheduler lets go of pid 5 kernel/proc.c
  15. 15Hart 1 keeps scanning and finds pid 4 kernel/proc.c
  16. 16Hart 0's tick, and pid 5 is picked up kernel/proc.c
  17. 17Pid 5 wakes up inside sched, on hart 0 kernel/proc.c
  18. 18yield releases the lock hart 0's scheduler took kernel/proc.c
  19. 19prepare_return records hart 0 kernel/trap.c
  20. 20The jump executes, on a different CPU user/usertests.c

Keys: ← → step · Home start