CoursifyCoursify

Writing a C Program with fork() to Demonstrate the Parent-Child Relationship of Processes

Writing a C Program with fork() to Demonstrate the Parent-Child Relationship of Processes

Verified Sources
May 27, 2026

In Unix-like operating systems, a new process is commonly created with the fork() system call. After fork(), execution continues in both the parent process and the child process from the next instruction, but with different return values that let each process identify its role.2 Specifically, fork() returns 0 in the child, the child’s PID in the parent, and -1 on failure.2

A well-designed demonstration program should show at least four things: the different return values of fork(), the use of getpid() and getppid() to print identities, the fact that parent and child execute concurrently, and the use of wait() or waitpid() so the parent can synchronize with the child and avoid leaving a zombie process.2 Modern systems also optimize fork() through copy-on-write rather than immediately duplicating all physical memory pages, so parent and child initially share memory pages safely until one writes to them.2

Footnotes

  1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values. 2 3

  2. Understanding fork() in Linux: How Process Creation Really Works - Explains process creation, scheduling behavior, copy-on-write, and common pitfalls. 2

  3. fork, exec, wait and exit - Explains parent-child process IDs, process trees, and the special two-return behavior of fork().

  4. wait(2) - Linux manual page - Official reference for wait() and waitpid(), including return values and status macros.

  5. Why Do We Need the fork System Call to Create New Processes? - Describes inheritance, process control, and copy-on-write memory behavior.

System Programming in C - Basics of Fork

Key Idea

A single call to fork() causes two processes to continue execution from the same point, but with different return values.2

Footnotes

  1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

  2. fork, exec, wait and exit - Explains parent-child process IDs, process trees, and the special two-return behavior of fork().

Core system calls and process relationships

The kernel records a parent-child relationship when one process creates another with fork(). Every process, except the system’s root process, has a parent, and the child can discover that relationship using getppid(). In a teaching example, printing both getpid() and getppid() makes the hierarchy visible at runtime.2

The parent typically calls waitpid() or wait() to suspend execution until the child changes state or exits. This is important because if the child terminates and the parent never collects its status, the child remains as a zombie entry in the process table until reaped.2 Thus, even a simple demonstration program should include synchronization.

A compact comparison is shown below:

FunctionPurposeTypical Return
fork()Create a child process0 in child, child PID in parent, -1 on error2
getpid()Get caller's PIDCaller’s PID
getppid()Get parent PIDParent’s PID2
wait() / waitpid()Collect child statusChild PID on success, -1 on error

Mathematically, if one process calls fork() exactly once and the call succeeds, the process count changes from 11 to 22. More generally, if each existing process forks once in a round, the count doubles to 2n2^n after nn rounds, which is why uncontrolled forking can exhaust system resources quickly.

Footnotes

  1. fork, exec, wait and exit - Explains parent-child process IDs, process trees, and the special two-return behavior of fork(). 2 3 4 5

  2. POSIX API 2 Slides - Parent and Child Processes - Teaching material covering getppid(), zombies, parent-child relationships, and waitpid() behavior. 2 3

  3. wait(2) - Linux manual page - Official reference for wait() and waitpid(), including return values and status macros. 2 3

  4. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

  5. Understanding fork() in Linux: How Process Creation Really Works - Explains process creation, scheduling behavior, copy-on-write, and common pitfalls.

Important Safety Note

Do not place fork() in an uncontrolled loop while experimenting. Repeated forking can create a large number of processes very quickly and may destabilize the system.

Footnotes

  1. Understanding fork() in Linux: How Process Creation Really Works - Explains process creation, scheduling behavior, copy-on-write, and common pitfalls.

How the demonstration program works

  1. 1
    Step 1

    Use stdio.h, stdlib.h, unistd.h, and sys/wait.h so the program can print output, call fork(), query process IDs, and wait for child termination.2

    Footnotes

    1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

    2. wait(2) - Linux manual page - Official reference for wait() and waitpid(), including return values and status macros.

  2. 2
    Step 2

    Store the return value in a variable of type pid_t. This single call causes the operating system to create a child process.2

    Footnotes

    1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

    2. fork, exec, wait and exit - Explains parent-child process IDs, process trees, and the special two-return behavior of fork().

  3. 3
    Step 3

    If the return value is negative, process creation failed and the program should report the error and terminate.

    Footnotes

    1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

  4. 4
    Step 4

    When the return value is 0, print the child PID and parent PID using getpid() and getppid(), optionally modify a local variable, then exit.3

    Footnotes

    1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

    2. Understanding fork() in Linux: How Process Creation Really Works - Explains process creation, scheduling behavior, copy-on-write, and common pitfalls.

    3. fork, exec, wait and exit - Explains parent-child process IDs, process trees, and the special two-return behavior of fork().

  5. 5
    Step 5

    When the return value is positive, print the parent PID and the child PID returned by fork(). Then call waitpid() or wait() to collect the child’s exit status.2

    Footnotes

    1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

    2. wait(2) - Linux manual page - Official reference for wait() and waitpid(), including return values and status macros.

  6. 6
    Step 6

    Use macros such as WIFEXITED(status) and WEXITSTATUS(status) to determine whether the child terminated normally and what status it returned.

    Footnotes

    1. wait(2) - Linux manual page - Official reference for wait() and waitpid(), including return values and status macros.

