Learning the wait Function in C: Usage, Zombie Prevention, and waitpid Differences

目次

1. Introduction

The C language is widely used in the development of system programs and embedded systems, and process management is one of the important topics among them. This article explains the “wait function” in the C language. The wait function is a system call used to achieve synchronization between processes, and it is particularly useful for waiting for the termination of child processes. Through this article, you can learn widely from the basic usage of the wait function to advanced methods, and related topics (e.g., the waitpid function and measures against zombie processes).

2. What is the wait Function in C?

Overview of the wait Function

wait function is one of the system calls used in UNIX-like systems, employed by the parent process to wait for the termination of the child process. This allows the parent process to receive the child process’s termination status.

Basic Operation

wait function is used as follows.
#include 
#include 
#include 

int main() {
    pid_t pid = fork();
    if (pid > 0) {
        // Parent process
        wait(NULL); // Wait until the child process terminates
    } else if (pid == 0) {
        // Child process
        printf("Hello from child process!\n");
    }
    return 0;
}
In the above code, the child process is created using the fork function, and the parent process waits for the child process to terminate using the wait function.

Return Value and Arguments

  • Return ValueIf the child process terminates, it returns its process ID. If an error occurs, it returns -1.
  • ArgumentsBy passing int* status as an argument, it is possible to receive the child process’s termination status.

3. wait Basic Usage of the Function

Simple Code Example

wait The following shows the basic usage of the function.
#include 
#include 
#include 
#include 

int main() {
    pid_t pid = fork();
    if (pid > 0) {
        int status;
        wait(&status); // Obtain the child process's exit status
        if (WIFEXITED(status)) {
            printf("Child exited with status: %d\n", WEXITSTATUS(status));
        }
    } else if (pid == 0) {
        printf("Child process running...\n");
        _exit(0); // Child process termination
    }
    return 0;
}

Explanation of the Sample Code

  1. wait(&status) waits for the child process to terminate.
  2. WIFEXITED(status) is used to check if the child process exited normally.
  3. WEXITSTATUS(status) obtains the child process’s exit code.
In this way, by using the wait function, the parent process can accurately grasp the child process’s termination status.

4. Zombie Processes and wait Function Relationship

What Are Zombie Processes?

A zombie process is a process that occurs when a child process has terminated but the parent process has not properly collected its termination status. In this state, although the child process itself has ended, the process information remains in the system. If there are many zombie processes in the system, it can overload the process table and prevent other processes from operating normally.

Example of Zombie Process Occurrence

The following is a simple example where a zombie process occurs.
#include 
#include 
#include 

int main() {
    pid_t pid = fork();
    if (pid > 0) {
        // Parent process does nothing and waits
        sleep(10);
    } else if (pid == 0) {
        // Child process
        printf("Child process exiting...\n");
        _exit(0);
    }
    return 0;
}
In this example, since the parent process does not collect the child process’s termination status, the child process becomes a zombie after termination.

Preventing Zombie Processes with the wait Function

By using the wait function, the parent process can properly collect the child process’s termination status and prevent zombie processes. The following is an example of the corrected version.
#include 
#include 
#include 
#include 

int main() {
    pid_t pid = fork();
    if (pid > 0) {
        // Parent process
        wait(NULL); // Wait for child process termination to prevent zombie processes
        printf("Parent process: Child process has been reaped.\n");
    } else if (pid == 0) {
        // Child process
        printf("Child process exiting...\n");
        _exit(0);
    }
    return 0;
}
In this program, by using wait(NULL), the parent process waits for the child process to terminate, preventing the occurrence of zombie processes.

Key Points for Handling Zombie Processes

  1. Always use wait or waitpidBy properly collecting the child process’s termination status, zombie processes are prevented.
  2. Method Using Signal HandlersSIGCHLD Capture the signal and automatically collect when the child process terminates.
The following is an example of that.
#include 
#include 
#include 
#include 
#include 

void sigchld_handler(int signo) {
    while (waitpid(-1, NULL, WNOHANG) > 0); // Collect all terminated child processes
}

int main() {
    signal(SIGCHLD, sigchld_handler); // Set SIGCHLD to handler

    pid_t pid = fork();
    if (pid == 0) {
        // Child process
        printf("Child process exiting...\n");
        _exit(0);
    } else if (pid > 0) {
        // Parent process
        printf("Parent process doing other work...\n");
        sleep(10); // SIGCHLD handler activates during other work
    }
    return 0;
}
With this method, terminated child processes are automatically collected without the parent process explicitly calling wait.

