1Who should receive the Ctrl-C?
Ctrl-C arrives as one byte, 0x03, in consoleintr (kernel/console.c:147),
called from an interrupt handler. You want it to kill “the program the user is
running”. The spec has the shell call setfg to tell the console which processes
those are. Why must the kernel be told at all? While cat | grep x runs, which
processes have the console open, and which of them should die? Try every rule the
kernel could apply with what it already knows (open files, parent pointers, who is
reading the console) and find the case each one gets wrong. Then: what must the thing
the shell passes to setfg name, so that one call covers a whole pipeline?
Read user/init.c (it opens the console as fds 0, 1 and 2) and the shell’s main and runcmd in user/sh.c: who forks whom for cat | grep x, and which fds does each process inherit?
Every process descends from init, and every one of them has the console open unless it closed it. “Has the console open” and “is reading the console” both pick the wrong set: the first includes init and the shell, the second misses grep (reading a pipe) and anything that computes without reading. Only the shell knows where a job starts and ends.
Look for a label that every process of a job would carry without the shell having to visit each one, and that init and the shell themselves do not carry.
The reference design
For cat | grep x the shell (pid 2) forks one child for the whole command line; that
child runs runcmd for the pipe, creates the pipe and forks cat and grep, then
waits for both (user/sh.c:101-user/sh.c:123). So five processes have the
console open: init, the shell, the job’s leader (a copy of the shell, asleep in
wait), cat (fd 0 and 2) and grep (fd 1 and 2). The right victims are exactly the
last three, the processes that descend from the shell’s child.
The kernel cannot find that set from what it has:
- Everyone with the console open kills
initand the shell:panic: init exiting. - Whoever is reading the console is only
cat;grepand its waiting parent would survive, and a job that computes without reading could never be stopped. - The descendants of some process needs the parent pointers, which are protected by
wait_lock. Walking them from an interrupt handler is the subject of a later question (and of clinic 2). - Everyone but
initkills the shell too (clinic 1).
The design used by Unix, and by the reference, is a process group: a number in
every struct proc, copied by fork, so a job’s processes share it automatically
however many times they fork. The shell creates a new group for each job and tells the
console which group is in front with a new system call; the console stores that one
number, the foreground group. Ctrl-C kills the processes of the foreground group.
init and the shell are in group 0, which means “no group”, and the kernel refuses to
kill group 0.
A simpler model, a single “console owner” pid, works for one command but not for a pipeline: the owner would have to be killed and then its children found some other way.
Check yourself
In the unmodified tree, you type cat | grep x and both programs are running. How
many processes have the console open on at least one file descriptor?
Suppose Ctrl-C killed every process whose file table contains the console. You type
cat and press Ctrl-C. What happens?