Complete C program

The following program demonstrates the parent-child relationship clearly. It shows identity, control flow separation, independent variable state, and parent synchronization.3

1#include <stdio.h> 2#include <stdlib.h> 3#include <unistd.h> 4#include <sys/wait.h> 5 6int main(void) { 7 pid_t pid; 8 int x = 100; // Demonstrates separate logical address spaces after fork 9 int status; 10 11 pid = fork(); 12 13 if (pid < 0) { 14 perror("fork failed"); 15 return EXIT_FAILURE; 16 } 17 else if (pid == 0) { 18 // Child process 19 x += 25; 20 printf("Child Process:\n"); 21 printf(" PID = %d\n", getpid()); 22 printf(" PPID = %d\n", getppid()); 23 printf(" x = %d\n", x); 24 printf(" fork() return value in child = %d\n", pid); 25 return 42; 26 } 27 else { 28 // Parent process 29 printf("Parent Process:\n"); 30 printf(" PID = %d\n", getpid()); 31 printf(" Child PID returned by fork() = %d\n", pid); 32 printf(" x = %d\n", x); 33 34 waitpid(pid, &status, 0); 35 36 if (WIFEXITED(status)) { 37 printf("Parent: child exited normally with status %d\n", 38 WEXITSTATUS(status)); 39 } else { 40 printf("Parent: child did not exit normally\n"); 41 } 42 } 43 44 return EXIT_SUCCESS; 45}

This example is pedagogically useful for several reasons. First, the child prints fork() return value in child = 0, while the parent prints the positive child PID returned by fork(), directly demonstrating role differentiation.2 Second, the variable x becomes 125 in the child but remains 100 in the parent, illustrating separate writable process state after the fork, even though the child begins as a copy of the parent.2 Third, the use of waitpid(pid, &status, 0) ensures the parent waits for that specific child and then decodes the termination result with standard macros.

Footnotes

  1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values. 2

  2. Understanding fork() in Linux: How Process Creation Really Works - Explains process creation, scheduling behavior, copy-on-write, and common pitfalls. 2

  3. wait(2) - Linux manual page - Official reference for wait() and waitpid(), including return values and status macros. 2

  4. fork, exec, wait and exit - Explains parent-child process IDs, process trees, and the special two-return behavior of fork().

  5. Why Do We Need the fork System Call to Create New Processes? - Describes inheritance, process control, and copy-on-write memory behavior.

1gcc -Wall -Wextra -o fork_demo fork_demo.c

Interpretation and common questions

Return values and observable roles after fork()

A conceptual comparison of outcomes seen by each execution path.

Execution lifecycle of the demonstration

The lifecycle of this program follows a standard POSIX process-creation pattern: create with fork(), differentiate behavior by return value, perform child work, and synchronize in the parent with waitpid().2 This pattern is foundational in operating systems, shells, and server designs.2

A subtle but important point is that waitpid() returns the PID of the child whose state changed, and status macros such as WIFEXITED and WEXITSTATUS decode the encoded termination information. This makes the demonstration more rigorous than simply printing messages, because it confirms not only ancestry but also process completion semantics.

Footnotes

  1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

  2. wait(2) - Linux manual page - Official reference for wait() and waitpid(), including return values and status macros. 2

  3. Understanding fork() in Linux: How Process Creation Really Works - Explains process creation, scheduling behavior, copy-on-write, and common pitfalls.

  4. fork, exec, wait and exit - Explains parent-child process IDs, process trees, and the special two-return behavior of fork().

Runtime timeline of the fork demonstration

Single process starts

T1

The program begins as one running process with one PID and one address space."

Footnotes

  1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

Child is created

T2

The call to fork() causes the kernel to create a new child process related to the caller.2"

Footnotes

  1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

  2. fork, exec, wait and exit - Explains parent-child process IDs, process trees, and the special two-return behavior of fork().

Execution splits

T3

The parent receives the child PID, while the child receives 0, allowing separate control paths.2"

Footnotes

  1. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

  2. fork, exec, wait and exit - Explains parent-child process IDs, process trees, and the special two-return behavior of fork().

Processes run concurrently

T4

Both processes print their information; scheduling determines visible output order."

Footnotes

  1. Understanding fork() in Linux: How Process Creation Really Works - Explains process creation, scheduling behavior, copy-on-write, and common pitfalls.