5. Differences with the waitpid Function

What is the waitpid Function?

waitpid function is a system call for waiting for the termination of a child process, similar to the wait function, but it allows for more flexible process management. By using waitpid, you can specify a particular child process or wait in non-blocking mode.

Basic Usage and Syntax

The syntax of the waitpid function is as follows.
#include 
#include 
#include 

pid_t waitpid(pid_t pid, int *status, int options);
  • pidSpecifies the ID of the child process to wait for. You can control the behavior using the following special values.
  • pid > 0: Wait for a specific process ID.
  • pid == 0: Wait for any child process in the same process group as the parent process.
  • pid < -1: Wait for all processes in the specified process group.
  • pid == -1: Wait for any child process (same as the wait function).
  • statusPointer to store the child process’s exit status.
  • optionsSpecifies options to modify the behavior. The main values are as follows.
  • WNOHANG: Non-blocking mode. Returns immediately if no child process has terminated.
  • WUNTRACED: Include stopped child processes in the targets.
  • Return Value
  • On normal termination: The PID of the terminated child process.
  • If no child processes: 0 (when WNOHANG is specified).
  • On error: -1.

Example of the waitpid Function

The following is an example of using the waitpid function to wait for a specific child process.
#include 
#include 
#include 
#include 

int main() {
    pid_t pid1 = fork();
    if (pid1 == 0) {
        // Child process 1
        printf("Child 1 running...\n");
        sleep(2);
        _exit(1);
    }

    pid_t pid2 = fork();
    if (pid2 == 0) {
        // Child process 2
        printf("Child 2 running...\n");
        sleep(4);
        _exit(2);
    }

    int status;
    // Wait for the specific child process pid1
    pid_t ret = waitpid(pid1, &status, 0);
    if (ret > 0 && WIFEXITED(status)) {
        printf("Child 1 exited with status: %d\n", WEXITSTATUS(status));
    }

    // Wait for the remaining child process
    waitpid(pid2, &status, 0);
    printf("Child 2 exited with status: %d\n", WEXITSTATUS(status));

    return 0;
}

Main Differences between wait and waitpid

Itemwait Functionwaitpid Function
Wait TargetAny child processCan specify a specific child process
BlockingAlways blockingNon-blocking possible
Option SpecificationNot possibleWNOHANG, WUNTRACED, etc.
FlexibilityLimitedHigh

When to Choose waitpid

  • When you want to manage a specific child processIt is suitable for programs that generate multiple child processes and want to control each one individually.
  • When you want to perform asynchronous processingIf you want to check the process termination status without blocking other processing, the WNOHANG option is convenient.

Selection Guide for wait and waitpid

  1. For simple programs, the wait function is sufficient. If you just need to wait for any child process, flexibility is not required.
  2. For complex process management, the waitpid function is recommended. Especially when asynchronous processing or controlling specific processes is needed, waitpid allows for efficient management.

6. Synchronization in Multithreaded Environments

Differences Between Process Synchronization and Thread Synchronization

In C, processes and threads operate as different management units. Process synchronization (e.g., wait or waitpid functions) controls termination status and resource sharing between multiple processes. On the other hand, thread synchronization manages resources and sequence control between threads within the same process.

Synchronization Processing in Multithreaded Environments

Commonly used for synchronization between threads are condition variables and mutexes. Here, we explain the synchronization method using condition variables from the pthread library.

Basics of Synchronization Using Condition Variables

By using condition variables (pthread_cond_t), waiting and notification between threads can be performed efficiently.

Basic Functions of Condition Variables

  • pthread_cond_waitWaits until the condition is satisfied. Releases the mutex during waiting.
  • pthread_cond_signalWakes up one waiting thread.
  • pthread_cond_broadcastWakes up all waiting threads.

Example of Synchronization Using Condition Variables

The following is a simple example of synchronizing multiple threads using condition variables.
#include 
#include 
#include 


// Mutex and condition variable initialization
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int shared_data = 0;

void* producer(void* arg) {
    pthread_mutex_lock(&mutex);

    printf("Producer: producing data...\n");
    shared_data = 1;

    // Notify with condition variable
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);

    return NULL;
}

