Fundamentals 25 min read

Mastering Linux Process Creation: fork, exec, wait, and Exit Explained

This comprehensive guide walks through Linux process creation with fork, explains its return values and copy‑on‑write behavior, covers normal and abnormal termination using return, exit and _exit, details waiting with wait/waitpid, and demonstrates program replacement via exec functions and a simple custom shell.

Open Source Linux
Open Source Linux
Open Source Linux
Mastering Linux Process Creation: fork, exec, wait, and Exit Explained

Process Creation

In Linux, fork creates a new child process; it returns 0 in the child, the child's PID in the parent, and –1 on failure.

Allocate new memory and kernel structures for the child.

Copy part of the parent’s data structures to the child.

Add the child to the system process list.

Return to user space and let the scheduler run the child.

After fork, parent and child share code and data until a write occurs. An example shows that "Before" prints once (parent) while "After" prints twice (parent and child).

fork Return Values

Why does fork return 0 to the child and the child's PID to the parent?

The parent needs the child's PID to manage it, while the child does not need to know the parent’s PID.

Why are there two return values?

Both processes continue executing after fork; each executes its own return statement, so both need a value.

Copy‑On‑Write

When a child is first created, parent and child share the same physical pages. Only when either process writes to a page does the kernel copy that page for the writer, preserving isolation.

Reasons for copy‑on‑write:

Process independence – modifications in the child must not affect the parent.

Efficiency – avoid copying data that may never be written.

Only code that is replaced (e.g., during exec) triggers a copy.

Typical fork Usage

A process creates a child to handle a separate task (e.g., a server spawning a handler).

The child may call an exec function to run a different program.

fork Failure Reasons

System has too many processes or insufficient memory.

User‑level process limit is reached.

Process Termination

Exit Codes

The main function returns an integer that becomes the process exit code; 0 indicates success, non‑zero indicates an error.

Commands such as ls or pwd also return 0 on success and non‑zero on failure.

return, exit, and _exit

return

from main is equivalent to calling exit. exit runs user‑registered cleanup functions, flushes buffers, closes streams, and then invokes _exit. _exit terminates the process immediately without any cleanup.

#include <stdio.h>
int main(){
    printf("Hello
");
    return 0; // same as exit(0)
}

Process Waiting

The parent must reap the child to avoid zombie processes. The kernel provides wait and waitpid, both returning a status integer that encodes exit information.

The low 16 bits of status contain:

High 8 bits – exit code (if the process exited normally).

Low 7 bits – terminating signal number (if killed by a signal).

Bit 7 – core‑dump flag.

int exitCode = (status >> 8) & 0xFF;   // exit code
int exitSignal = status & 0x7F;       // signal number

Macros simplify extraction: WIFEXITED(status) – true if the child exited normally. WEXITSTATUS(status) – retrieves the exit code.

wait

Prototype: pid_t wait(int *status); – blocks until any child terminates.

waitpid

Prototype: pid_t waitpid(pid_t pid, int *status, int options); – can wait for a specific child or use WNOHANG for non‑blocking polling.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(){
    pid_t id = fork();
    if(id==0){
        // child
        for(int i=0;i<10;i++){
            printf("I am child… PID:%d PPID:%d
", getpid(), getppid());
            sleep(1);
        }
        exit(0);
    }
    int status=0;
    pid_t ret = waitpid(id,&status,0);
    if(ret>0 && WIFEXITED(status))
        printf("child exited with code %d
", WEXITSTATUS(status));
    return 0;
}

Non‑Blocking Polling

Using waitpid with WNOHANG lets the parent perform other work while the child runs.

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(){
    pid_t id = fork();
    if(id==0){
        // child work
        sleep(3);
        exit(0);
    }
    while(1){
        int status=0;
        pid_t ret = waitpid(id,&status,WNOHANG);
        if(ret>0){
            printf("child finished, exit %d
", WEXITSTATUS(status));
            break;
        }else if(ret==0){
            printf("parent doing other work...
");
            sleep(1);
        }else{
            perror("waitpid");
            break;
        }
    }
    return 0;
}

Program Replacement (exec)

The exec family replaces the current process image with a new program. Six variants exist: execl – list of arguments, explicit path. execlp – list, searches PATH. execle – list with custom environment. execv – vector (array) of arguments, explicit path. execvp – vector, searches PATH. execve – vector with custom environment; the only true system call.

All return only on error (‑1). Example of execl:

execl("/usr/bin/ls", "ls", "-a", "-l", NULL);

Only execve directly invokes the kernel; the other five are thin wrappers.

exec family diagram
exec family diagram

Simple Shell Example

A minimal shell reads a command line, tokenises it, forks, calls execvp in the child, waits for the child, and prints the exit code.

#include <stdio.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEN 1024
#define NUM 32
int main(){
    char cmd[LEN];
    char *argv[NUM];
    while(1){
        printf("$ ");
        if(!fgets(cmd, LEN, stdin)) break;
        cmd[strlen(cmd)-1]='\0';
        argv[0]=strtok(cmd, " ");
        int i=1;
        while((argv[i]=strtok(NULL," "))) i++;
        pid_t pid=fork();
        if(pid==0){
            execvp(argv[0], argv);
            exit(1);
        }
        int status=0;
        waitpid(pid,&status,0);
        printf("exit code:%d
", WEXITSTATUS(status));
    }
    return 0;
}

Running the custom shell shows the child’s exit code, distinguishing it from the system’s default shell.

shell demo
shell demo
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

LinuxC programmingforkexecwait
Open Source Linux
Written by

Open Source Linux

Focused on sharing Linux/Unix content, covering fundamentals, system development, network programming, automation/operations, cloud computing, and related professional knowledge.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.