Threads and Process Creation in Operating Systems: fork(), Thread Models and Worked Examples

Build a reliable model of processes and threads, then trace conditional fork() calls across six named process paths without losing track of return values.

KnowledgeGate Team

Exam prep & CS education

Updated 13 Sep 20266 min read

A program is not a process, and a process is not a thread. That distinction becomes especially important when three lines containing fork() produce more processes than there are calls. Resource ownership, process creation and thread creation follow one consistent model in the named-PID trace. POSIX-style fork(), exec() and wait() form the exam model; the core resource-sharing ideas also apply to systems that expose different calls.

For the broader ownership boundary, copy-on-write, context-switch cost and symbolic six-process tree, use Threads and Process Creation in OS: fork() Worked Examples. A separate PID ledger follows exactly which return values PIDs 4000 to 4005 hold after a conditional fork() trace, and why exec(), waitpid() and exit status 7 do not add another process. The thread-model contrast exposes the shared-state boundary between creating a thread and creating a process.

Related reading: thread and process MCQs and process state questions.

1. Process and Thread Boundaries for the PID Ledger

A program is a passive set of instructions stored on disk. A process is a running instance of a program, identified by a PID and given a virtual address space plus resources managed by the operating system. A thread is an execution path inside that process.

The ownership rule is simple: processes own resource containers, while threads execute within them.

Suppose process PID 4000 contains code, a heap variable counter = 12 and open file descriptor 3. Its threads T1 and T2 share all three. T1 still needs its own program counter, registers and stack, and T2 needs a separate set of the same execution state. Switching from T1 to T2 therefore does not create another independent address space.

Place this topic beside DBMS and computer networks in the CS Fundamentals for Exams & Placements subject map.

Process PID 4000 with threads T1 and T2 sharing code, heap and file descriptor 3 while each keeps its own stack and registers.

2. fork() Return Values Build the Process Paths

Apply one rule before doing any multiplication: each successful fork() creates one child from every process path that reaches that call.

If PID 4000 executes child = fork() and the operating system assigns PID 4001 to the child, both processes continue at the next statement. In parent PID 4000, child = 4001. In child PID 4001, child = 0. These return values let later conditions select parent or child paths.

The child has a separate process identity and logically inherited state. An implementation may use copy-on-write to postpone physical copying, but that optimisation does not change the number of processes. Scheduling order is not guaranteed, and the illustrative consecutive PIDs used here should not be treated as a real-world promise.

For safe counting, list the paths currently alive, evaluate the next fork() only on paths that reach it, and add one child for each successful call. Keep two answers separate: total processes, including the original, and newly created children.

3. Fully Worked Example: Trace Conditional fork() Calls

Assume every call succeeds, the illustrative PIDs are assigned as shown, and no output was buffered before the first fork.

a = fork();
if (a == 0) {
b = fork();
}
c = fork();
printf("X");

Start with P0, PID 4000.

  1. P0 executes a = fork() and creates C1, PID 4001. There are now 2 processes. P0 has a = 4001; C1 has a = 0.

  2. Only C1 satisfies a == 0. Its conditional call creates C2, PID 4002. The running set is now P0, C1 and C2, so the count is 3.

  3. All three paths reach c = fork(). P0 creates C3, PID 4003. C1 creates C4, PID 4004. C2 creates C5, PID 4005. Three existing paths plus three new children give 6 total processes.

The final return values provide a second way to audit the paths:

Final path

PID

Created by final fork()?

Value of c

Output

P0

4000

No

4003

X once

C1

4001

No

4004

X once

C2

4002

No

4005

X once

C3

4003

Yes, by P0

0

X once

C4

4004

Yes, by C1

0

X once

C5

4005

Yes, by C2

0

X once

The answer is 6 total processes, 5 newly created children and 6 executions of printf(). The six X characters may appear in any order. A condition on a return value selects paths; an unconditional call doubles only the paths that actually reach it.

Process tree for the worked example growing from 1 to 2 to 3 to 6 processes, with P0 at PID 4000 and children C1 to C5.

4. exec(), wait(), exit() and the Parent-Child Lifecycle

Creation and replacement are different operations. Suppose P0, PID 4000, creates child PID 4001. The child then calls exec("/bin/worker"). Its PID remains 4001, but its old program image is replaced by the worker program. exec() therefore does not add a seventh process to the worked example.

The parent can call waitpid(4001, ...) and block until that child terminates. If PID 4001 exits with illustrative status 7, the parent collects, or reaps, that status and continues.

Two lifecycle terms are worth keeping precise. A zombie is a terminated child whose exit status has not yet been collected. An orphan is a live child whose parent exits first. For exam questions, focus on whether the child is already terminated and whether its parent has collected its status, without assuming platform-specific re-parenting behaviour.

5. Thread Models as a Process-Creation Contrast

Creating T2 inside PID 4000 adds an execution path, not another process or private heap. Threads in one process share code, data or heap, and open files. Each thread keeps a private program counter, register set and stack. Separate processes instead have distinct virtual address spaces and need explicit inter-process communication when they want to exchange data.

Thread models describe how user threads map to kernel scheduling entities:

  • Many-to-one: several user threads map to one kernel entity. Management can be simpler, but blocking is exposed across the group and parallel kernel scheduling is limited.

  • One-to-one: each user thread maps to a kernel thread. This supports direct kernel scheduling and concurrency, with more kernel overhead.

  • Many-to-many: several user threads are multiplexed over a set of kernel threads. The number of user paths can differ from the number of kernel entities, with more complex management.

Shared memory also creates correctness risks. If T1 and T2 both read counter = 12, each computes 13, and each writes 13, the final value can be 13 instead of the intended 14. That lost update follows directly from sharing the heap.

6. How GATE-Style Questions and Interviews Test the Topic

Most questions reduce to five checks:

  1. Count total processes after conditional calls.

  2. Identify the branch that sees a fork() return value of 0.

  3. Distinguish creation by fork() from image replacement by exec().

  4. Classify thread resources as shared or private.

  5. Match many-to-one, one-to-one and many-to-many models to their definitions.

Rapid check 1: if (fork() == 0) printf("C"); else printf("P"); finishes with 2 processes. One prints C and one prints P, in unspecified order.

Rapid check 2: two threads in one process share the heap and open files, while their stacks and registers are private.

7. Short Version and the Next Study Step

fork() creates another process path. Return value 0 identifies the child branch. exec() replaces a process image without creating a process. Threads share process resources but retain separate execution state.

Now redraw the 1 -> 2 -> 3 -> 6 tree from memory, write all six final PIDs, and answer the two rapid checks without notes. For structured GATE preparation, continue with GATE Guidance by Sanchit Sir. If technical interviews are the immediate goal, use CS Fundamentals for Placements by Sanchit Sir to revise the core subjects in that context.