Master the fundamental concepts of process management through this focused micro-challenge.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksUnix process creation rests on fork(): duplicate the parent, give the child its own PID, and let both resume at the same instruction. The return value is the only immediate clue about which side you are on. Parent gets the child's PID; child gets zero. That split is simple on paper and subtle in real kernels that use copy-on-write pages and careful file-descriptor tables.
A simulator keeps a process table instead of hardware page tables:
For example, if process 1000 calls fork() and receives PID 1001 in the parent branch, the child branch must see return value 0 and getpid() == 1001. Mix those up and your pstree-style output lies.
fork()'s copy-on-write duplication is why Apache's prefork MPM and PostgreSQL's process-per-connection model can spin up workers cheaply, and why a naive fork() in a multi-threaded program can deadlock (the classic post-fork mutex bug glibc's manual warns about). Building the PCB and generation tree by hand shows why chrome://process-internals and pstree display parent-child chains the way they do.
Before you call the implementation done, walk failure modes on purpose. Test empty structures, single-element edge cases, maximum concurrency, and errno paths that must not crash the program. OS code usually fails in production when happy-path tests pass but invariants break under contention or memory pressure.
Keep structures small and name fields after kernel counterparts when possible. That lets you read man pages and kernel source side by side while you work. Print observable events during development; remove noisy logs once tests pass reliably.
You will implement the PCB, fork(), wait(), and exit() in userspace so you can watch parent-child lifetimes without invoking the real syscall. This exercise asks you to make the generation counter and zombie reaping explicit, which is the same bookkeeping the kernel hides behind waitpid().
Write an interpreter for a tiny straight-line program that is run under fork() semantics. The first process (pid 100, parent 1) runs the program. fork creates a child that is an exact copy: it has the same variables and resumes at the same position, but its fork result is 0, while the parent's result is the child's pid. The output shows what every process prints, so you can check the classic "how many times does this print?" puzzles.
Program lines, then a line run. An optional line limit N (1..64, default 64) caps how many processes may ever be created, including the first.
| Statement | Meaning |
|---|---|
print TEXT | Print [pid P] TEXT. $name (letters only) is replaced by a variable, and $pid, $ppid and $r are built in. Unknown names print 0 |
set VAR N / add VAR N | Change a variable, in this process only |
fork | r becomes the child pid in the parent and 0 in the child. At the limit, r = -1 and no child is created |
child STMT / parent STMT | Run STMT only if r == 0 / r > 0 |
wait | Reap an exited child. Block if the process has children but none has exited |
exit N | Terminate with code N. Reaching the end of the program means exit 0 |
Run one process until it exits or blocks. Then run the ready process with the lowest pid. Child pids count up from 101. wait reaps the lowest-pid exited child. When a process exits, its parent becomes ready if it was blocked in wait. The exiting process's children are adopted by init (pid 1), so their $ppid becomes 1. Init reaps them straight away when they exit, so they never become zombies.
cLoading…
The final line counts every process created, including pid 100.
Input:
cLoading…
Output:
cLoading…
wait must finish when the process resumes, before its next statement runs.Hidden tests cover several children reaped in the order they exit, exit codes, wait with no children, orphans adopted by pid 1, the process limit with EAGAIN, nested forks with child/parent guards, and variables that change separately in each copy.