jobs(3)
_________________________________________________________________
jobs Subroutine
summary of job control facilities in DG/UX
_________________________________________________________________
SYNTAX
#include <sys/sgtty.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)
DESCRIPTION
The facilities described here to 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.
DG/UX 4.00 Page 1
Licensed material--property of copyright holder(s)
jobs(3)
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 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 and
mode setting are also allowed.
DG/UX 4.00 Page 2
Licensed material--property of copyright holder(s)
jobs(3)
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.
short 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 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
DG/UX 4.00 Page 3
Licensed material--property of copyright holder(s)
jobs(3)
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 follows:
sigset(SIGCHLD, onchild);
DG/UX 4.00 Page 4
Licensed material--property of copyright holder(s)
jobs(3)
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 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
DG/UX 4.00 Page 5
Licensed material--property of copyright holder(s)
jobs(3)
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
ioctl(2), killpg(2), setpgrp(2), sigsys(2), wait3(2), sigset(3),
tty(4)
csh(1) in the User's Reference for the DG/UX System.
DG/UX 4.00 Page 6
Licensed material--property of copyright holder(s)