xv6, line by line
tour 9
Tours9 Device interrupts and the PLIC

Tour 9 · Traps and system calls · about 39 minutes · 22 steps

Device interrupts and the PLIC

You type cat README. To print the first line, cat needs the file’s first data block, block 48 of the disk, and nobody has read it since boot. The kernel hands the request to the disk and puts cat to sleep. Some time later the disk finishes, and a wire goes high. This tour follows that wire.

The signal does not go to cat, which is not running anywhere. It goes to the PLIC, a small interrupt router that decides which harts to bother. It bothers all three. Whichever hart gets there first claims the interrupt; a hart that arrives later (if it traps at all) gets “nothing” and goes back to what it was doing. The winner runs the disk driver’s interrupt handler, marks the block as done and wakes cat, which then continues on whichever hart picks it up, possibly a third one. At the same moment a keystroke arrives from the UART, and another hart handles that.

Along the way you will see the one rule that makes interrupt handlers and ordinary kernel code able to share data: a spinlock that an interrupt handler takes must be held with interrupts off, everywhere (interrupts and spinlocks (push_off / pop_off)). Break it and a hart can deadlock against itself.

The general system-call path is in Tour 5: Life of a system call, and the mechanics of kernelvec and kerneltrap are in Tour 8: Traps taken inside the kernel. This tour starts where those leave off: at the device.

Best after: 5. Life of a system call, 8. Traps taken inside the kernel

Who is running where

The machine has three harts. When the tour starts:

Hart What it is doing
0 Running cat (pid 3), which has just called read on README: the process this tour follows
1 Idle: its scheduler finds nothing runnable
2 Idle in its own scheduler

The shell (pid 2) is asleep in kwait, waiting for cat; init (pid 1) is asleep waiting for the shell. The concrete numbers in this tour (inode 2, block 48, which hart claims what) come from this build’s fs.img and from one run of the kernel under gdb with three harts.

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 asks for 512 bytes user/cat.c
  2. 2readi needs block 48 kernel/fs.c
  3. 3bread misses the cache kernel/bio.c
  4. 4Taking vdisk_lock, which turns interrupts off kernel/virtio_disk.c
  5. 5Describing the request and ringing the doorbell kernel/virtio_disk.c
  6. 6Register, then release, then sleep kernel/virtio_disk.c
  7. 7cat leaves hart 0 kernel/proc.c
  8. 8Flashback: the PLIC's registers kernel/memlayout.h
  9. 9plicinit and plicinithart kernel/plic.c
  10. 10The order of setup in main kernel/main.c
  11. 11Three switches between a device and a hart kernel/start.c
  12. 12The disk finishes; every idle hart wakes kernel/proc.c
  13. 13kerneltrap, on the scheduler's stack kernel/trap.c
  14. 14devintr asks the PLIC who it was kernel/trap.c
  15. 15Claiming the interrupt, and the harts that lose kernel/plic.c
  16. 16virtio_disk_intr takes vdisk_lock and acknowledges the device kernel/virtio_disk.c
  17. 17Walking the used ring; block 48 is done kernel/virtio_disk.c
  18. 18wakeup makes cat runnable, from interrupt context kernel/proc.c
  19. 19Meanwhile, a keystroke on hart 1 kernel/uart.c
  20. 20plic_complete re-opens source 1 kernel/plic.c
  21. 21cat wakes on hart 0 and re-checks kernel/virtio_disk.c
  22. 22The data reaches cat, and what it cost kernel/fs.c

Keys: ← → step · Home start