diff options
author | Roland McGrath <roland@gnu.org> | 1995-02-18 01:27:10 +0000 |
---|---|---|
committer | Roland McGrath <roland@gnu.org> | 1995-02-18 01:27:10 +0000 |
commit | 28f540f45bbacd939bfd07f213bcad2bf730b1bf (patch) | |
tree | 15f07c4c43d635959c6afee96bde71fb1b3614ee /manual/job.texi | |
download | glibc-28f540f45bbacd939bfd07f213bcad2bf730b1bf.tar glibc-28f540f45bbacd939bfd07f213bcad2bf730b1bf.tar.gz glibc-28f540f45bbacd939bfd07f213bcad2bf730b1bf.tar.bz2 glibc-28f540f45bbacd939bfd07f213bcad2bf730b1bf.zip |
initial import
Diffstat (limited to 'manual/job.texi')
-rw-r--r-- | manual/job.texi | 1249 |
1 files changed, 1249 insertions, 0 deletions
diff --git a/manual/job.texi b/manual/job.texi new file mode 100644 index 0000000000..1ac15fffc4 --- /dev/null +++ b/manual/job.texi @@ -0,0 +1,1249 @@ +@node Job Control +@chapter Job Control + +@cindex process groups +@cindex job control +@cindex job +@cindex session +@dfn{Job control} refers to the protocol for allowing a user to move +between multiple @dfn{process groups} (or @dfn{jobs}) within a single +@dfn{login session}. The job control facilities are set up so that +appropriate behavior for most programs happens automatically and they +need not do anything special about job control. So you can probably +ignore the material in this chapter unless you are writing a shell or +login program. + +You need to be familiar with concepts relating to process creation +(@pxref{Process Creation Concepts}) and signal handling (@pxref{Signal +Handling}) in order to understand this material presented in this +chapter. + +@menu +* Concepts of Job Control:: Jobs can be controlled by a shell. +* Job Control is Optional:: Not all POSIX systems support job control. +* Controlling Terminal:: How a process gets its controlling terminal. +* Access to the Terminal:: How processes share the controlling terminal. +* Orphaned Process Groups:: Jobs left after the user logs out. +* Implementing a Shell:: What a shell must do to implement job control. +* Functions for Job Control:: Functions to control process groups. +@end menu + +@node Concepts of Job Control, Job Control is Optional, , Job Control +@section Concepts of Job Control + +@cindex shell +The fundamental purpose of an interactive shell is to read +commands from the user's terminal and create processes to execute the +programs specified by those commands. It can do this using the +@code{fork} (@pxref{Creating a Process}) and @code{exec} +(@pxref{Executing a File}) functions. + +A single command may run just one process---but often one command uses +several processes. If you use the @samp{|} operator in a shell command, +you explicitly request several programs in their own processes. But +even if you run just one program, it can use multiple processes +internally. For example, a single compilation command such as @samp{cc +-c foo.c} typically uses four processes (though normally only two at any +given time). If you run @code{make}, its job is to run other programs +in separate processes. + +The processes belonging to a single command are called a @dfn{process +group} or @dfn{job}. This is so that you can operate on all of them at +once. For example, typing @kbd{C-c} sends the signal @code{SIGINT} to +terminate all the processes in the foreground process group. + +@cindex session +A @dfn{session} is a larger group of processes. Normally all the +proccesses that stem from a single login belong to the same session. + +Every process belongs to a process group. When a process is created, it +becomes a member of the same process group and session as its parent +process. You can put it in another process group using the +@code{setpgid} function, provided the process group belongs to the same +session. + +@cindex session leader +The only way to put a process in a different session is to make it the +initial process of a new session, or a @dfn{session leader}, using the +@code{setsid} function. This also puts the session leader into a new +process group, and you can't move it out of that process group again. + +Usually, new sessions are created by the system login program, and the +session leader is the process running the user's login shell. + +@cindex controlling terminal +A shell that supports job control must arrange to control which job can +use the terminal at any time. Otherwise there might be multiple jobs +trying to read from the terminal at once, and confusion about which +process should receive the input typed by the user. To prevent this, +the shell must cooperate with the terminal driver using the protocol +described in this chapter. + +@cindex foreground job +@cindex background job +The shell can give unlimited access to the controlling terminal to only +one process group at a time. This is called the @dfn{foreground job} on +that controlling terminal. Other process groups managed by the shell +that are executing without such access to the terminal are called +@dfn{background jobs}. + +@cindex stopped job +If a background job needs to read from its controlling +terminal, it is @dfn{stopped} by the terminal driver; if the +@code{TOSTOP} mode is set, likewise for writing. The user can stop +a foreground job by typing the SUSP character (@pxref{Special +Characters}) and a program can stop any job by sending it a +@code{SIGSTOP} signal. It's the responsibility of the shell to notice +when jobs stop, to notify the user about them, and to provide mechanisms +for allowing the user to interactively continue stopped jobs and switch +jobs between foreground and background. + +@xref{Access to the Terminal}, for more information about I/O to the +controlling terminal, + +@node Job Control is Optional, Controlling Terminal, Concepts of Job Control , Job Control +@section Job Control is Optional +@cindex job control is optional + +Not all operating systems support job control. The GNU system does +support job control, but if you are using the GNU library on some other +system, that system may not support job control itself. + +You can use the @code{_POSIX_JOB_CONTROL} macro to test at compile-time +whether the system supports job control. @xref{System Options}. + +If job control is not supported, then there can be only one process +group per session, which behaves as if it were always in the foreground. +The functions for creating additional process groups simply fail with +the error code @code{ENOSYS}. + +The macros naming the various job control signals (@pxref{Job Control +Signals}) are defined even if job control is not supported. However, +the system never generates these signals, and attempts to send a job +control signal or examine or specify their actions report errors or do +nothing. + + +@node Controlling Terminal, Access to the Terminal, Job Control is Optional, Job Control +@section Controlling Terminal of a Process + +One of the attributes of a process is its controlling terminal. Child +processes created with @code{fork} inherit the controlling terminal from +their parent process. In this way, all the processes in a session +inherit the controlling terminal from the session leader. A session +leader that has control of a terminal is called the @dfn{controlling +process} of that terminal. + +@cindex controlling process +You generally do not need to worry about the exact mechanism used to +allocate a controlling terminal to a session, since it is done for you +by the system when you log in. +@c ??? How does GNU system let a process get a ctl terminal. + +An individual process disconnects from its controlling terminal when it +calls @code{setsid} to become the leader of a new session. +@xref{Process Group Functions}. + +@c !!! explain how it gets a new one (by opening any terminal) +@c ??? How you get a controlling terminal is system-dependent. +@c We should document how this will work in the GNU system when it is decided. +@c What Unix does is not clean and I don't think GNU should use that. + +@node Access to the Terminal, Orphaned Process Groups, Controlling Terminal, Job Control +@section Access to the Controlling Terminal +@cindex controlling terminal, access to + +Processes in the foreground job of a controlling terminal have +unrestricted access to that terminal; background proesses do not. This +section describes in more detail what happens when a process in a +background job tries to access its controlling terminal. + +@cindex @code{SIGTTIN}, from background job +When a process in a background job tries to read from its controlling +terminal, the process group is usually sent a @code{SIGTTIN} signal. +This normally causes all of the processes in that group to stop (unless +they handle the signal and don't stop themselves). However, if the +reading process is ignoring or blocking this signal, then @code{read} +fails with an @code{EIO} error instead. + +@cindex @code{SIGTTOU}, from background job +Similarly, when a process in a background job tries to write to its +controlling terminal, the default behavior is to send a @code{SIGTTOU} +signal to the process group. However, the behavior is modified by the +@code{TOSTOP} bit of the local modes flags (@pxref{Local Modes}). If +this bit is not set (which is the default), then writing to the +controlling terminal is always permitted without sending a signal. +Writing is also permitted if the @code{SIGTTOU} signal is being ignored +or blocked by the writing process. + +Most other terminal operations that a program can do are treated as +reading or as writing. (The description of each operation should say +which.) + +For more information about the primitive @code{read} and @code{write} +functions, see @ref{I/O Primitives}. + + +@node Orphaned Process Groups, Implementing a Shell, Access to the Terminal, Job Control +@section Orphaned Process Groups +@cindex orphaned process group + +When a controlling process terminates, its terminal becomes free and a +new session can be established on it. (In fact, another user could log +in on the terminal.) This could cause a problem if any processes from +the old session are still trying to use that terminal. + +To prevent problems, process groups that continue running even after the +session leader has terminated are marked as @dfn{orphaned process +groups}. + +When a process group becomes an orphan, its processes are sent a +@code{SIGHUP} signal. Ordinarily, this causes the processes to +terminate. However, if a program ignores this signal or establishes a +handler for it (@pxref{Signal Handling}), it can continue running as in +the orphan process group even after its controlling process terminates; +but it still cannot access the terminal any more. + +@node Implementing a Shell, Functions for Job Control, Orphaned Process Groups, Job Control +@section Implementing a Job Control Shell + +This section describes what a shell must do to implement job control, by +presenting an extensive sample program to illustrate the concepts +involved. + +@iftex +@itemize @bullet +@item +@ref{Data Structures}, introduces the example and presents +its primary data structures. + +@item +@ref{Initializing the Shell}, discusses actions which the shell must +perform to prepare for job control. + +@item +@ref{Launching Jobs}, includes information about how to create jobs +to execute commands. + +@item +@ref{Foreground and Background}, discusses what the shell should +do differently when launching a job in the foreground as opposed to +a background job. + +@item +@ref{Stopped and Terminated Jobs}, discusses reporting of job status +back to the shell. + +@item +@ref{Continuing Stopped Jobs}, tells you how to continue jobs that +have been stopped. + +@item +@ref{Missing Pieces}, discusses other parts of the shell. +@end itemize +@end iftex + +@menu +* Data Structures:: Introduction to the sample shell. +* Initializing the Shell:: What the shell must do to take + responsibility for job control. +* Launching Jobs:: Creating jobs to execute commands. +* Foreground and Background:: Putting a job in foreground of background. +* Stopped and Terminated Jobs:: Reporting job status. +* Continuing Stopped Jobs:: How to continue a stopped job in + the foreground or background. +* Missing Pieces:: Other parts of the shell. +@end menu + +@node Data Structures, Initializing the Shell, , Implementing a Shell +@subsection Data Structures for the Shell + +All of the program examples included in this chapter are part of +a simple shell program. This section presents data structures +and utility functions which are used throughout the example. + +The sample shell deals mainly with two data structures. The +@code{job} type contains information about a job, which is a +set of subprocesses linked together with pipes. The @code{process} type +holds information about a single subprocess. Here are the relevant +data structure declarations: + +@smallexample +@group +/* @r{A process is a single process.} */ +typedef struct process +@{ + struct process *next; /* @r{next process in pipeline} */ + char **argv; /* @r{for exec} */ + pid_t pid; /* @r{process ID} */ + char completed; /* @r{true if process has completed} */ + char stopped; /* @r{true if process has stopped} */ + int status; /* @r{reported status value} */ +@} process; +@end group + +@group +/* @r{A job is a pipeline of processes.} */ +typedef struct job +@{ + struct job *next; /* @r{next active job} */ + char *command; /* @r{command line, used for messages} */ + process *first_process; /* @r{list of processes in this job} */ + pid_t pgid; /* @r{process group ID} */ + char notified; /* @r{true if user told about stopped job} */ + struct termios tmodes; /* @r{saved terminal modes} */ + int stdin, stdout, stderr; /* @r{standard i/o channels} */ +@} job; + +/* @r{The active jobs are linked into a list. This is its head.} */ +job *first_job = NULL; +@end group +@end smallexample + +Here are some utility functions that are used for operating on @code{job} +objects. + +@smallexample +@group +/* @r{Find the active job with the indicated @var{pgid}.} */ +job * +find_job (pid_t pgid) +@{ + job *j; + + for (j = first_job; j; j = j->next) + if (j->pgid == pgid) + return j; + return NULL; +@} +@end group + +@group +/* @r{Return true if all processes in the job have stopped or completed.} */ +int +job_is_stopped (job *j) +@{ + process *p; + + for (p = j->first_process; p; p = p->next) + if (!p->completed && !p->stopped) + return 0; + return 1; +@} +@end group + +@group +/* @r{Return true if all processes in the job have completed.} */ +int +job_is_completed (job *j) +@{ + process *p; + + for (p = j->first_process; p; p = p->next) + if (!p->completed) + return 0; + return 1; +@} +@end group +@end smallexample + + +@node Initializing the Shell, Launching Jobs, Data Structures, Implementing a Shell +@subsection Initializing the Shell +@cindex job control, enabling +@cindex subshell + +When a shell program that normally performs job control is started, it +has to be careful in case it has been invoked from another shell that is +already doing its own job control. + +A subshell that runs interactively has to ensure that it has been placed +in the foreground by its parent shell before it can enable job control +itself. It does this by getting its initial process group ID with the +@code{getpgrp} function, and comparing it to the process group ID of the +current foreground job associated with its controlling terminal (which +can be retrieved using the @code{tcgetpgrp} function). + +If the subshell is not running as a foreground job, it must stop itself +by sending a @code{SIGTTIN} signal to its own process group. It may not +arbitrarily put itself into the foreground; it must wait for the user to +tell the parent shell to do this. If the subshell is continued again, +it should repeat the check and stop itself again if it is still not in +the foreground. + +@cindex job control, enabling +Once the subshell has been placed into the foreground by its parent +shell, it can enable its own job control. It does this by calling +@code{setpgid} to put itself into its own process group, and then +calling @code{tcsetpgrp} to place this process group into the +foreground. + +When a shell enables job control, it should set itself to ignore all the +job control stop signals so that it doesn't accidentally stop itself. +You can do this by setting the action for all the stop signals to +@code{SIG_IGN}. + +A subshell that runs non-interactively cannot and should not support job +control. It must leave all processes it creates in the same process +group as the shell itself; this allows the non-interactive shell and its +child processes to be treated as a single job by the parent shell. This +is easy to do---just don't use any of the job control primitives---but +you must remember to make the shell do it. + + +Here is the initialization code for the sample shell that shows how to +do all of this. + +@smallexample +/* @r{Keep track of attributes of the shell.} */ + +#include <sys/types.h> +#include <termios.h> +#include <unistd.h> + +pid_t shell_pgid; +struct termios shell_tmodes; +int shell_terminal; +int shell_is_interactive; + + +/* @r{Make sure the shell is running interactively as the foreground job} + @r{before proceeding.} */ + +void +init_shell () +@{ + + /* @r{See if we are running interactively.} */ + shell_terminal = STDIN_FILENO; + shell_is_interactive = isatty (shell_terminal); + + if (shell_is_interactive) + @{ + /* @r{Loop until we are in the foreground.} */ + while (tcgetpgrp (shell_terminal) != (shell_pgid = getpgrp ())) + kill (- shell_pgid, SIGTTIN); + + /* @r{Ignore interactive and job-control signals.} */ + signal (SIGINT, SIG_IGN); + signal (SIGQUIT, SIG_IGN); + signal (SIGTSTP, SIG_IGN); + signal (SIGTTIN, SIG_IGN); + signal (SIGTTOU, SIG_IGN); + signal (SIGCHLD, SIG_IGN); + + /* @r{Put ourselves in our own process group.} */ + shell_pgid = getpid (); + if (setpgid (shell_pgid, shell_pgid) < 0) + @{ + perror ("Couldn't put the shell in its own process group"); + exit (1); + @} + + /* @r{Grab control of the terminal.} */ + tcsetpgrp (shell_terminal, shell_pgid); + + /* @r{Save default terminal attributes for shell.} */ + tcgetattr (shell_terminal, &shell_tmodes); + @} +@} +@end smallexample + + +@node Launching Jobs, Foreground and Background, Initializing the Shell, Implementing a Shell +@subsection Launching Jobs +@cindex launching jobs + +Once the shell has taken responsibility for performing job control on +its controlling terminal, it can launch jobs in response to commands +typed by the user. + +To create the processes in a process group, you use the same @code{fork} +and @code{exec} functions described in @ref{Process Creation Concepts}. +Since there are multiple child processes involved, though, things are a +little more complicated and you must be careful to do things in the +right order. Otherwise, nasty race conditions can result. + +You have two choices for how to structure the tree of parent-child +relationships among the processes. You can either make all the +processes in the process group be children of the shell process, or you +can make one process in group be the ancestor of all the other processes +in that group. The sample shell program presented in this chapter uses +the first approach because it makes bookkeeping somewhat simpler. + +@cindex process group leader +@cindex process group ID +As each process is forked, it should put itself in the new process group +by calling @code{setpgid}; see @ref{Process Group Functions}. The first +process in the new group becomes its @dfn{process group leader}, and its +process ID becomes the @dfn{process group ID} for the group. + +@cindex race conditions, relating to job control +The shell should also call @code{setpgid} to put each of its child +processes into the new process group. This is because there is a +potential timing problem: each child process must be put in the process +group before it begins executing a new program, and the shell depends on +having all the child processes in the group before it continues +executing. If both the child processes and the shell call +@code{setpgid}, this ensures that the right things happen no matter which +process gets to it first. + +If the job is being launched as a foreground job, the new process group +also needs to be put into the foreground on the controlling terminal +using @code{tcsetpgrp}. Again, this should be done by the shell as well +as by each of its child processes, to avoid race conditions. + +The next thing each child process should do is to reset its signal +actions. + +During initialization, the shell process set itself to ignore job +control signals; see @ref{Initializing the Shell}. As a result, any child +processes it creates also ignore these signals by inheritance. This is +definitely undesirable, so each child process should explicitly set the +actions for these signals back to @code{SIG_DFL} just after it is forked. + +Since shells follow this convention, applications can assume that they +inherit the correct handling of these signals from the parent process. +But every application has a responsibility not to mess up the handling +of stop signals. Applications that disable the normal interpretation of +the SUSP character should provide some other mechanism for the user to +stop the job. When the user invokes this mechanism, the program should +send a @code{SIGTSTP} signal to the process group of the process, not +just to the process itself. @xref{Signaling Another Process}. + +Finally, each child process should call @code{exec} in the normal way. +This is also the point at which redirection of the standard input and +output channels should be handled. @xref{Duplicating Descriptors}, +for an explanation of how to do this. + +Here is the function from the sample shell program that is responsible +for launching a program. The function is executed by each child process +immediately after it has been forked by the shell, and never returns. + +@smallexample +void +launch_process (process *p, pid_t pgid, + int infile, int outfile, int errfile, + int foreground) +@{ + pid_t pid; + + if (shell_is_interactive) + @{ + /* @r{Put the process into the process group and give the process group} + @r{the terminal, if appropriate.} + @r{This has to be done both by the shell and in the individual} + @r{child processes because of potential race conditions.} */ + pid = getpid (); + if (pgid == 0) pgid = pid; + setpgid (pid, pgid); + if (foreground) + tcsetpgrp (shell_terminal, pgid); + + /* @r{Set the handling for job control signals back to the default.} */ + signal (SIGINT, SIG_DFL); + signal (SIGQUIT, SIG_DFL); + signal (SIGTSTP, SIG_DFL); + signal (SIGTTIN, SIG_DFL); + signal (SIGTTOU, SIG_DFL); + signal (SIGCHLD, SIG_DFL); + @} + + /* @r{Set the standard input/output channels of the new process.} */ + if (infile != STDIN_FILENO) + @{ + dup2 (infile, STDIN_FILENO); + close (infile); + @} + if (outfile != STDOUT_FILENO) + @{ + dup2 (outfile, STDOUT_FILENO); + close (outfile); + @} + if (errfile != STDERR_FILENO) + @{ + dup2 (errfile, STDERR_FILENO); + close (errfile); + @} + + /* @r{Exec the new process. Make sure we exit.} */ + execvp (p->argv[0], p->argv); + perror ("execvp"); + exit (1); +@} +@end smallexample + +If the shell is not running interactively, this function does not do +anything with process groups or signals. Remember that a shell not +performing job control must keep all of its subprocesses in the same +process group as the shell itself. + +Next, here is the function that actually launches a complete job. +After creating the child processes, this function calls some other +functions to put the newly created job into the foreground or background; +these are discussed in @ref{Foreground and Background}. + +@smallexample +void +launch_job (job *j, int foreground) +@{ + process *p; + pid_t pid; + int mypipe[2], infile, outfile; + + infile = j->stdin; + for (p = j->first_process; p; p = p->next) + @{ + /* @r{Set up pipes, if necessary.} */ + if (p->next) + @{ + if (pipe (mypipe) < 0) + @{ + perror ("pipe"); + exit (1); + @} + outfile = mypipe[1]; + @} + else + outfile = j->stdout; + + /* @r{Fork the child processes.} */ + pid = fork (); + if (pid == 0) + /* @r{This is the child process.} */ + launch_process (p, j->pgid, infile, + outfile, j->stderr, foreground); + else if (pid < 0) + @{ + /* @r{The fork failed.} */ + perror ("fork"); + exit (1); + @} + else + @{ + /* @r{This is the parent process.} */ + p->pid = pid; + if (shell_is_interactive) + @{ + if (!j->pgid) + j->pgid = pid; + setpgid (pid, j->pgid); + @} + @} + + /* @r{Clean up after pipes.} */ + if (infile != j->stdin) + close (infile); + if (outfile != j->stdout) + close (outfile); + infile = mypipe[0]; + @} + + format_job_info (j, "launched"); + + if (!shell_is_interactive) + wait_for_job (j); + else if (foreground) + put_job_in_foreground (j, 0); + else + put_job_in_background (j, 0); +@} +@end smallexample + + +@node Foreground and Background, Stopped and Terminated Jobs, Launching Jobs, Implementing a Shell +@subsection Foreground and Background + +Now let's consider what actions must be taken by the shell when it +launches a job into the foreground, and how this differs from what +must be done when a background job is launched. + +@cindex foreground job, launching +When a foreground job is launched, the shell must first give it access +to the controlling terminal by calling @code{tcsetpgrp}. Then, the +shell should wait for processes in that process group to terminate or +stop. This is discussed in more detail in @ref{Stopped and Terminated +Jobs}. + +When all of the processes in the group have either completed or stopped, +the shell should regain control of the terminal for its own process +group by calling @code{tcsetpgrp} again. Since stop signals caused by +I/O from a background process or a SUSP character typed by the user +are sent to the process group, normally all the processes in the job +stop together. + +The foreground job may have left the terminal in a strange state, so the +shell should restore its own saved terminal modes before continuing. In +case the job is merely been stopped, the shell should first save the +current terminal modes so that it can restore them later if the job is +continued. The functions for dealing with terminal modes are +@code{tcgetattr} and @code{tcsetattr}; these are described in +@ref{Terminal Modes}. + +Here is the sample shell's function for doing all of this. + +@smallexample +@group +/* @r{Put job @var{j} in the foreground. If @var{cont} is nonzero,} + @r{restore the saved terminal modes and send the process group a} + @r{@code{SIGCONT} signal to wake it up before we block.} */ + +void +put_job_in_foreground (job *j, int cont) +@{ + /* @r{Put the job into the foreground.} */ + tcsetpgrp (shell_terminal, j->pgid); +@end group + +@group + /* @r{Send the job a continue signal, if necessary.} */ + if (cont) + @{ + tcsetattr (shell_terminal, TCSADRAIN, &j->tmodes); + if (kill (- j->pgid, SIGCONT) < 0) + perror ("kill (SIGCONT)"); + @} +@end group + + /* @r{Wait for it to report.} */ + wait_for_job (j); + + /* @r{Put the shell back in the foreground.} */ + tcsetpgrp (shell_terminal, shell_pgid); + +@group + /* @r{Restore the shell's terminal modes.} */ + tcgetattr (shell_terminal, &j->tmodes); + tcsetattr (shell_terminal, TCSADRAIN, &shell_tmodes); +@} +@end group +@end smallexample + +@cindex background job, launching +If the process group is launched as a background job, the shell should +remain in the foreground itself and continue to read commands from +the terminal. + +In the sample shell, there is not much that needs to be done to put +a job into the background. Here is the function it uses: + +@smallexample +/* @r{Put a job in the background. If the cont argument is true, send} + @r{the process group a @code{SIGCONT} signal to wake it up.} */ + +void +put_job_in_background (job *j, int cont) +@{ + /* @r{Send the job a continue signal, if necessary.} */ + if (cont) + if (kill (-j->pgid, SIGCONT) < 0) + perror ("kill (SIGCONT)"); +@} +@end smallexample + + +@node Stopped and Terminated Jobs, Continuing Stopped Jobs, Foreground and Background, Implementing a Shell +@subsection Stopped and Terminated Jobs + +@cindex stopped jobs, detecting +@cindex terminated jobs, detecting +When a foreground process is launched, the shell must block until all of +the processes in that job have either terminated or stopped. It can do +this by calling the @code{waitpid} function; see @ref{Process +Completion}. Use the @code{WUNTRACED} option so that status is reported +for processes that stop as well as processes that terminate. + +The shell must also check on the status of background jobs so that it +can report terminated and stopped jobs to the user; this can be done by +calling @code{waitpid} with the @code{WNOHANG} option. A good place to +put a such a check for terminated and stopped jobs is just before +prompting for a new command. + +@cindex @code{SIGCHLD}, handling of +The shell can also receive asynchronous notification that there is +status information available for a child process by establishing a +handler for @code{SIGCHLD} signals. @xref{Signal Handling}. + +In the sample shell program, the @code{SIGCHLD} signal is normally +ignored. This is to avoid reentrancy problems involving the global data +structures the shell manipulates. But at specific times when the shell +is not using these data structures---such as when it is waiting for +input on the terminal---it makes sense to enable a handler for +@code{SIGCHLD}. The same function that is used to do the synchronous +status checks (@code{do_job_notification}, in this case) can also be +called from within this handler. + +Here are the parts of the sample shell program that deal with checking +the status of jobs and reporting the information to the user. + +@smallexample +@group +/* @r{Store the status of the process @var{pid} that was returned by waitpid.} + @r{Return 0 if all went well, nonzero otherwise.} */ + +int +mark_process_status (pid_t pid, int status) +@{ + job *j; + process *p; +@end group + +@group + if (pid > 0) + @{ + /* @r{Update the record for the process.} */ + for (j = first_job; j; j = j->next) + for (p = j->first_process; p; p = p->next) + if (p->pid == pid) + @{ + p->status = status; + if (WIFSTOPPED (status)) + p->stopped = 1; + else + @{ + p->completed = 1; + if (WIFSIGNALED (status)) + fprintf (stderr, "%d: Terminated by signal %d.\n", + (int) pid, WTERMSIG (p->status)); + @} + return 0; + @} + fprintf (stderr, "No child process %d.\n", pid); + return -1; + @} +@end group +@group + else if (pid == 0 || errno == ECHILD) + /* @r{No processes ready to report.} */ + return -1; + else @{ + /* @r{Other weird errors.} */ + perror ("waitpid"); + return -1; + @} +@} +@end group + +@group +/* @r{Check for processes that have status information available,} + @r{without blocking.} */ + +void +update_status (void) +@{ + int status; + pid_t pid; + + do + pid = waitpid (WAIT_ANY, &status, WUNTRACED|WNOHANG); + while (!mark_process_status (pid, status)); +@} +@end group + +@group +/* @r{Check for processes that have status information available,} + @r{blocking until all processes in the given job have reported.} */ + +void +wait_for_job (job *j) +@{ + int status; + pid_t pid; + + do + pid = waitpid (WAIT_ANY, &status, WUNTRACED); + while (!mark_process_status (pid, status) + && !job_is_stopped (j) + && !job_is_completed (j)); +@} +@end group + +@group +/* @r{Format information about job status for the user to look at.} */ + +void +format_job_info (job *j, const char *status) +@{ + fprintf (stderr, "%ld (%s): %s\n", (long)j->pgid, status, j->command); +@} +@end group + +@group +/* @r{Notify the user about stopped or terminated jobs.} + @r{Delete terminated jobs from the active job list.} */ + +void +do_job_notification (void) +@{ + job *j, *jlast, *jnext; + process *p; + + /* @r{Update status information for child processes.} */ + update_status (); + + jlast = NULL; + for (j = first_job; j; j = jnext) + @{ + jnext = j->next; + + /* @r{If all processes have completed, tell the user the job has} + @r{completed and delete it from the list of active jobs.} */ + if (job_is_completed (j)) @{ + format_job_info (j, "completed"); + if (jlast) + jlast->next = jnext; + else + first_job = jnext; + free_job (j); + @} + + /* @r{Notify the user about stopped jobs,} + @r{marking them so that we won't do this more than once.} */ + else if (job_is_stopped (j) && !j->notified) @{ + format_job_info (j, "stopped"); + j->notified = 1; + jlast = j; + @} + + /* @r{Don't say anything about jobs that are still running.} */ + else + jlast = j; + @} +@} +@end group +@end smallexample + +@node Continuing Stopped Jobs, Missing Pieces, Stopped and Terminated Jobs, Implementing a Shell +@subsection Continuing Stopped Jobs + +@cindex stopped jobs, continuing +The shell can continue a stopped job by sending a @code{SIGCONT} signal +to its process group. If the job is being continued in the foreground, +the shell should first invoke @code{tcsetpgrp} to give the job access to +the terminal, and restore the saved terminal settings. After continuing +a job in the foreground, the shell should wait for the job to stop or +complete, as if the job had just been launched in the foreground. + +The sample shell program handles both newly created and continued jobs +with the same pair of functions, @w{@code{put_job_in_foreground}} and +@w{@code{put_job_in_background}}. The definitions of these functions +were given in @ref{Foreground and Background}. When continuing a +stopped job, a nonzero value is passed as the @var{cont} argument to +ensure that the @code{SIGCONT} signal is sent and the terminal modes +reset, as appropriate. + +This leaves only a function for updating the shell's internal bookkeeping +about the job being continued: + +@smallexample +@group +/* @r{Mark a stopped job J as being running again.} */ + +void +mark_job_as_running (job *j) +@{ + Process *p; + + for (p = j->first_process; p; p = p->next) + p->stopped = 0; + j->notified = 0; +@} +@end group + +@group +/* @r{Continue the job J.} */ + +void +continue_job (job *j, int foreground) +@{ + mark_job_as_running (j); + if (foreground) + put_job_in_foreground (j, 1); + else + put_job_in_background (j, 1); +@} +@end group +@end smallexample + +@node Missing Pieces, , Continuing Stopped Jobs, Implementing a Shell +@subsection The Missing Pieces + +The code extracts for the sample shell included in this chapter are only +a part of the entire shell program. In particular, nothing at all has +been said about how @code{job} and @code{program} data structures are +allocated and initialized. + +Most real shells provide a complex user interface that has support for +a command language; variables; abbreviations, substitutions, and pattern +matching on file names; and the like. All of this is far too complicated +to explain here! Instead, we have concentrated on showing how to +implement the core process creation and job control functions that can +be called from such a shell. + +Here is a table summarizing the major entry points we have presented: + +@table @code +@item void init_shell (void) +Initialize the shell's internal state. @xref{Initializing the +Shell}. + +@item void launch_job (job *@var{j}, int @var{foreground}) +Launch the job @var{j} as either a foreground or background job. +@xref{Launching Jobs}. + +@item void do_job_notification (void) +Check for and report any jobs that have terminated or stopped. Can be +called synchronously or within a handler for @code{SIGCHLD} signals. +@xref{Stopped and Terminated Jobs}. + +@item void continue_job (job *@var{j}, int @var{foreground}) +Continue the job @var{j}. @xref{Continuing Stopped Jobs}. +@end table + +Of course, a real shell would also want to provide other functions for +managing jobs. For example, it would be useful to have commands to list +all active jobs or to send a signal (such as @code{SIGKILL}) to a job. + + +@node Functions for Job Control, , Implementing a Shell, Job Control +@section Functions for Job Control +@cindex process group functions +@cindex job control functions + +This section contains detailed descriptions of the functions relating +to job control. + +@menu +* Identifying the Terminal:: Determining the controlling terminal's name. +* Process Group Functions:: Functions for manipulating process groups. +* Terminal Access Functions:: Functions for controlling terminal access. +@end menu + + +@node Identifying the Terminal, Process Group Functions, , Functions for Job Control +@subsection Identifying the Controlling Terminal +@cindex controlling terminal, determining + +You can use the @code{ctermid} function to get a file name that you can +use to open the controlling terminal. In the GNU library, it returns +the same string all the time: @code{"/dev/tty"}. That is a special +``magic'' file name that refers to the controlling terminal of the +current process (if it has one). To find the name of the specific +terminal device, use @code{ttyname}; @pxref{Is It a Terminal}. + +The function @code{ctermid} is declared in the header file +@file{stdio.h}. +@pindex stdio.h + +@comment stdio.h +@comment POSIX.1 +@deftypefun {char *} ctermid (char *@var{string}) +The @code{ctermid} function returns a string containing the file name of +the controlling terminal for the current process. If @var{string} is +not a null pointer, it should be an array that can hold at least +@code{L_ctermid} characters; the string is returned in this array. +Otherwise, a pointer to a string in a static area is returned, which +might get overwritten on subsequent calls to this function. + +An empty string is returned if the file name cannot be determined for +any reason. Even if a file name is returned, access to the file it +represents is not guaranteed. +@end deftypefun + +@comment stdio.h +@comment POSIX.1 +@deftypevr Macro int L_ctermid +The value of this macro is an integer constant expression that +represents the size of a string large enough to hold the file name +returned by @code{ctermid}. +@end deftypevr + +See also the @code{isatty} and @code{ttyname} functions, in +@ref{Is It a Terminal}. + + +@node Process Group Functions, Terminal Access Functions, Identifying the Terminal, Functions for Job Control +@subsection Process Group Functions + +Here are descriptions of the functions for manipulating process groups. +Your program should include the header files @file{sys/types.h} and +@file{unistd.h} to use these functions. +@pindex unistd.h +@pindex sys/types.h + +@comment unistd.h +@comment POSIX.1 +@deftypefun pid_t setsid (void) +The @code{setsid} function creates a new session. The calling process +becomes the session leader, and is put in a new process group whose +process group ID is the same as the process ID of that process. There +are initially no other processes in the new process group, and no other +process groups in the new session. + +This function also makes the calling process have no controlling terminal. + +The @code{setsid} function returns the new process group ID of the +calling process if successful. A return value of @code{-1} indicates an +error. The following @code{errno} error conditions are defined for this +function: + +@table @code +@item EPERM +The calling process is already a process group leader, or there is +already another process group around that has the same process group ID. +@end table +@end deftypefun + +The @code{getpgrp} function has two definitions: one derived from BSD +Unix, and one from the POSIX.1 standard. The feature test macros you +have selected (@pxref{Feature Test Macros}) determine which definition +you get. Specifically, you get the BSD version if you define +@code{_BSD_SOURCE}; otherwise, you get the POSIX version if you define +@code{_POSIX_SOURCE} or @code{_GNU_SOURCE}. Programs written for old +BSD systems will not include @file{unistd.h}, which defines +@code{getpgrp} specially under @code{_BSD_SOURCE}. You must link such +programs with the @code{-lbsd-compat} option to get the BSD definition.@refill +@pindex -lbsd-compat +@pindex bsd-compat +@cindex BSD compatibility library + +@comment unistd.h +@comment POSIX.1 +@deftypefn {POSIX.1 Function} pid_t getpgrp (void) +The POSIX.1 definition of @code{getpgrp} returns the process group ID of +the calling process. +@end deftypefn + +@comment unistd.h +@comment BSD +@deftypefn {BSD Function} pid_t getpgrp (pid_t @var{pid}) +The BSD definition of @code{getpgrp} returns the process group ID of the +process @var{pid}. You can supply a value of @code{0} for the @var{pid} +argument to get information about the calling process. +@end deftypefn + +@comment unistd.h +@comment POSIX.1 +@deftypefun int setpgid (pid_t @var{pid}, pid_t @var{pgid}) +The @code{setpgid} function puts the process @var{pid} into the process +group @var{pgid}. As a special case, either @var{pid} or @var{pgid} can +be zero to indicate the process ID of the calling process. + +This function fails on a system that does not support job control. +@xref{Job Control is Optional}, for more information. + +If the operation is successful, @code{setpgid} returns zero. Otherwise +it returns @code{-1}. The following @code{errno} error conditions are +defined for this function: + +@table @code +@item EACCES +The child process named by @var{pid} has executed an @code{exec} +function since it was forked. + +@item EINVAL +The value of the @var{pgid} is not valid. + +@item ENOSYS +The system doesn't support job control. + +@item EPERM +The process indicated by the @var{pid} argument is a session leader, +or is not in the same session as the calling process, or the value of +the @var{pgid} argument doesn't match a process group ID in the same +session as the calling process. + +@item ESRCH +The process indicated by the @var{pid} argument is not the calling +process or a child of the calling process. +@end table +@end deftypefun + +@comment unistd.h +@comment BSD +@deftypefun int setpgrp (pid_t @var{pid}, pid_t @var{pgid}) +This is the BSD Unix name for @code{setpgid}. Both functions do exactly +the same thing. +@end deftypefun + + +@node Terminal Access Functions, , Process Group Functions, Functions for Job Control +@subsection Functions for Controlling Terminal Access + +These are the functions for reading or setting the foreground +process group of a terminal. You should include the header files +@file{sys/types.h} and @file{unistd.h} in your application to use +these functions. +@pindex unistd.h +@pindex sys/types.h + +Although these functions take a file descriptor argument to specify +the terminal device, the foreground job is associated with the terminal +file itself and not a particular open file descriptor. + +@comment unistd.h +@comment POSIX.1 +@deftypefun pid_t tcgetpgrp (int @var{filedes}) +This function returns the process group ID of the foreground process +group associated with the terminal open on descriptor @var{filedes}. + +If there is no foreground process group, the return value is a number +greater than @code{1} that does not match the process group ID of any +existing process group. This can happen if all of the processes in the +job that was formerly the foreground job have terminated, and no other +job has yet been moved into the foreground. + +In case of an error, a value of @code{-1} is returned. The +following @code{errno} error conditions are defined for this function: + +@table @code +@item EBADF +The @var{filedes} argument is not a valid file descriptor. + +@item ENOSYS +The system doesn't support job control. + +@item ENOTTY +The terminal file associated with the @var{filedes} argument isn't the +controlling terminal of the calling process. +@end table +@end deftypefun + +@comment unistd.h +@comment POSIX.1 +@deftypefun int tcsetpgrp (int @var{filedes}, pid_t @var{pgid}) +This function is used to set a terminal's foreground process group ID. +The argument @var{filedes} is a descriptor which specifies the terminal; +@var{pgid} specifies the process group. The calling process must be a +member of the same session as @var{pgid} and must have the same +controlling terminal. + +For terminal access purposes, this function is treated as output. If it +is called from a background process on its controlling terminal, +normally all processes in the process group are sent a @code{SIGTTOU} +signal. The exception is if the calling process itself is ignoring or +blocking @code{SIGTTOU} signals, in which case the operation is +performed and no signal is sent. + +If successful, @code{tcsetpgrp} returns @code{0}. A return value of +@code{-1} indicates an error. The following @code{errno} error +conditions are defined for this function: + +@table @code +@item EBADF +The @var{filedes} argument is not a valid file descriptor. + +@item EINVAL +The @var{pgid} argument is not valid. + +@item ENOSYS +The system doesn't support job control. + +@item ENOTTY +The @var{filedes} isn't the controlling terminal of the calling process. + +@item EPERM +The @var{pgid} isn't a process group in the same session as the calling +process. +@end table +@end deftypefun |