While I was hacking away at a tcsh-like shell for Minix (a 7th Edition clone
for IBM ATs), I came up with this truly bizarre method of job control. It
even works!
Warren
Seventh Edition Job Control
Job control can be achieved under early versions of Unix, such as
Seventh Edition, by using the ptrace(2) system call in a manner not
intended by its designers.
Ptrace() was designed to allow a parent process to trace the excution
of a child process, stopping the child under certain conditions,
examining or modifying the contents of the child's memory, and
restarting the child. The stopping/restarting abilities of ptrace()
can be used to provide job control.
To permit a child process to be stopped, it must inform the parent
that it wants its execution to be traced, which it does by
ptrace(0,0,0,0). Fortunately, this can be done after the fork()
and before the exec() in the shell.
When a traced process is executing, it is stopped under the following
conditions:
+ the process receives a signal, or
+ the process exec()s
If the shell is wait()ing on the child, it will be informed that
the child has stopped, and can determine the signal that caused
the process to stop (SIGTRAP in the case that the process exec()d).
It is then able, using ptrace() with various arguments, to terminate
or restart the process. At the same time, the shell can also deliver
the signal to the restarted process, or not deliver the signal (see
the manual for ptrace(2)).
Seventh Edition job control, thus, is not so much a matter of stopping
a process when requested to by the user, as ensuring that the
process is always restarted, except when the user wants it to stop.
Restarting stopped processes is straightforward. Stopping a running
foreground process, however, is difficult, as there is no terminal
key that, when pressed, will inform the shell to stop the process;
indeed, the shell is most likely blocked wait()ing for the process
to terminate.
Two keys that do affect the execution of a foreground process are
`int' (usually ^C or DEL), which sends a SIGINT to the process,
and `quit' (usually ^\), which sends a SIGQUIT to the process. The
latter cannot be caught or ignored by the process, and the delivery
of SIGQUIT causes the process to terminate, usually with a core
dump. However, when a process is being traced, pending signals are
not delivered; instead, the process is stopped, and the parent
informed about the pending signal. The parent can choose to terminate
or restart the process, delivering or ignoring the signal as
described above.
Therefore, with ^C being frequently used, and ^\ rarely used, it
is possible to reinterpret the meaning of ^\ and SIGQUIT to mean
``stop the process''. The SIGQUIT from ^\ is never delivered by
the shell, but all other signals (including the SIGINT from ^C)
are delivered. Users can then re-bind the `quit' key with stty(1)
to be the more traditional `stop' key, ^Z.