Unix job control and its interactions with TTYs (and shells)

May 16, 2021

For a user, Unix's job control gives you more or less two things. First, you can stop a program you're running with Ctrl-Z (usually), and often then have it run in the background for a while if you want (or you may want it to just stop, for example so it stops using up all of your CPU). Second, it lets you multiplex your terminal between multiple programs that all want to interact with you. You can Ctrl-Z the current foreground program, bring another one to the foreground, interact with it for a while, switch back, and so on.

The actual implementation of this is somewhat tangled. One way to put it is that Unix doesn't trust programs to cooperate with job control (which also means that programs didn't have to all be updated when BSD introduced the idea of job control). Instead, job control is managed by a combination of your Unix shell and the Unix TTY driver, primarily through the mechanism of the foreground (terminal) process group.

A job control shell puts every command or pipeline you run into a different process group. Then Unix TTYs (pseudo-ttys these days, but once upon a time real serial terminal connections) have one and only one foreground process group; normally, only processes in this process group are allowed to interact freely with the terminal. If a process in another process group attempts to either read from or write to the terminal, it will usually get hit with SIGTTIN or SIGTTOU.

(If you ignore SIGTTIN, you generally get an EIO error instead when you read from the TTY and you're not in the foreground process group.)

Plain ordinary Unix programs do nothing special with the TTY and let this basic handling take care of everything. Some Unix programs need to do more; for example, if they turn off the standard TTY handling, Ctrl-Z stops being special and they need to handle it themselves. Generally they do this by performing any particular clean-up they need to do then sending themselves (or their entire process group) a SIGSTOP signal.

To make all of this work, job control shells change the TTY's current foreground process group to match whatever they think should currently be in the foreground. They also watch for processes (and groups of them) that have become suspended, which is a sign of things like the user pressing Ctrl-Z. One consequence of this is that when the shell regains control of the terminal because the current foreground process has either been suspended or has finished, the first thing the shell has to do is reset the current foreground process group back to itself. Otherwise, its own attempts to do IO to the TTY will fail (when the shell wants to print its prompt, read input, and perhaps tell you that something has just been suspended).

PS: If there are multiple processes in the current foreground process group, they can freely write a jumble of TTY output or all try to read from the TTY at the same time. Job control only protects you from different command lines (ie, different process groups) interfering with each other. Generally this isn't an issue because if there's more than one process in a process group, they're in a pipeline (or a shell script).

Written on 16 May 2021.
« Downsampling your metrics data is a compromise (what we could call a 'hack')
Unix job control has some dark corners and challenging cases, illustrated »

Page tools: View Source.
Search:
Login: Password:

Last modified: Sun May 16 23:22:11 2021
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.