4.2.5Operating Systems

Process creation — fork(), exec(), wait(), exit()

2,412 words11 min readdifficulty · medium

WHY these four exist


fork() — the two-return mystery

What is shared vs copied?

Resource After fork()
Memory (code/data/heap/stack) Logically copied (in practice Copy-on-Write)
Open file descriptors Shared (same underlying file table entry, shared offset)
PID Different (child gets a fresh one)
Pending signals Not inherited (cleared)
CPU registers / program counter Copied → both resume at the same instruction

exec() — replace, don't return

execlp("ls", "ls", "-l", NULL);   // try to become 'ls'
perror("exec failed");            // ONLY reached if exec() failed
exit(1);

exit() and wait() — the handshake

Figure — Process creation — fork(), exec(), wait(), exit()

Derivation from scratch: the canonical pattern


Common mistakes (Steel-manned)


Flashcards

What three categories of value can fork() return and to whom?
0 to the child, child's PID (>0) to the parent, -1 on failure.
Why does the child receive 0 from fork() rather than its own PID?
The child can already get its PID via getpid(); 0 is a free unambiguous "you are the child" flag, and the parent needs the actual PID more.
What is Copy-on-Write and why does it make fork() cheap?
Pages are shared read-only and copied only when written, so you only pay to duplicate memory you actually modify.
Why does a successful exec() never return?
It overwrites the calling process's memory image, so the instruction after exec() no longer exists; control passes to the new program's main.
After fork(), are open file descriptors shared or copied?
Shared — both processes refer to the same open file table entry (and share the file offset).
What is a zombie process?
A child that has terminated but whose exit status has not yet been collected by its parent via wait().
What happens to orphaned children when their parent dies?
They are re-parented to init (PID 1), which reaps them.
How many processes exist after 3 consecutive fork() calls?
23=82^3 = 8.
What does WEXITSTATUS(status) give you?
The low 8 bits of the value the child passed to exit().
Why must a shell fork() before exec()?
So the parent (shell) survives; exec() would otherwise replace the shell itself with the new program.
What return value pattern means fork failed and how do you detect it?
fork() returns -1 and no child is created; check if (pid < 0).

Recall Feynman: explain it to a 12-year-old

Imagine you have a robot that already knows how to do chores. Making a brand-new robot from scratch is a lot of work, so instead you press a "copy" button (fork) and now there are two identical robots. One robot is the "boss" and one is the "helper" — they can tell who they are because the copy button hands the boss a sticky note with the helper's name, and hands the helper a blank note (the 0). The helper can then read a new instruction manual and become a totally different robot (exec) — same body, new brain. When the helper finishes its job it raises a little flag saying "done, here's my score" (exit). The boss watches for that flag (wait), reads the score, and only then lets the helper be thrown away — otherwise the finished helper just stands there frozen as a "zombie."


Connections

  • Processes vs Threads — threads share memory by default; fork gives separate (COW) memory.
  • Process Control Block (PCB) — what gets copied/created during fork().
  • Zombie and Orphan Processes — direct consequence of missing wait().
  • Inter-Process Communication — shared FDs after fork enable pipes.
  • Copy-on-Write Memory — the optimization behind cheap fork().
  • The Shell — fork+exec+wait is the heart of how a shell runs commands.
  • Signals (SIGCHLD) — alternative to blocking wait() for reaping children.

Concept Map

calls fork

returns 0

returns child PID

returns -1

Copy-on-Write

shared

calls exec

never returns on success

calls exit status

calls wait

status collected by

prevents

Parent process

fork clone

Child process

Failure, no child

Memory copied

File descriptors

exec replaces image

New program runs

exit terminates

wait suspends parent

Zombie processes

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, naya process banane ke liye OS scratch se kuch nahi banata — wo ek "copy" maar deta hai. fork() current process ki ek almost-exact copy bana deta hai. Mazedaar baat: fork() do baar return karta hai — parent ko child ki PID milti hai, aur child ko 0 milta hai, aur fail hone par -1. Child ko 0 isliye milta hai kyunki wo apni PID khud getpid() se nikaal sakta hai; parent ke paas child ki PID jaanne ka aur koi raasta nahi, isliye usko PID di jaati hai. Memory turant copy nahi hoti — Copy-on-Write se sirf wahi page copy hota hai jahan koi likhta hai, isliye fork() fast rehta hai.

exec() ka kaam hai current process ki memory ko naye program se replace karna — same PID, naya code. Isliye success par exec() kabhi return nahi karta (purana code to mit gaya!). exec() ke baad jo line hai wo sirf tab chalti hai jab exec fail ho. Isiliye shell hamesha pehle fork() karta hai, phir child me exec() — taaki parent (shell) zinda rahe.

Jab child khatam hota hai to exit(status) call karta hai, par OS uska chhota record (PID + status) rakh leta hai jab tak parent use padh na le. Yeh padhne ka kaam wait()/waitpid() karta hai. Agar parent wait() nahi karta, to child zombie ban kar latka rehta hai. Aur yaad rakho: exit code ke sirf low 8 bits parent tak pahunchte hain, isliye exit() ki value 00 se 255255 me rakho. Pattern simple hai — Fork karo, Exec karo, Wait karo, Exit karo.

Go deeper — visual, from zero

Test yourself — Operating Systems

Connections