📄 job.texi
字号:
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 jobsOnce the shell has taken responsibility for performing job control onits controlling terminal, it can launch jobs in response to commandstyped 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 alittle more complicated and you must be careful to do things in theright order. Otherwise, nasty race conditions can result.You have two choices for how to structure the tree of parent-childrelationships among the processes. You can either make all theprocesses in the process group be children of the shell process, or youcan make one process in group be the ancestor of all the other processesin that group. The sample shell program presented in this chapter usesthe first approach because it makes bookkeeping somewhat simpler.@cindex process group leader@cindex process group IDAs each process is forked, it should put itself in the new process groupby calling @code{setpgid}; see @ref{Process Group Functions}. The firstprocess in the new group becomes its @dfn{process group leader}, and itsprocess ID becomes the @dfn{process group ID} for the group.@cindex race conditions, relating to job controlThe shell should also call @code{setpgid} to put each of its childprocesses into the new process group. This is because there is apotential timing problem: each child process must be put in the processgroup before it begins executing a new program, and the shell depends onhaving all the child processes in the group before it continuesexecuting. If both the child processes and the shell call@code{setpgid}, this ensures that the right things happen no matter whichprocess gets to it first.If the job is being launched as a foreground job, the new process groupalso needs to be put into the foreground on the controlling terminalusing @code{tcsetpgrp}. Again, this should be done by the shell as wellas by each of its child processes, to avoid race conditions.The next thing each child process should do is to reset its signalactions.During initialization, the shell process set itself to ignore jobcontrol signals; see @ref{Initializing the Shell}. As a result, any childprocesses it creates also ignore these signals by inheritance. This isdefinitely undesirable, so each child process should explicitly set theactions for these signals back to @code{SIG_DFL} just after it is forked.Since shells follow this convention, applications can assume that theyinherit the correct handling of these signals from the parent process.But every application has a responsibility not to mess up the handlingof stop signals. Applications that disable the normal interpretation ofthe SUSP character should provide some other mechanism for the user tostop the job. When the user invokes this mechanism, the program shouldsend a @code{SIGTSTP} signal to the process group of the process, notjust 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 responsiblefor launching a program. The function is executed by each child processimmediately after it has been forked by the shell, and never returns.@smallexamplevoidlaunch_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 smallexampleIf the shell is not running interactively, this function does not doanything with process groups or signals. Remember that a shell notperforming job control must keep all of its subprocesses in the sameprocess 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 otherfunctions to put the newly created job into the foreground or background;these are discussed in @ref{Foreground and Background}.@smallexamplevoidlaunch_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 BackgroundNow let's consider what actions must be taken by the shell when itlaunches a job into the foreground, and how this differs from whatmust be done when a background job is launched.@cindex foreground job, launchingWhen a foreground job is launched, the shell must first give it accessto the controlling terminal by calling @code{tcsetpgrp}. Then, theshell should wait for processes in that process group to terminate orstop. This is discussed in more detail in @ref{Stopped and TerminatedJobs}.When all of the processes in the group have either completed or stopped,the shell should regain control of the terminal for its own processgroup by calling @code{tcsetpgrp} again. Since stop signals caused byI/O from a background process or a SUSP character typed by the userare sent to the process group, normally all the processes in the jobstop together.The foreground job may have left the terminal in a strange state, so theshell should restore its own saved terminal modes before continuing. Incase the job is merely been stopped, the shell should first save thecurrent terminal modes so that it can restore them later if the job iscontinued. 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.} */voidput_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, launchingIf the process group is launched as a background job, the shell shouldremain in the foreground itself and continue to read commands fromthe terminal. In the sample shell, there is not much that needs to be done to puta 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.} */voidput_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, detectingWhen a foreground process is launched, the shell must block until all ofthe processes in that job have either terminated or stopped. It can dothis by calling the @code{waitpid} function; see @ref{ProcessCompletion}. Use the @code{WUNTRACED} option so that status is reportedfor processes that stop as well as processes that terminate.The shell must also check on the status of background jobs so that itcan report terminated and stopped jobs to the user; this can be done bycalling @code{waitpid} with the @code{WNOHANG} option. A good place toput a such a check for terminated and stopped jobs is just beforeprompting for a new command.@cindex @code{SIGCHLD}, handling ofThe shell can also receive asynchronous notification that there isstatus information available for a child process by establishing ahandler for @code{SIGCHLD} signals. @xref{Signal Handling}.In the sample shell program, the @code{SIGCHLD} signal is normallyignored. This is to avoid reentrancy problems involving the global datastructures the shell manipulates. But at specific times when the shellis not using these data structures---such as when it is waiting forinput on the terminal---it makes sense to enable a handler for@code{SIGCHLD}. The same function that is used to do the synchronousstatus checks (@code{do_job_notification}, in this case) can also becalled from within this handler.Here are the parts of the sample shell program that deal with checkingthe 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.} */intmark_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.} */voidupdate_status (void)@{ int status;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -