A process, a thread, and a child created by fork() can all execute instructions, but they do not own the same state. That ownership boundary is where code-tracing answers go wrong. Three fork() calls, one of them inside a conditional, leave six processes running and not the eight that 2^3 suggests, and two threads that both read counter = 10 can leave it holding 7 or 15. Work each ownership and control-flow step rather than trusting a shortcut.
Process versus thread: draw the ownership boundary
A process is a running instance of a program. It has a virtual address space, a process identity, OS-managed resources, and at least one thread of execution. A thread is one execution stream inside that process.
State or resource | Thread-private or shared? |
|---|---|
Program counter and register set | Private to each thread |
Stack and scheduling state | Private to each thread |
Code and global or static data | Shared by threads in one process |
Heap | Shared by threads in one process |
Process-level open-file resources | Shared by threads in one process |
Separate processes have logically separate address spaces. The OS may temporarily map some of their virtual pages to the same physical pages, but that implementation detail does not turn their variables into shared thread state. Each process also carries lifecycle state, such as ready, running or blocked, and a process control block holding its saved registers and the pointer to its page table. That control block, and not any individual thread, is what the kernel builds afresh for each new child process.
Unix/POSIX-style process creation with fork(), exec(), and wait()
In the Unix/POSIX-style model, fork() creates a child process. On success, the call returns twice: the child receives 0, while the parent receives a positive identifier for the child. On failure, the parent receives -1 and no child is created. Unless a question puts failure in scope, assume every call succeeds, because a failed call adds nothing to the count.
The exec() family does not create another process. A successful exec() replaces the calling process image with a new program. The wait() family lets a parent collect a child's termination status, or reap the child.
Take parent P0 with 12 writable pages numbered 0 to 11, each 4 KiB. The logical region is 12 x 4 KiB = 48 KiB. After fork(), P0 and child P1 have separate logical address spaces, while all 12 pages can remain physically shared through copy-on-write.
P1 writes pages 2, 5, and 9. Exactly three child copies are then needed: 3 x 4 KiB = 12 KiB additional storage, with nine pages still shared. If x = 10 is on page 2, P1 can set its private x to 15 while P0 still reads 10. P0 can later set its own value to 7.

Worked fork tree: why the answer is six processes, not eight
Assume execution begins with one process, P0, and every fork() succeeds. The labels below are symbolic, not real PID values.
fork(); /* F1 */
if (fork() == 0) { /* F2 */
fork(); /* F3: only F2 children enter */
}Trace the control flow line by line:
F1 makes P1. The running set is now P0 and P1, so the count changes from 1 to 2.
Both processes execute F2. P0 makes P2, and P1 makes P3. The set is P0, P1, P2, P3, so the count changes from 2 to 4.
Only P2 and P3 received
0from F2. They enter the block, where P2 makes P4 and P3 makes P5. The count changes from 4 to 6.
The final set is P0, P1, P2, P3, P4, P5. The exact tree edges are P0 to P1, P0 to P2, P1 to P3, P2 to P4, and P3 to P5. A shortcut such as 2^3 = 8 is wrong because F3 is conditional. That shortcut works only when every process alive at each stage executes all three successful fork calls unconditionally, which is the straight-line case worked in fork() Questions in GATE.

Thread creation, mapping models, and shared state
Creating a thread adds another execution context inside an existing process. The mapping between user threads and kernel threads depends on the system. Many-to-one maps several user threads to one kernel thread. One-to-one gives each user thread a kernel thread. Many-to-many multiplexes user threads across a set of kernel threads. No one model is universal.
Consider one process with heap variable counter = 10. T1 has stack-local id = 1 and performs counter += 5. T2 has stack-local id = 2 and performs counter -= 3. If T1 completes before T2, the arithmetic is 10 + 5 = 15, then 15 - 3 = 12.
Now let both threads read 10 before either writes. T1 computes 15, and T2 computes 7. If T1 writes first and T2 writes last, the final value is 7. Reverse the writes and it is 15. Separate stacks protect the two id variables, not the shared heap counter. Process Synchronization and Semaphores is the next lesson for protecting such state.
Context switching and creation cost: what actually changes
A process switch may change the active address-space mapping as well as registers and kernel bookkeeping. A thread switch within one process normally keeps that address space and changes the selected thread's registers, program counter, stack pointer, scheduling state, and related bookkeeping.
This is why creating threads and switching between threads in one process often involves less work than creating or switching separate processes. It is not a universal timing rule. The actual cost depends on the operating system, runtime, hardware, cache state, and workload. Choose processes when stronger isolation and separate address spaces matter. Choose threads when execution streams need convenient access to shared process resources, while accepting the need for careful synchronisation.
Process and thread traps that cause wrong answers
Applying
2^nto conditional forks: trace who reaches each call and count only the children created.Counting
exec()as creation: it replaces the caller's image; it does not add a process.Ignoring failure: handle or exclude the
fork()return value-1before counting.Assuming output order: without
wait()or other synchronisation, neither parent-first nor child-first is guaranteed.Sharing everything between threads: each thread keeps its stack, registers, program counter, and scheduling state.
A zombie is a terminated child whose status has not been collected. An orphan is still running when its parent exits. Later responsibility for an orphan varies by system, so there is no universal adopter to memorise.
Buffered output is a separate trap. Data buffered before fork() may exist in both resulting user-space buffers. If both processes flush their copy, output may appear twice. The six-process example contains no output, so buffering does not affect its count.
GATE-style questions and interviews on threads and process creation
Practice this topic systematically in four forms:
Count processes and reconstruct the parent-child edges.
Trace parent and child branches from
fork()return values.Classify state as thread-private or process-shared.
Compare mapping models, isolation, and communication.
After redrawing the six-process tree, use the Threads & Process Creation MCQ practice, which sits inside a topic bank of over 120 practice questions on fork(), threading models and process creation.
For an interview, explain fork() versus exec(), copy-on-write, process versus thread context switches, and the counter race aloud. CS Fundamentals for Placements by Sanchit Sir is a structured follow-on for broader OS interview revision.
Short version and next step
Processes isolate address spaces.
Threads share process resources but keep their execution state.
fork()creates,exec()replaces, andwait()reaps.Conditional control flow decides the process count.
Shared thread data needs synchronisation.
For subject-wise study, continue through the GATE course and test-series hub. For interview-focused revision, use the placement course linked above. Before opening the MCQ set, redraw the P0 to P5 tree and re-derive six without the 2^n shortcut.