void* consumer(void* arg) {
    pthread_mutex_lock(&mutex);

    // Wait until condition is satisfied
    while (shared_data == 0) {
        printf("Consumer: waiting for data...\n");
        pthread_cond_wait(&cond, &mutex);
    }

    printf("Consumer: consumed data!\n");
    pthread_mutex_unlock(&mutex);

    return NULL;
}

int main() {
    pthread_t producer_thread, consumer_thread;

    // Create threads
    pthread_create(&consumer_thread, NULL, consumer, NULL);
    sleep(1); // Sleep to let consumer wait first
    pthread_create(&producer_thread, NULL, producer, NULL);

    // Wait for thread termination
    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);

    return 0;
}

Explanation of the Sample Code

  1. Use of MutexesUses pthread_mutex_lock and pthread_mutex_unlock to control exclusive access to shared resources.
  2. Waiting and Notification with Condition Variables
  • The consumer thread waits for shared_data to be updated using pthread_cond_wait.
  • The producer thread notifies the consumer thread using pthread_cond_signal.
  1. Coordinated Operation Between ThreadsRealizes a simple flow where the producer generates data and the consumer receives it.

Notes on Thread Synchronization

  • Preventing DeadlocksIt is necessary to pay attention to the order of locking and unlocking mutexes.
  • Avoiding Race ConditionsWhen threads try to change conditions simultaneously, it is necessary to properly combine condition variables and mutexes.
  • Considering ScalabilityTo synchronize efficiently between multiple threads, it is important to minimize unnecessary waiting and locking.

Notes on Using the wait Function in Multithreaded Environments

wait function is for synchronization between processes and is not suitable for thread-level synchronization. For thread synchronization, using condition variables and mutexes is safer and more efficient.

7. Troubleshooting and Best Practices

Common Errors and Solutions

When using wait and waitpid in C language, several typical errors may occur. We explain their causes and solutions.

1. wait function returns an error

Cause
  • The child process does not exist.
  • The system call was interrupted (EINTR error).
Solution
  • Check whether the child process exists.
  • If the system call is interrupted, retry in a loop.
int status;
while (wait(&status) == -1) {
    if (errno != EINTR) {
        perror("wait failed");
        break;
    }
}

2. Zombie processes occur

Cause
  • The parent process does not reap the child process’s termination.
Solution
  • In the parent process, use wait or waitpid appropriately.
  • Set a signal handler to automatically reap terminations.

3. Unstable behavior due to race conditions

Cause
  • When multiple parent processes attempt to wait for the same child process.
  • The child process’s termination status is not correctly reaped.
Solution
  • If you need to specify the process ID explicitly, use waitpid.
  • Design the system so that multiple parent processes do not manage the same child process.

Best Practices

1. Proper selection of wait and waitpid

  • For simple programs, wait is sufficient.
  • When complex process management (control of specific child processes or asynchronous processing) is required, use waitpid.

2. Using a signal handler

Using a signal handler allows the parent process to automatically collect the child process’s termination status without explicitly calling wait. This prevents zombie processes while keeping the parent process’s code concise.

3. Thorough error handling

Since wait and waitpid are system calls, errors may occur. Check the return value of every call and handle it appropriately.
pid_t pid = wait(NULL);
if (pid == -1) {
    perror("wait error");
}

4. Implementing asynchronous processing

For asynchronous processing, the following design is recommended.
  • Do not block the main processing; periodically call waitpid with WNOHANG.
  • Poll the child process’s termination status and reap it as needed.
Example:
int status;
pid_t pid;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
    printf("Child process %d terminated.\n", pid);
}

5. Managing the number of processes

In programs that generate many child processes, you need to control the number of processes.
  • Limit the number of child processes created simultaneously.
  • Adopt a design that does not create new processes until existing child processes have terminated.

8. Frequently Asked Questions (FAQ)

Q1: How to prevent zombie processes without using the wait function?

A:You can prevent zombie processes by using signal handlers without using the wait function. The parent process can automatically reap the child process’s exit status by catching the SIGCHLD signal. The following is an example using a signal handler.
#include 
#include 
#include 
#include 
#include 

