jobs(3) DG/UX 4.30 jobs(3)
NAME
jobs - summary of job control facilities in DG/UX
SYNOPSIS
#include <sys/sgtty.h>
#include <signal.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <wait.h>
int fildes, signo;
int pid, pgrp;
union wait status;
int options;
struct rusage ru;
ioctl(fildes, TIOCSPGRP, &pgrp)
ioctl(fildes, TIOCGPGRP, &pgrp)
setpgrp2(pid, pgrp)
getpgrp2(pid)
killpg(pgrp, signo)
sigset(signo, action)
sighold(signo)
sigrelse(signo)
sigpause(signo)
sigsys(signo, action)
wait3(&status, options, &ru)
DESCRIPTION
The facilities described here support the job control
implemented in csh(1) and may be used in other programs to
provide similar facilities.
For descriptions of the individual routines, see SEE ALSO
below. This section describes the facilities in general.
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 pipeline) are given the same process group. The
system assigns a single process group number to each
terminal. Processes running on a terminal are given read
access to that terminal only if they are in the same process
group as that terminal.
Thus, a command interpreter may start several jobs running
in different process groups and arbitrate access to the
terminal by controlling which, if any, of these processes is
Licensed material--property of copyright holder(s) Page 1
jobs(3) DG/UX 4.30 jobs(3)
in the same process group as the terminal. When a process
outside 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. This usually stops them
until they are continued with a SIGCONT signal. (See
sigsys(2) for a description of these signals; see tty(4) for
a description of process groups.)
If a process is not in the process group of the terminal,
and it tries to change the terminal's 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 try to write to the terminal while not in
its process group; this is controlled by the LTOSTOP bit in
the tty mode word. LTOSTOP is enabled by stty tostop and
disabled (the default) by stty-tostop. (The LTOSTOP bit is
described in tty(4)).
How the shell manipulates process groups
An interactive shell first establishes its own process group
and a process group for the terminal; this keeps other
processes from being stopped while the terminal is under its
control. The shell then assigns a distinct process group to
each job it creates. 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 completes, the shell reclaims
the terminal by resetting the terminal's process group to
that of the shell, using TIOCSPGRP again.
Shells 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 whose parents have exited, and descendants 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 that ignore or hold the SIGTTIN or SIGTTOU signal
are not sent these signals when accessing their control
terminal; if they are not in the process group of the
control terminal, reads simply return end-of-file. Output
Licensed material--property of copyright holder(s) Page 2
jobs(3) DG/UX 4.30 jobs(3)
and mode setting are also allowed.
Before a shell suspends itself, it places itself back in the
process group in which it was created. It then sends this
original group a stopping signal, stopping the shell, and
any other intermediate processes, back up to an interactive
parent. The shell also restores the process group of the
terminal when it finishes; the process that resumes might
not have control of the terminal otherwise.
Naive processes
A naive process does not alter the state of the terminal,
and does no job control. It can usually invoke subprocesses
safely, even if it has shell escapes or invokes other
processes. 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.
Processes that 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.
int tpgrp;
...
retry:
sigset(SIGTSTP, SIGHOLD);
sigset(SIGTTIN, SIGHOLD);
sigset(SIGTTOU, SIGHOLD);
if (ioctl(2, TIOCGPGRP, &tpgrp) != 0)
goto nottty;
if (tpgrp != getpgrp(0)) { /* not in foreground */
sigset(SIGTTOU, SIGDFL);
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);
SIGTSTP is ignored in this code to prevent our process from
being moved from the foreground to the background while
checking if it is in the foreground. The process holds all
Licensed material--property of copyright holder(s) Page 3
jobs(3) DG/UX 4.30 jobs(3)
the stopping signals in this critical section so that no
other process in our process group can block us on one of
these signals in the middle of our check. (This code
assumes that the command interpreter will not move a process
from foreground to background without stopping it; if it
did, we could not make the check correctly.)
The signal-handling routine 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 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, SIGDFL);
kill(0, signo);
/* stop here until continued */
sigset(signo, onstop);
... restore our special terminal state ...
}
This routine can also 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 that 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 that would normally have
hung will return end-of-file; writes that would normally
have caused SIGTTOU signals are instead permitted while
SIGTTOU is ignored.
Interrupt-level process handling
Sigset(3) lets you handle process state changes as they
occur. It provides an interrupt-handling routine for the
SIGCHLD signal, a signal that occurs whenever the status of
a child process changes. You establish a signal handler as
Licensed material--property of copyright holder(s) Page 4
jobs(3) DG/UX 4.30 jobs(3)
follows:
sigset(SIGCHLD, onchild);
The shell or other process then waits for a change in child
status with code like this:
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, sighold temporarily blocks the SIGCHLD signal while
the data structures are checked for a child to process. If
we didn't block the signal, we would have a race condition;
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, 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.
Important: a long-standing bug in the signal mechanism has
been fixed. The bug lost a SIGCHLD signal if it occurred
while the signal was blocked. This is because sighold uses
the SIG_HOLD signal set of sigsys(2) to prevent the signal
action from being taken without losing the signal.
Similarly, a signal action set with sigset has the signal
held while the action routine is running, much as the
interrupt priority of a processor is raised when a device
interrupt is taken.
In this interrupt-driven style of termination processing,
wait calls must not block when they retrieve status in the
SIGCHLD signal handler. 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 only
one interrupt is taken.
It is even possible that no children will 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
Licensed material--property of copyright holder(s) Page 5
jobs(3) DG/UX 4.30 jobs(3)
will be called whenever there is work to do; the handler
just has to collect all information by calling wait3 until
no more information is available. Further status changes
are guaranteed to be reflected in another SIGCHLD handler
call.
Restarting system calls
In older versions of UNIX, slow system calls were
interrupted when signals occurred, returning EINTR. The new
signal mechanism sigset(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 that case, they return indicating how
much data was read or written. In programs that use the
older signal(2) mechanisms, all of these calls return EINTR
if a signal occurs during the call.
SEE ALSO
csh(1), ioctl(2), killpg(2), setpgrp(2), sigsys(2),
wait3(2), sigset(3), tty(4).
Licensed material--property of copyright holder(s) Page 6