JOBS(3J) GENIX Programmer's Manual JOBS(3J)
NAME
jobs - summary of job control facilities
SYNOPSIS
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/vtimes.h>
#include <wait.h>
int fildes, signo;
short pid, pgrp;
union wait status;
struct vtimes vt;
ioctl(fildes, TIOCSPGRP, &pgrp)
ioctl(fildes, TIOCGPGRP, &pgrp)
setpgrp(pid, pgrp)
getpgrp(pid)
killpg(pgrp, signo)
sigset(signo, action)
sighold(signo)
sigrelse(signo)
sigpause(signo)
sigsys(signo, action)
wait3(&status, options, &vt)
cc ... -ljobs
DESCRIPTION
The facilities described here are used to support the job control
implemented in csh(1), and may be used in other programs to provide
similar facilities. Because these facilities are not standard in
UNIX and because the signal mechanisms are also slightly different,
the associated routines are not in the standard C library, but
rather in the -ljobs library.
For descriptions of the individual routines see the various sections
listed in SEE ALSO below. This section attempt only to place these
facilities in context, not to explain the semantics of the indivi-
dual calls.
Terminal arbitration mechanisms.
The job control mechanism works by associating with each process a
number called a process group ; related processes (e.g. in a pipe-
line) are given the same process group. The system assigns a single
process group number to each terminal. Processes running on a ter-
minal are given read access to that terminal only if they are in the
same process group as that terminal.
Printed 11/16/83 1
JOBS(3J) GENIX Programmer's Manual JOBS(3J)
Thus a command interpreter may start several jobs running in dif-
ferent process groups and arbitrate access to the terminal by con-
trolling which, if any, of these processes is in the same process
group as the terminal. When a process which is not in the process
group of the terminal tries to read from the terminal, all members
of the process group of the process receive a SIGTTIN signal, which
normally then causes them to stop until they are continued with a
SIGCONT signal. (See sigsys(2) for a description of these signals;
tty(4) for a description of process groups.)
If a process which is not in the process group of the terminal
attempts to change the terminals mode, the process group of that
process is sent a SIGTTOU signal, causing the process group to stop.
A similar mechanism is (optionally) available for output, causing
processes to block with SIGTTOU when they attempt to write to the
terminal while not in its process group; this is controlled by the
LTOSTOP bit in the tty mode word, enabled by "stty tostop" and dis-
abled (the default) by "stty -tostop." (The LTOSTOP bit is described
in tty(4)).
How the shell manipulates process groups.
A shell which is interactive first establishes its own process group
and a process group for the terminal; this prevents other processes
from being inadvertantly stopped while the terminal is under its
control. The shell then assigns each job it creates a distinct pro-
cess group. When a job is to be run in the foreground, the shell
gives the terminal to the process group of the job using the
TIOCSPGRP ioctl (See ioctl(2) and tty(4)). When a job stops or com-
pletes, the shell reclaims the terminal by resetting the terminals
process group to that of the shell using TIOCSPGRP again.
Shells which are running shell scripts or running non-interactively
do not manipulate process groups of jobs they create. Instead, they
leave the process group of sub-processes and the terminal unchanged.
This assures that if any sub-process they create blocks for terminal
i/o, the shell and all its sub-processes will be blocked (since they
are a single process group). The first interactive parent of the
non-interactive shell can then be used to deal with the stoppage.
Processes which are orphans (whose parents have exited), and descen-
dants of these processes are protected by the system from stopping,
since there can be no interactive parent. Rather than blocking,
reads from the control terminal return end-of-file and writes to the
control terminal are permitted (i.e. LTOSTOP has no effect for these
processes.) Similarly processes which ignore or hold the SIGTTIN or
SIGTTOU signal are not sent these signals when accessing their con-
trol terminal; if they are not in the process group of the control
terminal reads simply return end-of-file. Output and mode setting
are also allowed.
Printed 11/16/83 2
JOBS(3J) GENIX Programmer's Manual JOBS(3J)
Before a shell suspends itself, it places itself back in the process
group in which it was created, and then sends this original group a
stopping signal, stopping the shell and any other intermediate
processes back to an interactive parent. The shell also restores
the process group of the terminal when it finishes, as the process
which then resumes would not necessarily be in control of the termi-
nal otherwise.
Naive processes.
A process which does not alter the state of the terminal, and which
does no job control can invoke subprocesses normally without worry.
If such a process issues a system(3) call and this command is then
stopped, both of the processes will stop together. Thus simple
processes need not worry about job control, even if they have "shell
escapes" or invoke other processes.
Processes which modify the terminal state.
When first setting the terminal into an unusual mode, the process
should check, with the stopping signals held, that it is in the
foreground. It should then change the state of the terminal, and
set the catches for SIGTTIN, SIGTTOU and SIGTSTP. The following is
a sample of the code that will be needed, assuming that unit 2 is
known to be a terminal.
short tpgrp;
...
retry:
sigset(SIGTSTP, SIG_HOLD);
sigset(SIGTTIN, SIG_HOLD);
sigset(SIGTTOU, SIG_HOLD);
if (ioctl(2, TIOCGPGRP, &tpgrp) != 0)
goto nottty;
if (tpgrp != getpgrp(0)) { /* not in foreground */
sigset(SIGTTOU, SIG_DFL);
kill(0, SIGTTOU);
/* job stops here waiting for SIGCONT */
goto retry;
}
...save old terminal modes and set new modes...
sigset(SIGTTIN, onstop);
sigset(SIGTTOU, onstop);
sigset(SIGTSTP, onstop);
It is necessary to ignore SIGTSTP in this code because otherwise our
process could be moved from the foreground to the background in the
middle of checking if it is in the foreground. The process holds
all the stopping signals in this critical section so no other pro-
cess in our process group can mess us up by blocking us on one of
these signals in the middle of our check. (This code assumes that
Printed 11/16/83 3
JOBS(3J) GENIX Programmer's Manual JOBS(3J)
the command interpreter will not move a process from foreground to
background without stopping it; if it did we would have no way of
making the check correctly.)
The routine which handles the signal should clear the catch for the
stop signal and kill(2) the processes in its process group with the
same signal. The statement after this kill will be executed when
the process is later continued with SIGCONT.
Thus the code for the catch routine might look like:
...
sigset(SIGTSTP, onstop);
sigset(SIGTTIN, onstop);
sigset(SIGTTOU, onstop);
...
onstop(signo)
int signo;
{
... restore old terminal state ...
sigset(signo, SIG_DFL);
kill(0, signo);
/* stop here until continued */
sigset(signo, onstop);
... restore our special terminal state ...
}
This routine can also be used to simulate a stop signal.
If a process does not need to save and restore state when it is
stopped, but wishes to be notified when it is continued after a stop
it can catch the SIGCONT signal; the SIGCONT handler will be run
when the process is continued.
Processes which lock data bases such as the password file should
ignore SIGTTIN, SIGTTOU, and SIGTSTP signals while the data bases
are being manipulated. While a process is ignoring SIGTTIN signals,
reads which would normally have hung will return end-of-file; writes
which would normally have caused SIGTTOU signals are instead permit-
ted while SIGTTOU is ignored.
Interrupt-level process handling.
Using the mechanisms of sigset(3) it is possible to handle process
state changes as they occur by providing an interrupt-handling rou-
tine for the SIGCHLD signal which occurs whenever the status of a
child process changes. A signal handler for this signal is esta-
blished by:
sigset(SIGCHLD, onchild);
Printed 11/16/83 4
JOBS(3J) GENIX Programmer's Manual JOBS(3J)
The shell or other process would then await a change in child status
with code of the form:
recheck:
sighold(SIGCHLD); /* start critical section */
if (no children to process) {
sigpause(SIGCHLD); /* release SIGCHLD and pause */
goto recheck;
}
sigrelse(SIGCHLD); /* end critical region */
/* now have a child to process */
Here we are using sighold to temporarily block the SIGCHLD signal
during the checking of the data structures telling us whether we
have a child to process. If we didn't block the signal we would
have a race condition since the signal might corrupt our decision by
arriving shortly after we had finished checking the condition but
before we paused.
If we need to wait for something to happen, we call sigpause which
automically releases the hold on the SIGCHLD signal and waits for a
signal to occur by starting a pause(2). Otherwise we simply release
the SIGCHLD signal and process the child. Sigpause is similar to
the PDP-11 wait instruction, which returns the priority of the pro-
cessor to the base level and idles waiting for an interrupt.
It is important to note that the long-standing bug in the signal
mechanism which would have lost a SIGCHLD signal which occurred
while the signal was blocked has been fixed. This is because sig-
hold uses the SIG_HOLD signal set of sigsys(2) to prevent the signal
action from being taken without losing the signal if it occurs.
Similarly, a signal action set with sigset has the signal held while
the action routine is running, much as a the interrupt priority of
the processor is raised when a device interrupt is taken.
In this interrupt driven style of termination processing it is
necessary that the wait calls used to retrieve status in the SIGCHLD
signal handler not block. This is because a single invocation of
the SIGCHLD handler may indicate an arbitrary number of process
status changes: signals are not queued. This is similar to the case
in a disk driver where several drives on a single controller may
report status at once, while there is only one interrupt taken. It
is even possible for no children to be ready to report status when
the SIGCHLD handler is invoked, if the signal was posted while the
SIGCHLD handler was active, and the child was noticed due to a
SIGCHLD initially sent for another process. This causes no problem,
since the handler will be called whenever there is work to do; the
handler just has to collect all information by calling wait3 until
it says no more information is available. Further status changes
are guaranteed to be reflected in another SIGCHLD handler call.
Printed 11/16/83 5
JOBS(3J) GENIX Programmer's Manual JOBS(3J)
Restarting system calls.
In older versions of UNIX "slow" system calls were interrupted when
signals occurred, returning EINTR. The new signal mechanism sig-
set(3) normally restarts such calls rather than interrupting them.
To summarize: pause and wait return error EINTR (as before), ioctl
and wait3 restart, and read and write restart unless some data was
read or written in which case they return indicating how much data
was read or written. In programs which use the older signal(2)
mechanisms, all of these calls return EINTR if a signal occurs dur-
ing the call.
SEE ALSO
csh(1), ioctl(2), killpg(2), setpgrp(2), sigsys(2), wait3(2), sig-
nal(3), tty(4)
BUGS
The job control facilities are not available in standard version 7
UNIX. These facilities are still under development and may change
in future releases of the system as better inter-process communica-
tion facilities and support for virtual terminals become available.
The options and specifications of these system calls and even the
calls themselves are thus subject to change.
Printed 11/16/83 6