jobs(2) DG/UX 5.4R3.00 jobs(2)
NAME
jobs - summary of DG/UX job control facilities
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)
signal(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 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
Licensed material--property of copyright holder(s) 1
jobs(2) DG/UX 5.4R3.00 jobs(2)
signal(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.
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
Licensed material--property of copyright holder(s) 2
jobs(2) DG/UX 5.4R3.00 jobs(2)
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(3C) 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 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);
Licensed material--property of copyright holder(s) 3
jobs(2) DG/UX 5.4R3.00 jobs(2)
...
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(2) 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);
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 automatically releases
Licensed material--property of copyright holder(s) 4
jobs(2) DG/UX 5.4R3.00 jobs(2)
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
signal(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
In older versions of UNIX, slow system calls were interrupted when
signals occurred, returning EINTR. The new signal mechanism
sigset(2) 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), signal(2), sigset(2),
wait3(2), termio(7).
Licensed material--property of copyright holder(s) 5