Home/Learn/Operating Systems/Process Creation: fork, exec, wait

Process Creation: fork, exec, wait

Intermediate
Processes & Threads

Unix process creation uses fork() to duplicate the parent, exec() to replace the process image with a new program, and wait() for the parent to collect the child's exit status.

Overview

In Unix/Linux, every process (except init/systemd) is created by another process. fork() creates a child that is an almost-exact copy of the parent, using copy-on-write (COW) so memory pages are only physically copied when written. exec() replaces the calling process's code and data with a new program — the PID stays the same. wait() (or waitpid()) blocks the parent until the child exits, collecting its exit status and preventing zombie processes. A zombie is a process that has exited but whose PCB remains because the parent hasn't called wait(). An orphan is a process whose parent exited first — adopted by init. Java exposes process creation through ProcessBuilder and Process APIs.

fork, exec, wait — How the Shell Works

When you type a command in a shell, the shell calls fork() to clone itself, then the child calls exec() to replace itself with the command binary. The shell (parent) calls wait() to get the exit code. Copy-on-write means fork() is cheap — pages are shared until either process writes to them, only then is a physical copy made.

C pseudocode — fork/exec/wait (Unix model)
// Unix process creation (C pseudocode — conceptual)
pid_t pid = fork();          // duplicate current process
if (pid == 0) {
    // Child process: replace image with "ls -la"
    execl("/bin/ls", "ls", "-la", NULL);
    // exec never returns on success
    perror("exec failed");
    exit(1);
} else if (pid > 0) {
    // Parent process: wait for child to finish
    int status;
    waitpid(pid, &status, 0);   // blocks until child exits
    if (WIFEXITED(status)) {
        printf("Child exited with: %d\n", WEXITSTATUS(status));
    }
} else {
    perror("fork failed");
}
// A zombie occurs if parent exits without calling wait()
// An orphan occurs if parent exits before child — init adopts it

Java ProcessBuilder: fork+exec in the JVM

Java's ProcessBuilder wraps the OS fork+exec model. On Unix it internally calls posix_spawn (or fork/exec). You can redirect stdin/stdout/stderr, set environment variables, and use waitFor() to block until the child exits — equivalent to wait() in C.

Java — ProcessBuilder wraps fork/exec/wait
// Java ProcessBuilder — spawn a child process
ProcessBuilder pb = new ProcessBuilder("ls", "-la", "/tmp");
pb.redirectErrorStream(true);                  // merge stderr into stdout
pb.directory(new File(System.getProperty("user.home")));

Process child = pb.start();

// Read child's stdout
try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(child.getInputStream()))) {
    reader.lines().forEach(System.out::println);
}

int exitCode = child.waitFor();                // blocks — equivalent to wait()
System.out.println("Exit code: " + exitCode);

// Accessing child's PID (Java 9+)
System.out.println("Child PID: " + child.pid());

// Detecting zombie-equivalent: child finished, exitCode collected
// In Java, the JVM handles zombie cleanup automatically via waitFor()

Zombie and Orphan Processes

A zombie process has finished executing but its PCB remains in the process table because the parent hasn't called wait(). It consumes no CPU or memory except a PCB slot. Too many zombies can exhaust PID space. An orphan process is a running process whose parent has exited — in Unix, init (PID 1) automatically adopts orphans and calls wait() for them.

Java — handling zombie/orphan with waitFor and shutdown hook
// Java: detecting and handling child process lifecycle
ProcessBuilder pb = new ProcessBuilder("sleep", "5");
Process child = pb.start();

// If parent exits here without calling waitFor(), child becomes orphan
// Java's Process is NOT automatically waited — always call waitFor() or destroy()

// Timeout-based wait (Java 9+)
boolean finished = child.waitFor(10, TimeUnit.SECONDS);
if (!finished) {
    child.destroyForcibly();   // sends SIGKILL
    System.out.println("Child killed after timeout");
} else {
    System.out.println("Child exited: " + child.exitValue());
}

// Register shutdown hook to clean up child processes
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    if (child.isAlive()) child.destroyForcibly();
}));

Key Points to Remember

  • 1fork() creates a child process as a copy of the parent using copy-on-write pages.
  • 2exec() replaces the process image with a new program; the PID is preserved.
  • 3wait()/waitpid() collects the child's exit status and removes its PCB from the process table.
  • 4A zombie process has exited but its PCB remains because the parent hasn't called wait().
  • 5An orphan process's parent has exited; init (PID 1) adopts it and calls wait().
  • 6Java ProcessBuilder internally uses posix_spawn/fork+exec; waitFor() is equivalent to wait().

Interview Questions

Sign in to ask Aria
1

What is copy-on-write and how does it make fork() efficient?

MediumGoogle
2

What is a zombie process and how do you prevent it?

EasyAmazon
3

What is the difference between a zombie and an orphan process?

MediumMicrosoft
4

How does Java's ProcessBuilder map to Unix fork/exec/wait?

MediumAtlassian

Ask Aria about Process Creation: fork, exec, wait

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…