Every single time you open an app — whether it’s a browser, a game, or a background sync service — that action triggers a whole lifecycle inside the operating system. I want to walk through exactly what that lifecycle looks like from the moment a process is born to the moment it dies, including all the states in between, how transitions happen, and how this plays out differently (and similarly) across Linux, Windows, Android, and iOS.
What Is a Process?
Before the lifecycle makes sense, let’s ground the definition: a process is a program in execution. It’s not just the code — it includes the current activity, represented by the program counter, the contents of the CPU registers, the process stack (holding temporary data like function parameters and return addresses), a data section holding global variables, and a heap for dynamically allocated memory.
The Core Process States
Most operating system textbooks describe five primary states in the process lifecycle:
- New: The process is being created. The OS is setting up the necessary data structures (like the Process Control Block) but the process hasn’t yet been admitted to the ready queue.
- Ready: The process is loaded into main memory and waiting to be assigned to a CPU by the scheduler. It’s fully capable of running — it’s just waiting its turn.
- Running: The process’s instructions are actively being executed by the CPU. On a single-core system, only one process can be in this state at a time (though threads within it may interleave); on multi-core systems, multiple processes can genuinely run simultaneously, one per core.
- Waiting (Blocked): The process cannot continue until some event occurs — typically completion of an I/O operation, availability of a resource, or receipt of a signal. It’s removed from CPU contention until that event happens.
- Terminated (Exit): The process has finished execution (or been forcibly killed) and the OS is cleaning up its resources — closing file descriptors, freeing memory, and removing its PCB.
Some models also include a Suspended state (or two variants: suspended-ready and suspended-blocked), used when the medium-term scheduler swaps a process out of main memory to disk to free up RAM for other processes — this becomes relevant under heavy memory pressure.
Process State Transition Diagram
admit dispatch
[New] ------------> [Ready] -------------> [Running]
^ |
| time slice expired |
+-------------------------+
\
I/O or event wait
v
[Waiting]
|
I/O or event completes
|
v
[Ready]
[Running] ----exit/completion----> [Terminated]
Let’s trace through this with a concrete example: you double-click an application icon.
- New: The OS creates a PCB, allocates initial memory, and loads the executable’s code into memory (or prepares to load it lazily via demand paging).
- Ready: Once setup is complete, the process moves to the ready queue, waiting for scheduler dispatch.
- Running: The scheduler picks it, the CPU starts executing its instructions, and you see the app launch.
- Waiting: The app calls a function to read a configuration file from disk — this triggers an I/O wait, and the OS moves it to the waiting state while the disk read completes, freeing the CPU for other processes in the meantime.
- Ready (again): Once the disk read completes, an interrupt signals the OS, which moves the process back to ready, waiting for its next CPU turn.
- Running (again): The scheduler dispatches it again, and it continues execution.
- This cycle between Running, Waiting, and Ready repeats constantly throughout the process’s life — every file read, network request, or user input wait triggers this transition.
- Terminated: Eventually, you close the app (or it finishes its task). It calls an exit system call, the OS reclaims its memory, closes open file descriptors and network sockets, and removes its entry from the process table.
Process Creation in Detail
Process creation happens through specific system calls, which differ by OS family:
- UNIX/Linux: Uses
fork()to create a near-identical copy of the calling (parent) process, followed typically byexec()to replace the child’s memory image with a new program. This fork-then-exec model is foundational to UNIX-family process creation and is why you’ll see this pattern in shells, servers, and countless system utilities. - Windows: Uses
CreateProcess(), which combines process creation and program loading into a single call, rather than the two-step fork/exec model. - Android: Uses a specialized approach via the Zygote process — a pre-initialized process with the Android runtime and common libraries already loaded, which is then forked to quickly spawn new app processes, dramatically speeding up app launch times compared to starting from scratch each time.
- iOS: Process creation is tightly controlled by the OS via
launchd, the system and service management daemon that starts, stops, and manages processes, including apps launched by the user.
Parent-Child Relationships and Zombie/Orphan Processes
In UNIX-like systems, processes form a tree, with each process (except the initial one) having a parent. Two special situations are worth understanding:
- Zombie process: A process that has terminated, but its parent hasn’t yet called
wait()to read its exit status and clean up its PCB entry. It’s “dead” but still has an entry in the process table, hence the name. - Orphan process: A process whose parent has terminated before it. In UNIX/Linux, orphaned processes are typically “adopted” by the
initprocess (PID 1) or, in modern systemd-based Linux systems, by systemd, which takes over responsibility for eventually reaping them.
Real-World Examples Across Platforms
Linux: You can watch process states live using ps aux (state column: R for running, S for sleeping/waiting, Z for zombie, T for stopped) or top/htop for a real-time view. The /proc/[pid]/status file gives detailed state information for any process.
Windows: Task Manager and Process Explorer (from Sysinternals) show process states, and tasklist from the command line offers a quick textual view.
Android: The Activity Manager oversees app process lifecycles, which are actually more nuanced than the basic OS process model — apps move through Android-specific states like Foreground, Visible, Service, Background, and Empty, which the system uses to decide which processes to kill first under memory pressure (this is layered on top of the underlying Linux process states).
iOS: Apps have their own lifecycle states — Not Running, Inactive, Active, Background, and Suspended — managed by the OS to balance user experience with battery and memory efficiency, again layered on top of the underlying Darwin/XNU process model.
Troubleshooting Process Lifecycle Issues
- Zombie process accumulation: If you see many zombie (
Zstate) processes on Linux, it usually indicates a parent process isn’t callingwait()/waitpid()properly — a common bug in custom server or daemon code. - Processes stuck in Waiting/Uninterruptible Sleep (
Dstate on Linux): Often indicates a hung I/O operation, commonly a problem with slow or failing storage, or an NFS mount that’s become unresponsive. - High “Ready queue” length: Indicates CPU contention — more processes want to run than the CPU can currently service, suggesting either a need for more cores or investigation into runaway processes.
- Apps unexpectedly killed on mobile: On Android and iOS, background apps are often killed due to memory pressure — this is expected behavior, not a bug, and apps should be designed to save state before being backgrounded.
Best Practices
- Always properly reap child processes (
wait()/waitpid()on UNIX-like systems) to avoid zombie process buildup. - Design applications to gracefully handle being moved to a waiting/background state, saving necessary state proactively.
- Avoid unnecessary busy-waiting loops that keep a process in the Running state when it should logically be Waiting — this wastes CPU cycles that the scheduler could give to other processes.
- Use appropriate signal handling (
SIGTERMvsSIGKILLon UNIX-like systems) to allow processes to terminate gracefully, cleaning up resources properly rather than being abruptly killed.
Summary
The process life cycle describes the journey a program takes from creation to termination, moving through New, Ready, Running, Waiting, and Terminated states, with transitions driven by scheduler decisions, I/O operations, and system events. Understanding this lifecycle is fundamental to understanding how operating systems manage multitasking, and while the core model is consistent across UNIX, Linux, Windows, Android, and iOS, each platform layers its own specific mechanisms (fork/exec, Zygote, launchd, app lifecycle states) on top of these foundational concepts.
FAQs
Q: What’s the difference between a process and a thread in terms of lifecycle? A process has its own full lifecycle with its own memory space; threads within a process share that memory space but each thread still moves through similar running/waiting/ready states independently.
Q: What causes a zombie process? A child process terminates, but its parent hasn’t yet called wait() to collect its exit status, leaving an entry in the process table until it’s reaped.
Q: Can a process skip the Waiting state entirely? Yes — a purely CPU-bound process with no I/O or blocking calls can transition directly between Ready and Running repeatedly without ever entering the Waiting state, until it terminates.
Q: Why does Android use Zygote instead of standard process creation? Zygote pre-loads the Android runtime and common framework classes once, then forks lightweight copies for each new app, drastically reducing app launch time compared to initializing everything from scratch.
Q: What happens to open files when a process terminates? The OS automatically closes any file descriptors the process still had open and reclaims associated memory and resources during the termination cleanup phase.
References
- Silberschatz, Galvin, Gagne — Operating System Concepts, Chapter on Processes
- Linux
procFilesystem Documentation — https://www.kernel.org/doc/html/latest/filesystems/proc.html - Android Developers — App Lifecycle — https://developer.android.com/guide/components/activities/process-lifecycle
- Apple Developer Documentation — App Lifecycle — https://developer.apple.com/documentation/uikit/app_and_environment