Linux Process Waiting and Execution Replacement

Process Waiting and Reaping

Parent processes must synchronize with their child processes to reclaim system resources and retrieve termination statuses. Without proper waiting, terminated children become zombies, retaining entries in the process table.

Waiting Mechanisms

The wait function provides a basic blocking approach:

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *exit_status);

It returns the process ID of the terminated child on success, or -1 on failure. The exit_status parameter is an output pointer that captures the child's exit information; passing NULL ignores this data.

For finer control, waitpid allows specifying which child to wait for and supports non-blocking operation:

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t target_pid, int *exit_status, int flags);

Return Values:

  • On success, it returns the PID of the collected child process.
  • If the WNOHANG flag is set and the child has not yet exited, it returns 0 (non-blocking).
  • On error, it returns -1 and sets errno.

Parameters:

  • target_pid: Set to -1 to wait for any child process (equivalent to wait), or a specific PID greater than 0.
  • exit_status: Interpreted using macros rather than direct integer logic.
    • WIFEXITED(status): Evaluates to true if the child terminated normally.
    • WEXITSTATUS(status): Extracts the exit code when WIFEXITED is true.
  • flags: Setting WNOHANG makes the call return immediately if no child has exited, preventing the parent from blocking.

Interpreting the Status Bitmap

The status integer returned by wait and waitpid acts as a bitmap filled by the operating system. If not NULL, it encodes both the exit code and the termination signal. Standard macros abstract the bitwise operations, though manual extraction is also possible.

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    pid_t child_id = fork();
    if (child_id < 0) {
        perror("Failed to fork");
        return EXIT_FAILURE;
    }

    if (child_id == 0) {
        sleep(3);
        return 42;
    } else {
        int child_status;
        pid_t waited_id = wait(&child_status);

        if (waited_id > 0) {
            if (WIFEXITED(child_status)) {
                printf("Child terminated normally, exit code: %d\n", WEXITSTATUS(child_status));
            } else {
                printf("Child terminated abnormally, signal: %d\n", WTERMSIG(child_status));
            }
        }
    }
    return EXIT_SUCCESS;
}

Process Execution Replacement

Replacement Principle

After a fork, the child process inherits the parent's code and data. To execute a different program, the child invokes an exec function. This completely overwrites the process's user-space memory (code, data, heap, and stack) with the new program, starting execution at its entry point. Crucially, exec does not create a new process; the process ID remains unchanged.

The Exec Family of Functions

Successful calls to exec functions do not return; the calling program is entirely replaced. A return value of -1 indicates failure, meaning the only observable return value is an error.

#include <unistd.h>
int execl(const char *filepath, const char *arg, ...);
int execlp(const char *filename, const char *arg, ...);
int execle(const char *filepath, const char *arg, ..., char *const envp[]);
int execv(const char *filepath, char *const argv[]);
int execvp(const char *filename, char *const argv[]);
int execve(const char *filepath, char *const argv[], char *const envp[]);

The naming conventions dictate their behavior:

  • l (list): Arguments are passed as a list.
  • v (vector): Arguments are passed as an array.
  • p (path): Uses the PATH environment variable to locate the executable, so a full file path is not strictly necessary.
  • e (environment): Accepts a custom array of environment variables for the new process.

Only execve is a true system call; the others are library wrappers around it.

#include <unistd.h>

int main() {
    char *const arg_vector[] = {"ls", "-l", "/tmp", NULL};
    char *const custom_env[] = {"PATH=/usr/bin:/bin", "TERM=xterm", NULL};

    // List arguments, full path required
    execl("/bin/ls", "ls", "-l", "/tmp", NULL);

    // List arguments, uses PATH variable
    execlp("ls", "ls", "-l", "/tmp", NULL);

    // List arguments, custom environment
    execle("/bin/ls", "ls", "-l", "/tmp", NULL, custom_env);

    // Vector arguments, full path required
    execv("/bin/ls", arg_vector);

    // Vector arguments, uses PATH variable
    execvp("ls", arg_vector);

    // Vector arguments, custom environment (system call basis)
    execve("/bin/ls", arg_vector, custom_env);

    return 0;
}

Tags: Linux Process Management wait exec c programming

Posted on Fri, 08 May 2026 20:18:15 +0000 by brodywx