9.9. Shell Execution of ProgramsLet'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.
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
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 shellNow 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. |