Child terminates

T5

The child returns an exit status, which becomes available to the parent."

Footnotes

  1. wait(2) - Linux manual page - Official reference for wait() and waitpid(), including return values and status macros.

Parent reaps child

T6

The parent calls waitpid() to collect the status and complete the demonstration cleanly.2"

Footnotes

  1. wait(2) - Linux manual page - Official reference for wait() and waitpid(), including return values and status macros.

  2. POSIX API 2 Slides - Parent and Child Processes - Teaching material covering getppid(), zombies, parent-child relationships, and waitpid() behavior.

Best Practice

Use waitpid(pid, &status, 0) instead of plain wait() when you want the parent to synchronize with a specific child and inspect its exit code precisely.

Footnotes

  1. wait(2) - Linux manual page - Official reference for wait() and waitpid(), including return values and status macros.

Educational extensions

Once the basic parent-child relationship is clear, the program can be extended in academically meaningful ways:

  1. Add sleep() in the child to make scheduling effects easier to observe.
  2. Replace the child code with exec() to demonstrate the common fork() + exec() model used by shells.2
  3. Create multiple children and call waitpid() repeatedly to study process trees and status handling.2
  4. Print memory addresses and values before and after modification to reinforce the copy-on-write model.2

These variations help students move from a single demonstration toward deeper understanding of concurrency, synchronization, exit status, and process lifecycle management.2

Footnotes

  1. Understanding fork() in Linux: How Process Creation Really Works - Explains process creation, scheduling behavior, copy-on-write, and common pitfalls. 2 3

  2. fork(2) - Linux manual page - Official Linux manual documentation for fork(), its semantics, and return values.

  3. fork, exec, wait and exit - Explains parent-child process IDs, process trees, and the special two-return behavior of fork().

  4. wait(2) - Linux manual page - Official reference for wait() and waitpid(), including return values and status macros. 2

  5. POSIX API 2 Slides - Parent and Child Processes - Teaching material covering getppid(), zombies, parent-child relationships, and waitpid() behavior.

  6. Why Do We Need the fork System Call to Create New Processes? - Describes inheritance, process control, and copy-on-write memory behavior.

Knowledge Check

Question 1 of 4
Q1Single choice

What value does fork() return in the child process?

Explore Related Topics

1

User-Level and Kernel-Level Threads in Operating Systems

User‑Level Threads (ULTs) are managed entirely in user space, while Kernel‑Level Threads (KLTs) are created, scheduled, and tracked by the OS kernel, leading to trade‑offs between low overhead and true parallelism.

  • ULT operations avoid kernel mode switches, giving O(1)O(1) creation and context‑switch cost but suffer from the “blocking system call trap” where one blocked thread stalls the whole process.
  • KLTs enable true hardware parallelism and non‑blocking concurrency; however, creation and switching incur kernel overhead O(C)O(C).
  • Mapping models: Many‑to‑One (single kernel thread, parallelism =1=1), One‑to‑One (parallelism =min(Nuser,Ncores)=\min(N_{\text{user}}, N_{\text{cores}})), Many‑to‑Many (hybrid overhead).
  • Modern runtimes (e.g., Go goroutines, Java virtual threads) use M:N scheduling to combine ULT speed with KLT scalability.
  • Performance charts show ULT operations costing far fewer CPU cycles than KLT equivalents.
2

CPU Scheduling Case Study: FCFS vs SJF for a Five-Process Workload

The case study compares FCFS and non‑preemptive SJF scheduling for five processes that all arrive at time 0, showing their Gantt charts, individual waiting times, and average waiting times.

  • FCFS order A → B → C → D → E; waiting times 0, 10, 11, 13, 14 ms; average 9.69.6 ms.
  • SJF order B → D → C → E → A (ties broken by arrival order); waiting times 0, 1, 2, 4, 9 ms; average 3.23.2 ms.
  • With simultaneous arrivals, each process’s waiting time equals the sum of burst times of all jobs scheduled before it.
  • Non‑preemptive SJF is optimal for minimizing average waiting time when burst lengths are known.
  • The priority column is irrelevant for this comparison.
3

Rust Programming

Rust is a systems programming language that provides memory‑safe, high‑performance code through its ownership, borrowing, and lifetime model, combined with modern type features and strong tooling.

  • Ownership means each value has a single owner; moving transfers ownership and dropping occurs at scope end, preventing leaks and double frees.
  • Borrowing uses immutable &T or mutable &mut T references with strict aliasing rules, ensuring data‑race‑free safe code.
  • Enums, Option<T> and Result<T,E> with pattern matching make absence and errors explicit, enhancing reliability.
  • Traits and generics enable zero‑cost abstractions and polymorphism, while Cargo manages packages, builds, tests, and documentation.
Chat with Kiro