Team BBL
Previous Page Next Page

9.9. Shell Execution of Programs

Let's examine how the shells execute programs and how this relates to the concepts of process groups, controlling terminals, and sessions. To do this, we'll use the ps command again.

First, we'll use a shell that doesn't support job controlthe classic Bourne shell running on Solaris. If we execute

   ps -o pid,ppid,pgid,sid,comm

the output is

     PID  PPID  PGID  SID  COMMAND
     949   947   949  949  sh
    1774   949   949  949  ps

The parent of the ps command is the shell, which we would expect. Both the shell and the ps command are in the same session and foreground process group (949). We say that 949 is the foreground process group because that is what you get when you execute a command with a shell that doesn't support job control.

Some platforms support an option to have the ps(1) command print the process group ID associated with the session's controlling terminal. This value would be shown under the TPGID column. Unfortunately, the output of the ps command often differs among versions of the UNIX System. For example, Solaris 9 doesn't support this option. Under FreeBSD 5.2.1 and Mac OS X 10.3, the command

    ps -o pid,ppid,pgid,sess,tpgid,command

and under Linux 2.4.22, the command

    ps -o pid,ppid,pgrp,session,tpgid,comm

print exactly the information we want.

Note that it is a misnomer to associate a process with a terminal process group ID (the TPGID column). A process does not have a terminal process control group. A process belongs to a process group, and the process group belongs to a session. The session may or may not have a controlling terminal. If the session does have a controlling terminal, then the terminal device knows the process group ID of the foreground process. This value can be set in the terminal driver with the tcsetpgrp function, as we show in Figure 9.8. The foreground process group ID is an attribute of the terminal, not the process. This value from the terminal device driver is what ps prints as the TPGID. If it finds that the session doesn't have a controlling terminal, ps prints 1.

If we execute the command in the background,

   ps -o pid,ppid,pgid,sid,comm &

the only value that changes is the process ID of the command:

        PID  PPID  PGID  SID COMMAND
        949   947   949  949 sh
       1812   949   949  949 ps

This shell doesn't know about job control, so the background job is not put into its own process group and the controlling terminal isn't taken away from the background job.

Let's now look at how the Bourne shell handles a pipeline. When we execute

   ps -o pid,ppid,pgid,sid,comm | cat1

the output is

    PID  PPID  PGID  SID COMMAND
    949   947   949  949 sh
   1823   949   949  949 cat1
   1824  1823   949  949 ps

