Advanced Programming in the UNIX Environment: Second Edition [Electronic resources]

W. Richard Stevens; Stephen A. Rago

نسخه متنی -صفحه : 369/ 141
نمايش فراداده

8.6. wait and waitpid Functions

When a process terminates, either normally or abnormally, the kernel notifies the parent by sending the SIGCHLD signal to the parent. Because the termination of a child is an asynchronous eventit can happen at any time while the parent is runningthis signal is the asynchronous notification from the kernel to the parent. The parent can choose to ignore this signal, or it can provide a function that is called when the signal occurs: a signal handler. The default action for this signal is to be ignored. We describe these options in Chapter 10. For now, we need to be aware that a process that calls wait or waitpid can

  • Block, if all of its children are still running

  • Return immediately with the termination status of a child, if a child has terminated and is waiting for its termination status to be fetched

  • Return immediately with an error, if it doesn't have any child processes

If the process is calling wait because it received the SIGCHLD signal, we expect wait to return immediately. But if we call it at any random point in time, it can block.

#include <sys/wait.h> pid_t wait(int *

statloc ); pid_t waitpid(pid_t

pid , int *

statloc , int

options );

Both return: process ID if OK, 0 (see later), or 1 on error

The differences between these two functions are as follows.

  • The wait function can block the caller until a child process terminates, whereas waitpid has an option that prevents it from blocking.

  • The waitpid function doesn't wait for the child that terminates first; it has a number of options that control which process it waits for.

If a child has already terminated and is a zombie, wait returns immediately with that child's status. Otherwise, it blocks the caller until a child terminates. If the caller blocks and has multiple children, wait returns when one terminates. We can always tell which child terminated, because the process ID is returned by the function.

Figure 8.4.

We'll discuss how a process can be stopped in Section 9.8 when we discuss job control.

Example

The function pr_exit in Figure 8.4 to print a description of the termination status. We'll call this function from numerous programs in the text. Note that this function handles the WCOREDUMP macro, if it is defined.

FreeBSD 5.2.1, Linux 2.4.22, Mac OS X 10.3, and Solaris 9 all support the WCOREDUMP macro.

The program shown in Figure 8.6 calls the pr_exit function, demonstrating the various values for the termination status. If we run the program in Figure 8.6, we get

$ ./

a.out normal termination, exit status = 7 abnormal termination, signal number = 6 (core file generated) abnormal termination, signal number = 8 (core file generated)

Unfortunately, there is no portable way to map the signal numbers from WTERMSIG into descriptive names. (See Section 10.21 for one method.) We have to look at the <signal.h> header to verify that SIGABRT has a value of 6 and that SIGFPE has a value of 8.

Figure 8.5. Print a description of the exit status
#include "apue.h" #include <sys/wait.h> void pr_exit(int status) { if (WIFEXITED(status)) printf("normal termination, exit status = %d\n", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) printf("abnormal termination, signal number = %d%s\n", WTERMSIG(status), #ifdef WCOREDUMP WCOREDUMP(status) ? " (core file generated)" : "); #else "); #endif else if (WIFSTOPPED(status)) printf("child stopped, signal number = %d\n", WSTOPSIG(status)); }

Figure 8.6. Demonstrate various exit statuses
#include "apue.h" #include <sys/wait.h> int main(void) { pid_t pid; int status; if ((pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) /* child */ exit(7); if (wait(&status) != pid) /* wait for child */ err_sys("wait error"); pr_exit(status); /* and print its status */ if ((pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) /* child */ abort(); /* generates SIGABRT */ if (wait(&status) != pid) /* wait for child */ err_sys("wait error"); pr_exit(status); /* and print its status */ if ((pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) /* child */ status /= 0; /* divide by 0 generates SIGFPE */ if (wait(&status) != pid) /* wait for child */ err_sys("wait error"); pr_exit(status); /* and print its status */ exit(0); }

As we mentioned, if we have more than one child, wait returns on termination of any of the children. What if we want to wait for a specific process to terminate (assuming we know which process ID we want to wait for)? In older versions of the UNIX System, we would have to call wait and compare the returned process ID with the one we're interested in. If the terminated process wasn't the one we wanted, we would have to save the process ID and termination status and call wait again. We would need to continue doing this until the desired process terminated. The next time we wanted to wait for a specific process, we would go through the list of already terminated processes to see whether we had already waited for it, and if not, call wait Section 9.4.)

pid < 1

Waits for any child whose process group ID equals the absolute value of

pid .

Chapter 10.) With waitpid, however, it's also possible to get an error if the specified process or process group does not exist or is not a child of the calling process.

The

options argument lets us further control the operation of waitpid. This argument is either 0 or is constructed from the bitwise OR of the constants in Figure 8.7.

Figure 8.7. The

options constants for waitpid

Constant

Description

WCONTINUED

If the implementation supports job control, the status of any child specified by

pid that has been continued after being stopped, but whose status has not yet been reported, is returned (XSI extension to POSIX.1).

WNOHANG

The waitpid function will not block if a child specified by

pid is not immediately available. In this case, the return value is 0.

WUNTRACED

If the implementation supports job control, the status of any child specified by

pid that has stopped, and whose status has not been reported since it has stopped, is returned. The WIFSTOPPED macro determines whether the return value corresponds to a stopped child process.

Solaris supports one additional, but nonstandard,

option constant. WNOWAIT has the system keep the process whose termination status is returned by waitpid in a wait state, so that it may be waited for again.

The waitpid function provides three features that aren't provided by the wait function.

  • The waitpid function lets us wait for one particular process, whereas the wait function returns the status of any terminated child. We'll return to this feature when we discuss the popen function.

  • The waitpid function provides a nonblocking version of wait. There are times when we want to fetch a child's status, but we don't want to block.

  • The waitpid function provides support for job control with the WUNtrACED and WCONTINUED options.

  • Example

    Recall our discussion in Section 8.5 about zombie processes. If we want to write a process so that it forks a child but we don't want to wait for the child to complete and we don't want the child to become a zombie until we terminate, the trick is to call fork twice. The program in Figure 8.8 does this.

    We call sleep in the second child to ensure that the first child terminates before printing the parent process ID. After a fork, either the parent or the child can continue executing; we never know which will resume execution first. If we didn't put the second child to sleep, and if it resumed execution after the fork before its parent, the parent process ID that it printed would be that of its parent, not process ID 1.

    Executing the program in Figure 8.8 gives us

    $

    ./a.out $ second child, parent pid = 1

    Note that the shell prints its prompt when the original process terminates, which is before the second child prints its parent process ID.

    Figure 8.8. Avoid zombie processes by calling fork twice
    #include "apue.h" #include <sys/wait.h> int main(void) { pid_t pid; if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) { /* first child */ if ((pid = fork()) < 0) err_sys("fork error"); else if (pid > 0) exit(0); /* parent from second fork == first child */ /* * We're the second child; our parent becomes init as soon * as our real parent calls exit() in the statement above. * Here's where we'd continue executing, knowing that when * we're done, init will reap our status. */ sleep(2); printf("second child, parent pid = %d\n", getppid()); exit(0); } if (waitpid(pid, NULL, 0) != pid) /* wait for first child */ err_sys("waitpid error"); /* * We're the parent (the original process); we continue executing, * knowing that we're not the parent of the second child. */ exit(0); }