C wait function: usage, zombie fix, and waitpid differences

目次

1. Introduction

C is widely used for developing system programs and embedded systems, and process management is one of the important topics among them. This article explains the wait function in C. The wait function is a system call used to achieve synchronization between processes, especially useful for waiting for child processes to terminate. Through this article, you can learn broadly about the wait function from basic usage to advanced techniques, and related topics (e.g., the waitpid function and zombie process mitigation).

2. What is the C language wait function?

wait function overview

wait function is one of the system calls used on UNIX-like systems, employed by a parent process to wait for a child process to exit. This enables the parent process to obtain the child’s exit status.

Basic operation

wait function is used as follows.
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

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!
");
    }
    return 0;
}
In the above code, a child process is created with the fork function, and the parent process uses the wait function to wait for the child’s termination.

Return value and arguments

  • Return value If the child process has terminated, it returns its process ID. If an error occurs, it returns -1.
  • Argument By passing int* status as an argument, you can obtain the child process’s exit status.
年収訴求

3. Basic usage of the wait function

Simple code example

wait function basic usage is shown below.
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

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

Explanation of the sample code

  1. Waits for the child process to terminate using wait(&status).
  2. Uses WIFEXITED(status) to check whether the child process exited normally.
  3. Retrieves the child process’s exit code with WEXITSTATUS(status).
Thus, by using the wait function, the parent process can accurately determine the child process’s termination status.

4. Relationship between Zombie Processes and wait Function

What Is a Zombie Process

A zombie process occurs when a child process has terminated but the parent process fails to properly reap its exit status. In this state, the child process itself has ended, but its process information remains in the system. If many zombie processes accumulate in the system, they can fill up the process table and prevent other processes from running correctly.

Example of Zombie Process Creation

Below is a simple example that creates a zombie process.
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

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...
");
        _exit(0);
    }
    return 0;
}
In this example, because the parent does not reap the child’s exit status, the child becomes a zombie after it exits.

wait Function Prevents Zombie Processes

wait function can be used so that the parent properly reaps the child’s exit status and prevents zombie processes. Below is a corrected version.
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    pid_t pid = fork();
    if (pid > 0) {
        // Parent process
        wait(NULL); // Prevent zombie processes by waiting for the child to exit
        printf("Parent process: Child process has been reaped.
");
    } else if (pid == 0) {
        // Child process
        printf("Child process exiting...
");
        _exit(0);
    }
    return 0;
}
In this program, using wait(NULL) makes the parent wait for the child’s termination, preventing zombie processes.

Key Points for Handling Zombie Processes

  1. Always use wait or waitpid Properly reaping a child’s exit status prevents zombie processes.
  2. Using a signal handler You can catch the SIGCHLD signal and automatically reap child processes when they exit.
Below is an example.
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>

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

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

    pid_t pid = fork();
    if (pid == 0) {
        // Child process
        printf("Child process exiting...
");
        _exit(0);
    } else if (pid > 0) {
        // Parent process
        printf("Parent process doing other work...
");
        sleep(10); // SIGCHLD handler may fire while doing other work
    }
    return 0;
}
With this approach, even if the parent never explicitly calls wait, terminated child processes are automatically reaped.

5. Differences from the waitpid Function

What is the waitpid function?

waitpid is a system call that, like the wait function, waits for a child process to exit, but it enables more flexible process management. By using waitpid, you can specify a particular child process or wait in non‑blocking mode.

Basic Usage and Syntax

waitpid function syntax is as follows.
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

pid_t waitpid(pid_t pid, int *status, int options);
  • pid Specifies the ID of the child process to wait for. You can control its behavior using the following special values.
  • pid > 0: Wait for the specified process ID.
  • pid == 0: Wait for any child process in the same process group as the parent.
  • pid < -1: Wait for all processes in the specified process group.
  • pid == -1: Wait for any child process (same as wait function).
  • status Pointer that stores the child process’s exit status.
  • options Specifies options that modify the behavior. The main values are:
  • WNOHANG: Non-blocking mode. Returns immediately if no child has exited.
  • WUNTRACED: Also includes child processes that are stopped.
  • Return value
  • On success: PID of the terminated child process.
  • If no child process exists: 0 (when WNOHANG is specified).
  • On error: -1.

waitpid Function Example

The following is an example that uses the waitpid function to wait for a specific child process.
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

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

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

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

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

    return 0;
}

Main Differences Between wait and waitpid

Itemwait Functionwaitpid Function
Target to wait forAny child processCan specify a particular child process
BlockingAlways blockingNon‑blocking possible
Option specificationNot availableWNOHANG, WUNTRACED, etc.
FlexibilityLimitedHigh

When to Choose waitpid

  • When you need to manage specific child processes It is suitable when a program creates multiple child processes and you want to control each one individually.
  • When you want to perform asynchronous processing The WNOHANG option is useful when you need to check a process’s exit status without blocking other operations.

Selection Guide for wait and waitpid

  1. Simple programs can use the wait function. If you only need to wait for any child process, flexibility is not required.
  2. Complex process management should use the waitpid function. Especially when asynchronous processing or control of specific processes is needed, using waitpid allows efficient management.

6. Synchronization in a Multithreaded Environment

Differences Between Process Synchronization and Thread Synchronization

C language treats processes and threads as distinct management units. Process synchronization (e.g., wait and waitpid functions) controls termination states and resource sharing among multiple processes. In contrast, thread synchronization manages resources and ordering between threads within the same process.

Synchronization in a Multithreaded Environment

When synchronizing between threads, condition variables and mutexes are commonly used. Here we explain how to synchronize using condition variables from the pthread library.

Basics of Synchronization Using Condition Variables

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

Basic Functions of Condition Variables

  • pthread_cond_wait Waits until the condition is satisfied. The mutex is released while waiting.
  • pthread_cond_signal Wakes up one waiting thread.
  • pthread_cond_broadcast Wakes up all waiting threads.

Example of Synchronization Using Condition Variables

Below is a simple example that synchronizes multiple threads using a condition variable.
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// Initialize mutex and condition variable
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...
");
    shared_data = 1;

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

    return NULL;
}

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

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

    printf("Consumer: consumed data!
");
    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 so that the consumer waits 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 Sample Code

  1. Use of Mutex pthread_mutex_lock and pthread_mutex_unlock are used 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 with pthread_cond_signal.
  1. Cooperative Operation Between Threads The producer generates data and the consumer receives it, implementing a simple flow.

Points to Consider in Thread Synchronization

  • Preventing Deadlocks You need to be careful about the order of locking and unlocking mutexes.
  • Avoiding Race Conditions When threads attempt to modify a condition simultaneously, you must correctly combine condition variables with mutexes.
  • Considering Scalability To synchronize efficiently among multiple threads, it is important to minimize unnecessary waiting and locking.

Caution When Using the wait Function in a Multithreaded Environment

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

7. Troubleshooting and Best Practices

Common Errors and Solutions

When using wait or waitpid in C, several typical errors can occur. This section explains 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
  • Use wait or waitpid properly in the parent process.
  • Set up a signal handler to automatically reap terminations.

3. Unstable behavior due to race conditions

Cause
  • Multiple parent processes attempt to wait for the same child process.
  • The child’s termination status is not correctly reaped.
Solution
  • If you need to specify a 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 (controlling specific child processes or asynchronous handling) is needed, use waitpid.

2. Leveraging signal handlers

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

3. Thorough error handling

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

4. Implementing asynchronous processing

When implementing asynchronous processing, the following design is recommended.
  • Do not block the main processing; periodically call waitpid with WNOHANG.
  • Poll the child’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.
", pid);
}

