xv6, line by line
lab 6
Lab 66 Ctrl-C: interrupting the foreground job

Lab 6 · reveal · 17 steps · 9 commits

Ctrl-C: interrupting the foreground job: the reference solution

In this tree, Ctrl-C is just a byte. Type cat, press Ctrl-C, and the UART delivers 0x03 to consoleintr, which stores it in the input buffer like any letter. cat keeps waiting. A program that computes forever can only be stopped by rebooting. In this lab you make Ctrl-C kill the shell’s current foreground job, every process of a pipeline, while the shell itself survives and prints a new prompt.

The keystroke arrives in an interrupt handler, on whichever hart the PLIC picked, usually while some unrelated process or nobody at all is running there. That raises the questions this lab is about. The console is shared by init, the shell and every job: who is “in front”, and who tells the console? What does “kill” mean in a kernel where a victim can only die at certain checkpoints, and how long can it take for a process asleep in the kernel, or one that never makes a system call? What may an interrupt handler do with a spinlock already held, and which locks may it never take? A process asleep in consoleread is waiting for exactly the device that is now interrupting it: does it wake? And what happens at the edges: a ^C at an empty prompt, a job that exits just as the key is pressed, a job that forks at that very moment?

The reference solution is nine small commits: three small system calls, one new case in consoleintr, six lines in the shell, a pause command, an in-kernel test program and a QEMU driver that types Ctrl-C from outside the machine.

Each step shows one change on the branch ext/06-ctrl-c, the code around it, and the state of the machine when that code runs.

The route
  1. 1A group number in every process kernel/proc.h
  2. 2fork copies the group, under two locks taken one after the other kernel/proc.c
  3. 3A freed slot belongs to no group kernel/proc.c
  4. 4setpgid: only yourself or your children kernel/proc.c
  5. 5killpgrp: kkill for a whole group kernel/proc.c
  6. 6killpg, for programs that are not a keyboard kernel/sysproc.c
  7. 7The console learns which group is in front kernel/console.c
  8. 8setfg takes cons.lock from a system call kernel/console.c
  9. 9Ctrl-C: drop the line, echo, kill kernel/console.c
  10. 10Nobody found: an empty line; somebody found: wake the console's sleepers kernel/console.c
  11. 11The victim notices kernel/console.c
  12. 12usertrap ends the job kernel/trap.c
  13. 13The shell puts each job in front user/sh.c
  14. 14A background job leaves the group user/sh.c
  15. 15pause, a job that sleeps without reading user/pause.c
  16. 16pgtest drives the kill loop from inside user/pgtest.c
  17. 17ctrlc-test.py types the key ctrlc-test.py

Keys: ← → step · Home start