Best viewed on desktop for optimal interactive experience
The Heart of Linux: Process Management
Every program you run on Linux becomes a process - a living entity with its own memory space, resources, and lifecycle. From the moment you boot your system with PID 1 (init/systemd) to the thousands of processes running right now, understanding process management is key to mastering Linux.
Think of processes as actors on a stage. The kernel is the director, the CPU cores are the stages, and the scheduler decides who performs when. Some actors are parents who create children (fork), some transform into entirely different characters (exec), and sometimes actors die but refuse to leave the stage (zombies).
Let's explore this fascinating world where programs come to life, multiply, transform, and eventually die.
Interactive Process Visualization
Watch every step of process creation, execution, and termination - from fork to zombie reaping:
Process Management Deep Dive
Watch processes fork, exec, become zombies, get orphaned, and transition through states - every step visualized.
Parent process running
Process PID 1234 is executing. It needs to run a child program (e.g., shell executes "ls" command).
The Process Tree
Every process on a Linux system is part of a hierarchical tree structure, with PID 1 (systemd/init) at the root:
A Typical Process Tree
Key Concepts
Viewing the tree: Use pstree -p to see this hierarchy with PIDs, or ps axjf for a tree-like format.
Process Fundamentals
What is a Process?
A process is more than just a running program. It's a container managed by the kernel that includes:
- Identity: PID (Process ID), PPID (Parent PID), UID/GID (owner)
- State: RUNNING, READY, WAITING, STOPPED, ZOMBIE
- Memory: Code, data, heap, stack (separate virtual address space)
- Resources: Open files, network connections, signals
- Scheduling: Priority, nice value, CPU time consumed
The kernel represents each process with a task_struct - a data structure containing all this metadata.
Process vs Thread
Process: Heavy-weight, separate memory space, independent execution. Thread: Light-weight, shared memory space within process, shared resources.
When Firefox runs with 47 threads, they all share memory and file descriptors but have separate stacks and registers.
The Fork-Exec Model
fork(): Cloning Processes
fork() creates a new process by duplicating the calling process:
- Kernel creates child: New PID, copy of task_struct
- Copy-on-Write (COW): Memory pages shared, not copied
- Returns twice: Returns 0 to child, child PID to parent
- Identical but separate: Same code, but two different processes
Why Copy-on-Write? Copying gigabytes of memory would be wasteful. Instead, parent and child share read-only pages. Only when either writes to a page does the kernel create an actual copy. Efficient!
exec(): Transformation
exec() replaces the current process image with a new program:
- Same PID: Process identity preserved
- New program: Code, data, stack replaced from executable file
- File descriptors preserved: Open files remain open (unless close-on-exec)
- Common pattern:
fork()followed byexec()in child
Example: When you type ls in bash, bash calls fork() to create a child copy of itself, then the child calls exec("/bin/ls") to replace itself with the ls program.
Process States
Every process is always in one of these states:
- NEW: Being created (fork in progress)
- READY: Runnable, waiting in scheduler queue for CPU
- RUNNING: Actively executing on a CPU core
- WAITING: Blocked on I/O or event (sleep, disk read, network)
- STOPPED: Suspended by signal (SIGSTOP, Ctrl+Z)
- ZOMBIE: Terminated but exit status not yet collected by parent
State Transitions
- READY → RUNNING: Scheduler assigns CPU
- RUNNING → READY: Time slice expired (preempted)
- RUNNING → WAITING: Process blocks (I/O, sleep, lock)
- WAITING → READY: Event completes (I/O done, wake up)
- RUNNING → STOPPED: Receives SIGSTOP or SIGTSTP
- STOPPED → READY: Receives SIGCONT
- RUNNING → ZOMBIE: Calls exit() or killed
Zombies & Orphans
Zombie Processes
When a process terminates, it becomes a zombie - dead but not fully gone:
- Why exist? Must preserve exit status for parent to retrieve
- What's left? Only task_struct (process descriptor), no memory
- How to see?
ps aux | grep defunctor state shows 'Z' - How to kill? You can't! Already dead. Parent must call
wait() - Problem: If parent never calls
wait(), zombie persists forever
Orphan Processes
When a parent dies before its children:
- Kernel re-parents: Orphans automatically adopted by init (PID 1)
- init reaps: init periodically calls
wait()to clean up orphans - No zombie accumulation: init ensures orphans don't become permanent zombies
This is why PID 1 (init/systemd) is special - it's the ultimate parent that never dies and always cleans up its adopted children.
Orphan Process Adoption
Why This Matters
Orphan adoption ensures every process always has a parent. This is critical because when a process exits, it becomes a zombie until its parent calls wait(). If orphans weren't adopted, they'd stay as zombies forever. Systemd automatically reaps (cleans up) zombies from processes it adopts.
Real-World Example
When you close a terminal window that's running background processes, those processes become orphans and are immediately adopted by init/systemd. They continue running happily under their new parent.
# Close terminal - process becomes orphan
# systemd adopts it, process keeps running!
Process Creation Workflow
Step-by-step: Shell executes "ls" command
- User types:
ls -la - Shell (bash) calls:
fork() - Kernel creates child: New process, same code as bash
- fork() returns: Parent gets child PID (e.g., 1235), child gets 0
- Parent (bash): Calls
wait()to wait for child - Child (bash clone): Calls
exec("/bin/ls", "-la") - exec replaces child: bash code replaced with ls code, same PID
- Child (now ls): Executes, prints directory listing
- Child exits: Calls
exit(0), becomes zombie - Parent reaps:
wait()retrieves exit status, zombie cleaned up - Shell ready: Shows prompt again
Process Inspection
# View all processes ps aux # Process tree showing relationships pstree -p # Detailed process info ps -eo pid,ppid,state,comm,cmd # Watch processes in real-time top htop # Check specific process ps -p 1234 -o pid,ppid,state,comm,%cpu,%mem # View process memory map cat /proc/1234/maps # View process status cat /proc/1234/status # View process file descriptors ls -l /proc/1234/fd/
Process Control
# Create background process command & # List jobs jobs # Bring to foreground fg %1 # Send to background bg %1 # Suspend process (Ctrl+Z sends SIGTSTP) # Resume with: fg or bg # Send signals kill -SIGTERM 1234 # Request termination (15) kill -SIGKILL 1234 # Force kill (9, cannot be caught) kill -SIGSTOP 1234 # Suspend (19) kill -SIGCONT 1234 # Resume (18) # Kill all processes by name pkill firefox killall chromium
Process Priorities
Nice Values
Process priority controlled by "nice" value (-20 to +19):
- -20: Highest priority (least nice, hogs CPU)
- 0: Default priority
- +19: Lowest priority (very nice, yields CPU)
# Start with lower priority nice -n 10 ./cpu-intensive-task # Change priority of running process renice -n 5 -p 1234 # Require root for negative nice (higher priority) sudo renice -n -10 -p 1234
Real-time Priorities
For critical processes needing guaranteed CPU time:
# Set real-time priority (requires root) chrt -f 99 ./realtime-app # SCHED_FIFO priority 99 chrt -r 50 ./realtime-app # SCHED_RR priority 50 # View scheduling policy chrt -p 1234
Sessions and Process Groups
Beyond the parent-child hierarchy, processes are organized into sessions and process groups for job control and terminal management:
Sessions, Process Groups, and Processes
Session
Collection of process groups, usually one per terminal/login. Session leader creates session with setsid()
Process Group
One or more processes that can receive signals together. Used for job control (Ctrl+C, Ctrl+Z affect whole group)
Process
Individual running program within a process group
Example: Pipeline Command
⚠️Signal Delivery
When you press Ctrl+C, the kernel sends SIGINT to all processes in theforeground process group only. Background jobs are unaffected. This is why pipeline commands all terminate together - they're in the same process group!
Understanding the Hierarchy
- Session: A collection of process groups, typically one per terminal/login
- Process Group: One or more processes treated as a unit for job control
- Process: Individual running program
When you press Ctrl+C, SIGINT is sent to all processes in the foreground process group only. This is why piped commands (like cat file | grep pattern | wc) all terminate together - they share the same PGID!
CPU Scheduling
Linux uses the Completely Fair Scheduler (CFS) for normal processes:
- Red-black tree: Processes sorted by virtual runtime
- Fairness: Each process gets fair share of CPU proportional to priority
- Time slices: Dynamic, based on number of runnable processes
- Real-time classes: SCHED_FIFO and SCHED_RR for real-time tasks
Context Switch: When scheduler switches from one process to another:
- Save current process state (registers, PC) to memory
- Load new process state from memory
- Switch page tables (change memory view)
- Jump to new process's program counter
Context switches are expensive (~microseconds) so minimize thrashing!
Common Patterns
Fork-Exec Pattern
# Shell implementing command execution pid = fork() if (pid == 0) { # Child process exec("/bin/ls") } else { # Parent process wait(&status) }
Daemon Process
Long-running background service:
fork()and parent exits (detach from terminal)setsid()to create new session- Change directory to
/ - Close stdin/stdout/stderr
- Open
/dev/nullfor standard streams - Write PID to
/var/run/daemon.pid
Signal Handlers
# Install signal handler signal(SIGINT, handler_function) # Handler receives signal, performs cleanup # Can ignore, catch, or default action
Best Practices
- Always wait() for children: Prevent zombie accumulation
- Handle signals gracefully: Clean up resources on SIGTERM
- Set resource limits: Prevent runaway processes
- Use appropriate priorities: Don't starve other processes
- Monitor process count: Too many processes = system slowdown
- Clean up file descriptors: Close unneeded files in child after fork
- Check return values: fork() can fail if resource limits hit
Related Concepts
- System Calls: How user programs communicate with kernel
- Memory Management: Virtual memory and address spaces
- Kernel Architecture: How the kernel manages processes
- Init Systems: PID 1 and the process hierarchy