5. Managing the number of processes

Programs that spawn many child processes need to control the number of processes.
  • Limit the number of child processes created simultaneously.
  • Adopt a design that does not spawn 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: wait functionを使用しなくても、シグナルハンドラーを活用することでゾンビプロセスを防ぐことができます。親プロセスがSIGCHLDシグナルを捕捉することで、子プロセスの終了状態を自動的に回収します。 以下はシグナルハンドラーを使った例です。
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>

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 work
    sleep(5);
    printf("Parent process completed.\n");
    return 0;
}

Q2: In what situations should you use waitpid?

A: waitpidは以下のような場合に適しています。
  • 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 stopped child processes (use the WUNTRACED option).

Q3: What are the reasons why the wait function does not return a value?

A: The following reasons can cause the wait function to not return a value (return -1).
  1. 子プロセスが存在しない。
  • The process may have already terminated, or fork may have failed.
  1. システムコールが中断された(EINTRエラー)。
  • If the interruption is caused 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 multithreaded programs?

A: When using wait in multithreaded programs, keep the following points in mind.
  • wait operates at the process level, so use condition variables or mutexes for thread-level synchronization.
  • Managing child processes is generally safest when limited 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 function offers high flexibility and can control specific processes.
  • Signal handler processes child termination asynchronously.
  • Event-driven programming 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, follow these steps.
  1. Use the WUNTRACED option to check for stopped child processes.
  2. If needed, terminate the child process with the kill system call.

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

A: The status obtained by the wait function contains the following information.
  • Normal exit: WIFEXITED(status) returns true, and the exit code can be retrieved 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, a common approach is 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 provided a comprehensive overview of the C language wait function, covering everything from basic usage to advanced examples and related topics (such as the waitpid function and zombie process mitigation). Below are the key points summarized.

Fundamentals of the wait Function

  • wait is a system call that allows a parent process to wait for a child process to terminate and to reap its termination status.
  • Using wait(NULL) lets you wait for a child process without checking its exit status.

Advanced Usage and Related Topics

  • Zombie Process Mitigation If a child’s termination status is not reaped, a zombie process will appear. Proper use of wait and waitpid prevents this.
  • waitpid Function It is very useful when you need to wait for a specific child process or perform asynchronous handling.
  • Signal Handler By using the SIGCHLD signal, a parent can automatically reap termination statuses without explicitly calling wait.

Considerations in Multithreaded Environments

  • The wait function synchronizes between processes, but for thread-level synchronization you should use condition variables or mutexes.
  • It is important to design your program so that multiple threads do not call wait simultaneously.

Troubleshooting and Best Practices

  • Always check the return values of wait and waitpid and handle errors appropriately.
  • When asynchronous handling is needed, using the WNOHANG option enables efficient process management.
  • Aim for a simple design, avoiding unnecessary process creation and complex dependencies.

What to Learn Next

Having mastered the basics of the wait function and process synchronization, the next step is to study the following topics:
  • Interprocess Communication (IPC) Methods for exchanging data using pipes, message queues, and shared memory.
  • Asynchronous Programming How to leverage event-driven programming and asynchronous I/O.
  • fork Function and Process Creation Details Memory management and behavior of process branching when creating child processes.
We hope this article has helped you gain a deep understanding of the C wait function and its related concepts. By implementing proper process management, you can develop efficient and stable programs.
侍エンジニア塾