xref: /original-bsd/old/man/jobs.3j (revision 30ae0772)
Copyright (c) 1980 Regents of the University of California.
All rights reserved. The Berkeley software License Agreement
specifies the terms and conditions for redistribution.

@(#)jobs.3j 4.1 (Berkeley) 05/15/85

JOBS 3J
C 4
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 \s-2SEE ALSO\s0 below. This section attempt only to place these facilities in context, not to explain the semantics of the individual 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 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 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 \*(lqstty tostop\*(rq and disabled (the default) by \*(lqstty -tostop.\*(rq (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 process 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 completes, 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 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 which 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, 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 terminal 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 \*(lqshell escapes\*(rq 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 process 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 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 permitted 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 routine for the SIGCHLD signal which occurs whenever the status of a child process changes. A signal handler for this signal is established by:

"sigset(SIGCHLD, onchild);"

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 processor 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 sighold 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.

Restarting system calls.

In older versions of UNIX \*(lqslow\*(rq 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 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 during the call.

SEE ALSO
csh(1), ioctl(2), killpg(2), setpgrp(2), sigsys(2), wait3(2), signal(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 communication 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.