Process Lifecycle¶
What is it: the sequence of states a process goes through: created → running → waiting → terminated.
fork(): Creates a child process as a CoW copy of the parent. Child inherits: address space (shared pages until written), fd table, signal handlers, env vars, working directory. New PID. Returns 0 in child, child's PID in parent.
exec():
Replaces current process image with a new program. PID unchanged. Address space replaced. FDs inherited unless FD_CLOEXEC set. Typical pattern: fork() + exec() to run a new program in a child.
wait(): Parent collects child's exit status. Removes child's PCB entry from process table.
Zombie process:
Child has exited but parent hasn't called wait(). PCB entry (PID, exit status) remains in process table. Wastes one process table slot. Accumulates if parent never calls wait(). Detection: ps aux | grep Z.
Orphan process:
Parent exits before child. Linux re-parents orphan to init (PID 1). init calls wait() for all its children — orphans are cleaned up automatically. Useful for daemons: fork, parent exits, child is re-parented to init and continues as a background daemon.
Real-world usage: Redis uses fork() for RDB snapshots. Go's os/exec uses fork+exec. Proxel launches Puppeteer subprocesses via exec.Command with FD_CLOEXEC to prevent fd leaks.
Common pitfall¶
A long-running parent process that spawns many short-lived children
but never calls wait() (or waitpid() in a SIGCHLD handler)
accumulates zombies indefinitely — each is small (just a PCB entry),
but the process table itself has a finite size, and enough
accumulated zombies can eventually block new processes from being
created system-wide, not just within the leaking application. This is
a common gap in code that spawns subprocesses via a library that
doesn't automatically reap them; ps aux | grep Z catching zombies in
production is the direct symptom to watch for.