Skip to content

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:

  1. Describe task_struct as the PCB and explain its most important fields
  2. Trace a fork() call from user space through the kernel to the creation of a new task_struct
  3. Explain exec() and the exec family: how the address space is replaced while the PID is preserved
  4. Describe the complete process state machine and valid transitions
  5. Explain zombie and orphan processes, how they occur, and how to prevent/resolve them
  6. Describe process groups, sessions, and the role of the session leader and controlling terminal
  7. Explain UNIX signals: delivery, disposition, signal handlers, and async-signal safety
  8. Enumerate and describe the major IPC mechanisms: pipes, FIFOs, message queues, shared memory, semaphores, sockets
  9. Explain Linux namespaces and how they form the isolation primitive underlying containers
  10. 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 common fork()+exec() pattern. The child borrows the parent's address space and the parent is blocked until the child calls exec() or _exit(). Dangerous; prefer posix_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_struct has not been freed because the parent has not yet called wait(). 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_struct introduced 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

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.