void handle_sigchld(int sig) {
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main() {
    signal(SIGCHLD, handle_sigchld);

    if (fork() == 0) {
        // Child process
        _exit(0);
    }

    // Parent process processing
    sleep(5);
    printf("Parent process completed.\n");
    return 0;
}

Q2: In what situations should you use waitpid?

A:waitpid is suitable for the following cases.
  • When you want to wait for a specific child process only (specify pid).
  • When you want to manage processes asynchronously (use the WNOHANG option).
  • When you want to check for stopped child processes (use the WUNTRACED option).

Q3: What are the causes when the wait function does not return a value?

A:Possible causes for the wait function not returning a value (returning -1) include the following.
  1. No child process exists.
  • The process has already exited, or fork may have failed.
  1. The system call was interrupted (EINTR error).
  • If interrupted by a signal, retry in a loop.
int status;
pid_t pid;
while ((pid = wait(&status)) == -1) {
    if (errno != EINTR) {
        perror("wait error");
        break;
    }
}

Q4: How to safely use the wait function in a multithreaded program?

A:When using wait in a multithreaded program, pay attention to the following points.
  • wait operates at the process level, so use condition variables or mutexes for thread-level synchronization.
  • It is usually safer to limit child process management to the main thread. If other threads call wait, it may cause unexpected behavior.

Q5: Are there alternatives to the wait function?

A:The following alternatives are available.
  • waitpid functionIt offers high flexibility and can control specific processes.
  • Signal handlerIt handles child process termination asynchronously.
  • Event-driven programmingYou can also manage process termination using an event loop (e.g., select or poll).

Q6: What should you do if a child process does not terminate?

A:The child process may be stopped. In this case, handle it with the following steps.
  1. WUNTRACED option to check for stopped child processes.
  2. If necessary, terminate the child process using the kill system call.

Q7: What is the meaning of the exit status obtained by the wait function?

A:The wait function obtains the status, which includes the following information.
  • Normal termination: WIFEXITED(status) returns true, and the exit code can be obtained with WEXITSTATUS(status).
  • Abnormal termination: If terminated by a signal, WIFSIGNALED(status) returns true, and the terminating signal can be obtained with WTERMSIG(status).
Example:
int status;
wait(&status);
if (WIFEXITED(status)) {
    printf("Exited with code: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
    printf("Killed by signal: %d\n", WTERMSIG(status));
}

Q8: How to manage multiple child processes simultaneously?

A:When managing multiple child processes, it is common to use waitpid in a loop to reap all processes.
pid_t pid;
int status;
while ((pid = waitpid(-1, &status, 0)) > 0) {
    printf("Child process %d exited.\n", pid);
}

9. Summary

In this article, we comprehensively covered the C language wait function, from basic usage to advanced examples, and related topics (waitpid function and zombie process mitigation). Below we summarize the key points.

wait Function Basics

  • wait function is a system call that allows a parent process to wait for a child process to terminate and to collect its termination status.
  • wait(NULL) can be used to wait for a child process easily without checking the exit status.

Applications and Related Topics

  • Zombie Process MitigationIf the termination status of a child process is not collected, a zombie process will occur. Using wait or waitpid appropriately can prevent this.
  • waitpid functionis very useful when you want to wait for a specific child process or perform asynchronous processing.
  • Signal HandlerUsing the SIGCHLD signal allows the parent process to automatically collect termination status without explicitly calling wait.

Cautions in Multithreaded Environments

  • wait function performs interprocess synchronization, but for thread-level synchronization, using condition variables or mutexes is appropriate.
  • It is important to design so that multiple threads do not call wait simultaneously.

Troubleshooting and Best Practices

  • wait and waitpid return values should always be checked, and error handling should be performed appropriately.
  • When asynchronous processing is required, using the WNOHANG option enables efficient process management.
  • Aim for simple design, avoiding unnecessary process creation and complex dependencies.

What to Learn Next

wait function and process synchronization basics understood, the next step is to learn the following topics:
  • Interprocess Communication (IPC)Methods for exchanging data using pipes, message queues, and shared memory.
  • Asynchronous ProgrammingHow to use event-driven programming and asynchronous I/O.
  • fork function and details of process creationMemory management and process branching behavior when creating child processes.
We hope that through this article you have gained a deep understanding of the C language wait function and its related knowledge. Achieving proper process management will help you develop efficient and stable programs.