Writing a Shell That Handles Signals
bird_shell: pipes, redirection, job control, and the part of POSIX nobody warns you about.
Writing a shell is a standard exercise, and for the first two evenings it feels like one. You read a line, you fork, you exec, you wait. Pipes take an afternoon. Redirection takes another.
Then you press Ctrl-C and everything you believed about your program turns out to be wrong.
Job control is the actual project
The gap between “runs commands” and “is a shell” is entirely signals and process groups. A real shell has to:
- put each pipeline in its own process group, so a signal reaches the whole pipeline rather than one member of it
- hand terminal ownership to the foreground group and take it back afterwards, without racing against the child
- ignore the signals it is responsible for delivering, while making sure its children do not inherit that indifference
- reap children that finished while it was not looking, without blocking on the ones that have not
None of this is difficult in isolation. All of it is difficult at once, because the failure modes are timing-dependent and the bug you are chasing frequently stops reproducing when you add a print statement.
What it was worth
I have never since been confused about what a terminal is doing. That alone justified the project. Everything I now know about why a hung process is hung comes from the week I spent finding out why mine was.
Status
Archived, and cheerfully so.