(The program cat1 is just a copy of the standard cat program, with a different name. We have another copy of cat with the name cat2, which we'll use later in this section. When we have two copies of cat in a pipeline, the different names let us differentiate between the two programs.) Note that the last process in the pipeline is the child of the shell and that the first process in the pipeline is a child of the last process. It appears that the shell forks a copy of itself and that this copy then forks to make each of the previous processes in the pipeline.

If we execute the pipeline in the background,

   ps -o pid,ppid,pgid,sid,comm | cat1 &

only the process IDs change. Since the shell doesn't handle job control, the process group ID of the background processes remains 949, as does the process group ID of the session.

What happens in this case if a background process tries to read from its controlling terminal? For example, suppose that we execute

   cat > temp.foo &

With job control, this is handled by placing the background job into a background process group, which causes the signal SIGTTIN to be generated if the background job tries to read from the controlling terminal. The way this is handled without job control is that the shell automatically redirects the standard input of a background process to /dev/null, if the process doesn't redirect standard input itself. A read from /dev/null generates an end of file. This means that our background cat process immediately reads an end of file and terminates normally.

The previous paragraph adequately handles the case of a background process accessing the controlling terminal through its standard input, but what happens if a background process specifically opens /dev/tty and reads from the controlling terminal? The answer is "it depends," but it's probably not what we want. For example,

   crypt < salaries | lpr &

is such a pipeline. We run it in the background, but the crypt program opens /dev/tty, changes the terminal characteristics (to disable echoing), reads from the device, and resets the terminal characteristics. When we execute this background pipeline, the prompt Password: from crypt is printed on the terminal, but what we enter (the encryption password) is read by the shell, which tries to execute a command of that name. The next line we enter to the shell is taken as the password, and the file is not encrypted correctly, sending junk to the printer. Here we have two processes trying to read from the same device at the same time, and the result depends on the system. Job control, as we described earlier, handles this multiplexing of a single terminal between multiple processes in a better fashion.

Returning to our Bourne shell example, if we execute three processes in the pipeline, we can examine the process control used by this shell:

   ps -o pid,ppid,pgid,sid,comm | cat1 | cat2

generates the following output

     PID  PPID  PGID  SID COMMAND
     949   947   949  949 sh
    1988   949   949  949 cat2
    1989  1988   949  949 ps
    1990  1988   949  949 cat1

Don't be alarmed if the output on your system doesn't show the proper command names. Sometimes you might get results such as

        PID  PPID  PGID  SID COMMAND
        949   947   949  949 sh
       1831   949   949  949 sh
       1832  1831   949  949 ps
       1833  1831   949  949 sh

What's happening here is that the ps process is racing with the shell, which is forking and executing the cat commands. In this case, the shell hasn't yet completed the call to exec when ps has obtained the list of processes to print.

Again, the last process in the pipeline is the child of the shell, and all previous processes in the pipeline are children of the last process. Figure 9.9 shows what is happening. Since the last process in the pipeline is the child of the login shell, the shell is notified when that process (cat2) terminates.

Figure 9.9. Processes in the pipeline ps | cat1 | cat2 when invoked by Bourne shell


Now let's examine the same examples using a job-control shell running on Linux. This shows the way these shells handle background jobs. We'll use the Bourne-again shell in this example; the results with other job-control shells are almost identical.

   ps -o pid,ppid,pgrp,session,tpgid,comm

gives us

     PID  PPID  PGRP  SESS  TPGID COMMAND
    2837  2818  2837  2837   5796 bash
    5796  2837  5796  2837   5796 ps

(Starting with this example, we show the foreground process group in a bolder font.) We immediately have a difference from our Bourne shell example. The Bourne-again shell places the foreground job (ps) into its own process group (5796). The ps command is the process group leader and the only process in this process group.

Furthermore, this process group is the foreground process group, since it has the controlling terminal. Our login shell is a background process group while the ps command executes. Note, however, that both process groups, 2837 and 5796, are members of the same session. Indeed, we'll see that the session never changes through our examples in this section.

Executing this process in the background,

   ps -o pid,ppid,pgrp,session,tpgid,comm &

gives us

     PID  PPID  PGRP  SESS  TPGID COMMAND
    2837  2818  2837  2837   2837 bash
    5797  2837  5797  2837   2837 ps

Again, the ps command is placed into its own process group, but this time the process group (5797) is no longer the foreground process group. It is a background process group. The TPGID of 2837 indicates that the foreground process group is our login shell.

Executing two processes in a pipeline, as in

   ps -o pid,ppid,pgrp,session,tpgid,comm | cat1

gives us

     PID  PPID  PGRP  SESS  TPGID COMMAND
    2837  2818  2837  2837   5799 bash
    5799  2837  5799  2837   5799 ps
    5800  2837  5799  2837   5799 cat1

Both processes, ps and cat1, are placed into a new process group (5799), and this is the foreground process group. We can also see another difference between this example and the similar Bourne shell example. The Bourne shell created the last process in the pipeline first, and this final process was the parent of the first process. Here, the Bourne-again shell is the parent of both processes. If we execute this pipeline in the background,

   ps -o pid,ppid,pgrp,session,tpgid,comm | cat1 &

the results are similar, but now ps and cat1 are placed in the same background process group:

     PID  PPID  PGRP  SESS  TPGID COMMAND
    2837  2818  2837  2837   2837 bash
    5801  2837  5801  2837   2837 ps
    5802  2837  5801  2837   2837 cat1

Note that the order in which a shell creates processes can differ depending on the particular shell in use.

    Team BBL
    Previous Page Next Page