Section 07: Process Management — Overview
Section Purpose and Scope
The process is the fundamental unit of execution and isolation in every general-purpose operating system. Everything else — scheduling, memory management, file systems, networking — exists to serve processes or to manage their interactions. This section covers the complete lifecycle of a process from the kernel's perspective: what a process is at the data structure level, how processes are created and destroyed, how they transition through states, how they communicate, and how modern Linux extends the classical process model with namespaces and containers.
The section is Linux-centric with comparisons to Windows NT and other systems where design choices diverge significantly. By the end, you should be able to trace any process-related system call to its kernel implementation and reason about edge cases like zombie accumulation, orphan adoption, and PID namespace nesting.
Prerequisites
- Section 00 (Foundations): system calls, privilege rings, interrupts
- Section 03 (Kernel Fundamentals):
task_struct, kernel memory, system call interface - Section 06 (CPU Architecture): register state, context switching mechanics
Learning Objectives
After completing this section you will be able to:
- Describe
task_structas the PCB and explain its most important fields - Trace a
fork()call from user space through the kernel to the creation of a newtask_struct - Explain
exec()and theexecfamily: how the address space is replaced while the PID is preserved - Describe the complete process state machine and valid transitions
- Explain zombie and orphan processes, how they occur, and how to prevent/resolve them
- Describe process groups, sessions, and the role of the session leader and controlling terminal
- Explain UNIX signals: delivery, disposition, signal handlers, and async-signal safety
- Enumerate and describe the major IPC mechanisms: pipes, FIFOs, message queues, shared memory, semaphores, sockets
- Explain Linux namespaces and how they form the isolation primitive underlying containers
- Describe PID namespaces and how they create nested PID number spaces
Architecture Overview
PROCESS STATE MACHINE (Linux)
fork() / clone()
│
▼
┌──────────┐
┌─────►│ CREATED │
│ └────┬─────┘
│ │ scheduler admits
│ ▼
│ ┌──────────┐ preempted / yield
│ │ RUNNABLE │◄──────────────────────┐
│ └────┬─────┘ │
│ │ CPU assigned │
│ ▼ │
│ ┌──────────┐ time slice expires │
│ │ RUNNING │────────────────────────┘
│ └────┬─────┘
│ ┌────┴─────┐
│ │ │
wait│ I/O │ signal│ exit()
ok │ req │ │
│ ▼ ▼
│ ┌────────┐ ┌──────┐
│ │INTERR- │ │STOP- │ SIGSTOP/SIGTSTP
│ │RUPTIBLE│ │ PED │◄── debugger ptrace
│ │ SLEEP │ └──┬───┘
│ └───┬────┘ │ SIGCONT
│ │ ◄───────┘
│ I/O│done
└──────┘
│ exit() / fatal signal
▼
┌──────────┐
│ ZOMBIE │ PCB kept until parent wait()
└────┬─────┘
│ parent calls wait() / waitpid()
▼
┌──────────┐
│ DEAD │ task_struct freed
└──────────┘
PROCESS HIERARCHY AND RELATIONSHIPS:
PID 1 (systemd / init)
├── PID 100 (sshd) [session leader]
│ └── PID 200 (bash) [process group leader]
│ └── PID 300 (vim)
│ └── PID 301 (vim) [same process group as PID 300]
├── PID 101 (nginx master)
│ ├── PID 202 (nginx worker 1) [orphan if 101 dies → adopted by init]
│ └── PID 203 (nginx worker 2)
└── PID 102 (cron)
FORK / EXEC MODEL:
Parent Kernel Child
────── ────── ─────
pid = fork()
│
├──────────────────► copy_process()
│ ├── alloc task_struct
│ ├── copy mm_struct (CoW)
│ ├── copy files_struct
│ ├── copy signal_struct
│ └── add to run queue
│
pid = child_pid ◄────────────────────────── pid = 0
(returns child PID) (child returns 0)
│
wait(child_pid)
← child calls execve("/bin/ls")
├── load_elf_binary()
├── flush old address space
├── mmap new segments
└── jump to ELF entry point
Key Concepts
- Process: An instance of a running program. Has an independent virtual address space, at least one thread, open file descriptors, a set of credentials (UID/GID/capabilities), and a resource accounting record.
- PCB (Process Control Block): The kernel data structure representing a process. In Linux:
task_struct. In Windows:EPROCESS. - fork(): Creates an exact copy of the calling process. Parent and child differ only in the return value. Uses copy-on-write (CoW) for memory pages — no data is physically copied until either process writes to a page.
- exec(): Replaces the calling process's address space with a new program, while preserving the PID, open file descriptors (unless
O_CLOEXEC), and certain other attributes. - vfork(): A legacy variant of
fork()designed for the commonfork()+exec()pattern. The child borrows the parent's address space and the parent is blocked until the child callsexec()or_exit(). Dangerous; preferposix_spawn(). - clone(): Linux-specific. The generalized system call underlying both
fork()and thread creation. Takes flags specifying which resources to share (address space, file table, signal handlers, etc.).CLONE_NEWPID,CLONE_NEWNET, etc. create new namespaces. - Zombie Process: A process that has exited but whose
task_structhas not been freed because the parent has not yet calledwait(). The zombie holds its exit status. A zombie flood (thousands of zombies from a failing parent) consumes PID space. - Orphan Process: A process whose parent has exited before it. The kernel re-parents orphans to PID 1 (init/systemd), which periodically calls
wait()to reap them. - Process Group: A collection of processes that can receive a signal together (e.g., sending SIGTERM to a shell job). Every process has a PGID.
kill(-pgid, sig)signals all group members. - Session: A collection of process groups, typically associated with a login session. Has a session leader and optionally a controlling terminal.
- Signal: An asynchronous software notification sent to a process. 31 real-time signals (SIGRTMIN–SIGRTMAX) + classic signals (SIGTERM, SIGKILL, SIGCHLD, etc.). Delivered by the kernel; caught, ignored, or take default action.
- IPC (Inter-Process Communication): Mechanisms for processes to exchange data. Taxonomy:
- Pipes: Anonymous, unidirectional, byte stream, in-kernel buffer
- FIFOs: Named pipes — persistent in the filesystem namespace
- POSIX Message Queues: Prioritized message passing via
mq_open() - POSIX Shared Memory:
shm_open()+mmap()— fastest IPC (no kernel copy) - POSIX Semaphores: Synchronization primitives (not data transfer)
- UNIX Domain Sockets: Full-duplex, datagram or stream, can pass file descriptors
- Sysv IPC: Legacy msgget/shmget/semget — avoid in new code
- Linux Namespaces: Kernel mechanisms that partition global resources into isolated instances. Each namespace type wraps one resource type. Combining namespaces creates containers.
- PID Namespace: Processes within a PID namespace see a different (nested) PID number space. PID 1 in a namespace is the "init" for that namespace. Nested namespaces form a tree.
Linux Namespace Types
┌─────────────────┬──────────────────────────────────────────────────────┐
│ Namespace │ What it isolates │
├─────────────────┼──────────────────────────────────────────────────────┤
│ PID │ Process IDs — PID 1 in namespace is not system init │
│ Net │ Network stack — separate interfaces, routes, iptables│
│ Mnt │ Mount points — private filesystem view │
│ UTS │ Hostname and domain name │
│ IPC │ POSIX message queues, System V IPC objects │
│ User │ UID/GID mapping — root in container ≠ root on host │
│ Cgroup │ cgroup root — separate resource control hierarchy │
│ Time │ CLOCK_MONOTONIC, CLOCK_BOOTTIME offsets (Linux 5.6) │
└─────────────────┴──────────────────────────────────────────────────────┘
Major Historical Milestones
| Year | Milestone |
|---|---|
| 1969 | Unix: fork() and exec() as separate system calls — elegant simplicity |
| 1974 | wait() system call added — parent-child lifecycle formalized |
| 1979 | Unix V7: setpgrp() — process groups introduced |
| 1983 | BSD 4.2: waitpid(), job control signals (SIGTSTP/SIGCONT) |
| 1988 | POSIX.1: fork/exec/wait standardized across Unix variants |
| 1994 | Linux 1.0: basic process management; no SMP |
| 1996 | Linux 2.0: SMP support; per-CPU runqueues emerge |
| 2001 | Linux 2.4.2: CLONE flags expanded; linuxthreads basis |
| 2002 | Linux 2.4.19: first namespace prototype (mount namespaces) |
| 2003 | Linux 2.6: NPTL (Native POSIX Thread Library) — efficient 1:1 threads |
| 2006 | Linux 2.6.19: UTS, IPC namespaces added |
| 2006 | Linux 2.6.24: PID namespaces |
| 2007 | Linux 2.6.24: Network namespaces (full support in 2.6.29) |
| 2008 | LXC (Linux Containers) 0.1: combines namespaces + cgroups |
| 2013 | Docker 0.1: popularizes containers via namespaces |
| 2016 | Linux 4.6: User namespaces fully stable |
| 2020 | Linux 5.6: Time namespace added |
| 2022 | Linux 5.17: process_madvise(), more granular process control |
Modern Relevance and Production Use Cases
Containers: Every Docker container, Kubernetes pod, and OCI container is a process (or process tree) running in a set of Linux namespaces with cgroup resource limits. Understanding clone() flags and namespace semantics is mandatory for container security analysis.
Security isolation: The user namespace enables rootless containers — a key security improvement where the container's "root" maps to an unprivileged host UID. Understanding UID mapping prevents privilege escalation bugs.
Process supervision: systemd unit files, supervisord, and s6 all manage the fork/exec/wait lifecycle. Process leak bugs (zombie floods, PID exhaustion) are production incidents that require this section to diagnose.
Debugging: strace traces system calls including clone, execve, wait4. pstree shows the process hierarchy. /proc/[pid]/status exposes task_struct fields. /proc/[pid]/ns/ shows namespace membership.
Performance: fork() CoW, exec() ELF loading time, and signal delivery overhead all appear in production profiles. Frequent fork()+exec() (CGI, shell scripts) can be a bottleneck; solutions (pre-forking, process pools, posix_spawn()) require this knowledge.
File Map
07-process-management/
├── 00-overview.md ← This file
├── 01-process-concept.md ← Definition, PCB, process vs thread vs task
├── 02-task-struct-anatomy.md ← Key fields, relationships, kernel source tour
├── 03-fork-and-exec.md ← copy_process(), CoW, execve(), ELF loading
├── 04-process-lifecycle.md ← State machine, scheduler states, transitions
├── 05-process-termination.md ← exit(), do_exit(), zombie collection
├── 06-orphans-and-zombies.md ← Edge cases, detection, prevention, subreaping
├── 07-process-groups-sessions.md ← Job control, controlling terminal, SIGHUP
├── 08-signals.md ← Signal table, delivery, dispositions, safety
├── 09-ipc-mechanisms.md ← Pipes, FIFOs, mqueues, shm, sockets comparison
├── 10-process-isolation.md ← Address space isolation, seccomp, capabilities
├── 11-namespaces.md ← All 8 namespace types, unshare, nsenter
├── 12-pid-namespaces.md ← Nested PID spaces, init in namespace, /proc
Cross-References
- Section 03 (Kernel Fundamentals):
task_structintroduced there, expanded here - Section 08 (Threading Models): Threads as tasks sharing address space via
clone() - Section 09 (Scheduling): The runqueue and scheduling entity within
task_struct - Section 11 (Memory Management):
mm_struct, CoW,exec()address space replacement - Section 20 (Containers): Namespaces combined with cgroups = containers
- Section 26 (Security): Seccomp, capabilities, user namespaces for privilege reduction
Recommended Depth of Study
Essential: Files 01–09. Process creation, lifecycle, signals, and IPC are foundational for any Unix programmer.
Deep dive recommended: Files 10–12 for container/security engineers. File 02 for kernel developers.
Hands-on: Write a shell in C using fork()/exec()/wait(). Observe zombie accumulation with a broken parent. Create a PID namespace with unshare -p --fork --mount-proc and observe the new PID space.
Estimated study time: 15–20 hours including programming exercises.