Process Groups, Sessions, and Job Control 703
Using setpgid() in a job-control shell
The restriction that a process may not change the process group ID of one of its
children after that child has performed an exec() affects the programming of job-
control shells, which have the following requirements:
z All of the processes in a job (i.e., a command or a pipeline) must be placed in a
single process group. (We can see the desired result by looking at the two pro-
cess groups created by bash in Figure 34-1.) This step permits the shell to use
killpg() (or, equivalently, kill() with a negative pid argument) to simultaneously
send job-control signals to all of the members of the process group. Naturally,
this step must be carried out before any job-control signals are sent.
z Each of the child processes must be transferred to the process group before it
execs a program, since the program itself is ignorant of manipulations of the
process group ID.
For each process in the job, either the parent or the child could use setpgid() to change
the process group ID of the child. However, because the scheduling of the parent and
child is indeterminate after a fork() (Section 24.4), we can’t rely on the parent changing
the child’s process group ID before the child does an exec(); nor can we rely on the
child changing its process group ID before the parent tries to send any job-control
signals to it. (Dependence on either one of these behaviors would result in a race con-
dition.) Therefore, job-control shells are programmed so that the parent and the
child process both call setpgid() to change the child’s process group ID to the same
value immediately after a fork(), and the parent ignores any occurrence of the
EACCES error on the setpgid() call. In other words, in a job-control shell, we’ll find
code something like that shown in Listing 34-1.
Listing 34-1: How a job-control shell sets the process group ID of a child process
pid_t childPid;
pid_t pipelinePgid; / PGID to which processes in a pipeline
are to be assigned /
/ Other code /
childPid = fork();
switch (childPid) {
case -1: / fork() failed /
/ Handle error /
case 0: / Child /
if (setpgid(0, pipelinePgid) == -1)
/ Handle error /
/ Child carries on to exec the required program /
default: / Parent (shell) /
if (setpgid(childPid, pipelinePgid) == -1 && errno != EACCES)
/ Handle error /
/ Parent carries on to do other things /
}