02-process-management.md

βš™οΈ Module 02: Process Management & IPC

Focus: Process Anatomy, PCB & TCB, Multiprogramming vs Multitasking vs Multiprocessing vs Multithreading, Context Switching, fork() & Copy-On-Write, Zombie/Orphan processes, and Inter-Process Communication (IPC).


πŸ“‘ Table of Contents

  1. Process Concept & Memory Layout
  2. Process Control Block (PCB)
  3. Process Lifecycle: 5-State & 7-State Models
  4. Multiprogramming vs Multitasking vs Multiprocessing vs Multithreading
  5. Process vs Thread: Deep Dive
  6. Threading Models (1:1, N:1, M:N) & Green Threads
  7. Context Switching Internals
  8. Process Creation & Lifecycle (fork, COW, exec, wait)
  9. Zombie and Orphan Processes
  10. Inter-Process Communication (IPC) Mechanisms
  11. Common Mistakes & Gotchas
  12. Practice Problems with Solutions
  13. High-Yield Interview Questions & Answers

1. Process Concept & Memory Layout

A Process is a program in active execution. While a program is a passive entity sitting on a disk (executable file like a.out or .exe), a process is an active entity loaded into RAM with allocated hardware resources.

High Memory (0xFFFFFFFF / 0x7FFFFFFFFFFF) β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ KERNEL SPACE β”‚ β”‚ (Memory mapped kernel code, page tables) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ USER STACK β”‚ β”‚ (Local variables, function frames, return addresses) β”‚ β”‚ β”‚ β”‚ β”‚ β–Ό (Grows downward toward lower addresses) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β–² (Grows upward toward higher addresses) β”‚ β”‚ β”‚ β”‚ β”‚ USER HEAP β”‚ β”‚ (Dynamic memory: malloc, new, calloc, brk/sbrk) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ BSS SEGMENT β”‚ β”‚ (Uninitialized global and static variables: int x;) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ DATA SEGMENT β”‚ β”‚ (Initialized global and static variables: int x = 5;) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ TEXT / CODE SEGMENT β”‚ β”‚ (Compiled read-only machine instructions) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Low Memory (0x00000000)

Memory Segments Breakdown:

  1. Text (Code) Segment: Read-only region containing executable binary instructions. Shared among multiple instances of the same program (e.g., two terminal windows running bash).
  2. Data Segment: Stores initialized global and static variables (e.g., static int count = 100;).
  3. BSS Segment (Block Started by Symbol): Stores uninitialized global and static variables (e.g., int global_array[1000];). Takes $0$ bytes in the executable file on disk because the kernel zeros it out at runtime.
  4. Heap Segment: Dynamically allocated memory at runtime via malloc(), calloc(), or new. Managed by the programmer / language runtime.
  5. Stack Segment: Automatically manages function call frames, local variables, parameters, and return addresses. Follows LIFO (Last-In-First-Out).

2. Process Control Block (PCB)

The Process Control Block (PCB) (called struct task_struct in the Linux kernel) is the central data structure the OS kernel uses to represent and track a process.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PROCESS CONTROL BLOCK (PCB) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β€’ Process ID (PID) & Parent PID (PPID) β”‚ β”‚ β€’ Process State (READY, RUNNING, WAITING, ZOMBIE) β”‚ β”‚ β€’ Program Counter (PC / RIP) β”‚ β”‚ β€’ CPU Registers (RAX, RBX, RCX, RSP, RBP, EFLAGS) β”‚ β”‚ β€’ CPU Scheduling Info (Priority, CFS vruntime, nice) β”‚ β”‚ β€’ Memory Management Info (Page Table Pointer %CR3) β”‚ β”‚ β€’ I/O Status Info (Open File Descriptor Table) β”‚ β”‚ β€’ Accounting & Limits (CPU time used, Max RAM, UID) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3. Process Lifecycle: 5-State & 7-State Models

The Classic 5-State Process Model

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ NEW β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Admitted β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ί β”Œβ”€β”€β”€β”€β”€β”€β”€β” ◄─────────────────┐ β”‚ β”‚ READY β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€ β””β”€β”€β”€β”€β”€β”€β”€β”˜ ────────┐ β”‚ β”‚ β”‚ β”‚ β”‚ I/O or β”‚ Timeout β”‚ (Preemption) Selected β”‚ by β”‚ I/O or Event β”‚ β”‚ β”‚ Sched β”‚ Event Done β”‚ β–Ό β–Ό β”‚ Wait β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ RUNNING β”‚β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ Exit β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚WAITINGβ”‚ β”‚TERMINATED β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  1. New: The process is being created (memory allocated, PCB initialized).
  2. Ready: The process is loaded in RAM and waiting to be assigned a CPU core by the scheduler.
  3. Running: The process's instructions are actively being executed on a CPU core.
  4. Waiting (Blocked): The process cannot run because it is waiting for an external event (disk I/O, network packet, lock acquisition, timer sleep).
  5. Terminated: The process has finished execution; resources are released, but PCB may remain until reaped by parent.

The 7-State Model (With Memory Swapping)

When physical RAM runs low, the OS moves inactive processes from RAM to the swap space on disk:

  • Ready Suspended: Process is ready to execute, but its address space has been swapped to disk.
  • Blocked Suspended: Process is waiting for an I/O event AND has been swapped to disk.

4. Multiprogramming vs Multitasking vs Multiprocessing vs Multithreading

These four terms are frequently tested in interviews and often confused:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ MULTIPROGRAMMING β”‚ β”‚ β€’ Single CPU core. β”‚ β”‚ β€’ Multiple programs in RAM simultaneously. β”‚ β”‚ β€’ CPU switches ONLY when current job waits for I/O (Non-preemptive). β”‚ β”‚ β€’ Goal: Maximize CPU Utilization. β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ MULTITASKING (Time-Sharing) β”‚ β”‚ β€’ Single or Multi-core CPU. β”‚ β”‚ β€’ Time-slicing via timer interrupts (Preemptive). Rapid switching (~10-50ms). β”‚ β”‚ β€’ Goal: Minimize User Response Time (Illusion of simultaneous execution). β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ MULTIPROCESSING β”‚ β”‚ β€’ Physical Hardware feature: 2+ CPU cores or physical processors. β”‚ β”‚ β€’ True True Parallel Execution: Multiple processes execute at the EXACT same nanosecond.β”‚ β”‚ β€’ Goal: Maximize Throughput / Parallel computing power. β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ MULTITHREADING β”‚ β”‚ β€’ Multiple execution paths (threads) within the SAME process address space. β”‚ β”‚ β€’ Shared code, data, heap, and file descriptors; Private stack & registers. β”‚ β”‚ β€’ Goal: Responsiveness, resource sharing, and fine-grained concurrency. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Comparison Matrix

FeatureMultiprogrammingMultitaskingMultiprocessingMultithreading
Core GoalHigh CPU utilizationLow response timeHigh throughputFine-grained parallelism
Preemption?No (switches on I/O)Yes (time quantum)YesYes
Hardware Needed1 CPU is enough1 CPU is enough2+ Physical CPU cores1+ CPU cores
Address SpaceSeparate per processSeparate per processSeparate per processShared within process

5. Process vs Thread: Deep Dive

A Thread is the smallest unit of execution that the OS scheduler can dispatch. It is often called a Lightweight Process (LWP).

SINGLE-THREADED PROCESS MULTI-THREADED PROCESS β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Code β”‚ Data β”‚ BSS β”‚ Heap β”‚ β”‚ Code β”‚ Data β”‚ BSS β”‚ Heap β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Open File Descriptors β”‚ β”‚ Open File Descriptors β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Registers β”‚ Program Counter (PC) β”‚ β”‚ Registers T1 β”‚ Registers T2 β”‚ Regs T3β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Stack (T1) β”‚ β”‚ Stack T1 β”‚ Stack T2 β”‚Stack T3β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜

What is Shared vs Private?

Shared Between Threads of Same ProcessPrivate to Each Individual Thread
β€’ Text / Code Segment (Instructions)β€’ Thread ID (TID)
β€’ Data & BSS Segment (Global / Static vars)β€’ Program Counter (PC / RIP)
β€’ Heap Segment (malloc / dynamically allocated memory)β€’ CPU Register Set (RAX, RBX, etc.)
β€’ Open File Descriptors (Sockets, files)β€’ Individual Call Stack & Stack Pointer (%RSP)
β€’ Process ID (PID) and User/Group permissionsβ€’ Thread-Local Storage (TLS)
β€’ Signal Handlersβ€’ Signal Mask & Errno variable

Why Threads are "Lighter" than Processes:

  1. Creation Overhead: Spawning a thread doesn't require duplicating page tables or address spaces.
  2. Context Switch Speed: Switching between threads of the same process keeps the same page table (%CR3), meaning the TLB (Translation Lookaside Buffer) is NOT flushed and CPU caches remain warm.
  3. Communication: Threads share memory directly (no syscalls needed for IPC), whereas processes require pipes, sockets, or shared memory syscalls.

6. Threading Models (1:1, N:1, M:N) & Green Threads

1:1 (Kernel-Level Threads) N:1 (User-Level Threads) M:N (Hybrid / Goroutines) β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”‚ T1 β”‚ β”‚ T2 β”‚ β”‚ T3 β”‚ β”‚ T1 β”‚ β”‚ T2 β”‚ β”‚ T3 β”‚ β”‚ G1 β”‚ β”‚ G2 β”‚ β”‚ G3 β”‚ User β””β”€β”¬β”€β”€β”˜ β””β”€β”¬β”€β”€β”˜ β””β”€β”¬β”€β”€β”˜ β””β”€β”€β”€β”¬β”΄β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”¬β”€β”€β”˜ β””β”€β”€β”€β”¬β”΄β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”¬β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ User Thread Runtime β”‚ Go / Loom Runtime β–Ό β–Ό β–Ό β–Ό β–Ό β–Ό β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ K1 β”‚ β”‚ K2 β”‚ β”‚ K3 β”‚ Kernel β”‚ K1 β”‚ Kernel β”‚ K1 β”‚ β”‚ K2 β”‚ Kernel β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ (Linux pthreads, Java threads) (Early Java, Python Greenlets) (Go Goroutines, Erlang)
  1. 1:1 Model (Kernel-Level Threads - NPTL on Linux):
    • Every user thread maps directly to an OS kernel thread.
    • Advantage: True multicore parallelism. If Thread 1 blocks on disk I/O, Thread 2 runs unaffected on another core.
    • Cost: Thread creation and context switches require kernel traps.
  2. N:1 Model (User-Level Threads / Green Threads):
    • Multiple user threads mapped to a single kernel thread via a user-space library.
    • Advantage: Ultra-fast creation and switching (no syscalls).
    • Fatal Flaw: If ONE user thread issues a blocking I/O syscall, the entire process blocks. Cannot use multiple CPU cores.
  3. M:N Model (Two-Tier / Go Goroutines / Java Project Loom):
    • Multiplexes $M$ lightweight user routines onto $N$ kernel OS threads.
    • Combines instant context switches with multicore scalability and non-blocking I/O multiplexers (epoll).

7. Context Switching Internals

A Context Switch is the procedure by which the CPU saves the state of the currently executing process/thread and restores the state of another ready process/thread.

PROCESS P0 OPERATING SYSTEM PROCESS P1 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Executing code β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ │───[ Interrupt / Trap ]β–Ίβ”‚ Saves P0 State to β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ PCB0 (Regs, PC, Stack)β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ β”‚ β”‚ Scheduler picks P1 β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ β”‚ β”‚ Loads P1 State from β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ PCB1 (Regs, PC, %CR3) │───[ `sysret` ]───►│ Executing code β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚

Step-by-Step Breakdown of a Process Context Switch:

  1. Interrupt Trigger: Hardware timer expires (preemption) or process makes a blocking I/O system call.
  2. Save CPU Context: Hardware & OS push all general-purpose registers (RAX, RBX, RSI, RSP, RIP, EFLAGS) onto the process's Kernel Stack.
  3. Update PCB: State of $P_0$ is changed from RUNNING to READY or WAITING.
  4. Run Scheduler: The CPU scheduling algorithm selects next candidate process ($P_1$).
  5. Switch Address Space: The OS updates the CPU's %CR3 register to point to $P_1$'s Page Table base. This causes a TLB Flush.
  6. Restore CPU Context: Load registers from $P_1$'s PCB / Kernel Stack into hardware registers.
  7. Resume Execution: Execute return instruction (sysret / iret), setting the Program Counter to $P_1$'s saved %RIP.

The Hidden Cost: Direct vs Indirect Overhead

  • Direct Overhead: ~1–5 microseconds spent executing kernel context switch instructions and updating memory pointers.
  • Indirect Overhead (Cache Pollution): When $P_1$ begins running, CPU L1/L2/L3 caches and the TLB are filled with data from $P_0$. $P_1$ experiences a barrage of cache misses and TLB misses for several milliseconds until its working set is warm.

8. Process Creation & Lifecycle (fork, COW, exec, wait)

1. The fork() System Call

In Unix-like systems, new processes are created by cloning an existing process using fork():

#include <stdio.h> #include <unistd.h> #include <sys/types.h> int main() { pid_t pid = fork(); if (pid < 0) { perror("Fork failed"); } else if (pid == 0) { // Child Process printf("Child: PID = %d, Parent PID = %d\n", getpid(), getppid()); } else { // Parent Process printf("Parent: PID = %d, Created Child PID = %d\n", getpid(), pid); } return 0; }

2. Copy-On-Write (COW)

Historically, fork() duplicated the entire physical RAM of the parent process, which was devastatingly slow. Modern OSes use Copy-On-Write (COW):

PARENT & CHILD AFTER FORK() (Read-Only Shared Pages) β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Parent Page Table │───┐ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”œβ”€β”€β”€β–Ί β”‚ Physical RAM Page 100 β”‚ (Marked READ-ONLY) β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Child Page Table β”‚β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ WHEN CHILD TRIES TO WRITE TO PAGE 100: 1. MMU detects Write to Read-Only Page -> Triggers Page Fault (Trap 14) 2. Kernel allocates NEW Physical Page 200, copies Page 100 contents into it 3. Updates Child Page Table to point to Page 200 with READ-WRITE permissions 4. Resumes child execution transparently

3. exec() Family

Replaces the current process's text, data, heap, and stack segments with a completely new binary from disk (e.g., execvp("ls", args)). The PID does NOT change.

4. wait() / waitpid()

Suspends the parent process until one of its children terminates, collecting the child's exit status code and allowing the kernel to free the child's PCB.


9. Zombie and Orphan Processes

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ZOMBIE PROCESS β”‚ β”‚ β€’ A child process that has finished execution (`exit()`), but its parent has NOT yet β”‚ β”‚ called `wait()` to read its exit status. β”‚ β”‚ β€’ State: `Z` (Zombie / Defunct). β”‚ β”‚ β€’ Memory: Releases text/data/heap/stack, but KEEPS its entry in the Kernel PCB table. β”‚ β”‚ β€’ Danger: If a server leaks millions of zombies, it exhausts the system PID pool β”‚ β”‚ (`/proc/sys/kernel/pid_max`), preventing any new processes from spawning! β”‚ β”‚ β€’ How to Kill: You CANNOT kill a zombie with `kill -9` (it is already dead). β”‚ β”‚ You must kill its parent or make the parent call `wait()`. β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ ORPHAN PROCESS β”‚ β”‚ β€’ A child process whose parent terminated or crashed before the child finished. β”‚ β”‚ β€’ What happens: The OS Kernel automatically reparents the orphan to **PID 1** β”‚ β”‚ (`systemd` or `init`). β”‚ β”‚ β€’ PID 1 periodically calls `wait()` on all its adopted children, cleaning up their β”‚ β”‚ PCBs automatically when they exit. β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Reaping Zombies in C (The SIGCHLD Signal Handler):

#include <signal.h> #include <sys/wait.h> void sigchld_handler(int s) { // Non-blocking wait for all dead children while (waitpid(-1, NULL, WNOHANG) > 0); } int main() { struct sigaction sa; sa.sa_handler = sigchld_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; sigaction(SIGCHLD, &sa, NULL); // Now parent can fork freely without creating zombies! }

10. Inter-Process Communication (IPC) Mechanisms

Because processes run in strictly isolated address spaces, they cannot directly read or write each other's memory. The OS provides IPC mechanisms:

IPC TAXONOMY β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β–Ό β–Ό β–Ό DATA TRANSFER SHARED MEMORY SYNCHRONIZATION β€’ Anonymous Pipes β€’ POSIX Shared Memory β€’ Semaphores β€’ Named Pipes (FIFOs) (`shm_open`, `mmap`) β€’ File Locks (`flock`) β€’ Message Queues β€’ System V `shmget` β€’ Mutexes / Futexes β€’ Sockets (TCP/Unix Domain)

Comparison of IPC Mechanisms

IPC MechanismScopeSpeedData FormatKernel Involvement per I/O
Anonymous PipeRelated processes (Parent-Child)FastByte stream (Unidirectional)Yes (Syscalls read/write)
Named Pipe (FIFO)Any processes on same hostFastByte stream (Half/Full duplex)Yes (Syscalls read/write)
Message QueueAny processes on same hostModerateStructured discrete messagesYes (msgsnd/msgrcv)
Shared MemoryAny processes on same hostBlazing FastRaw memory bufferNone (Direct RAM access after setup)
Unix Domain SocketProcesses on same hostFastStream or DatagramYes (Syscalls via socket API)
Network SocketsAcross different machinesNetwork-boundStream (TCP) / Packets (UDP)Yes (Full network stack)

1. Anonymous Pipe Code Example

int fd[2]; // fd[0] = Read end, fd[1] = Write end pipe(fd); if (fork() == 0) { close(fd[0]); // Child closes unused read end char msg[] = "Hello Parent!"; write(fd[1], msg, sizeof(msg)); close(fd[1]); } else { close(fd[1]); // Parent closes unused write end char buf[128]; read(fd[0], buf, sizeof(buf)); printf("Parent received: %s\n", buf); close(fd[0]); }

11. Common Mistakes & Gotchas

[!CAUTION]

1. Fork Bombs and Uncontrolled Recursion

Calling fork() in a loop without proper termination (while(1) fork();) will exponentially spawn $2^N$ processes, instantly crashing the system by exhausting the process table and RAM.

[!WARNING]

2. Forgetting to Close Pipe Ends

If the parent process does not close its unused write-end of a pipe (close(fd[1])), a child reading from fd[0] will hang forever, because read() only returns EOF (0 bytes) when ALL write descriptors across the entire system are closed!

[!NOTE]

3. Confusing Thread Stacks with Heap

Thread stacks are isolated (each thread has its own %RSP), but because threads share a single address space, a thread can technically write to another thread's stack if it obtains a raw pointer to it. This leads to silent memory corruption bugs.


12. Practice Problems with Solutions

Problem 1: fork() Execution Counting

Question: How many times will the string "Hello" be printed by the following code?

#include <stdio.h> #include <unistd.h> int main() { fork(); fork(); fork(); printf("Hello\n"); return 0; }

Solution:

  • Initial state: $1$ process ($P_0$).
  • 1st fork(): Creates $1$ child $\rightarrow$ Total = $2$ processes.
  • 2nd fork(): Both processes fork $\rightarrow$ Total = $4$ processes.
  • 3rd fork(): All $4$ processes fork $\rightarrow$ Total = $8$ processes ($2^3$).
  • Each of the $8$ concurrent processes reaches printf("Hello\n").
  • Answer: "Hello" is printed 8 times.
  • General Formula: For $n$ consecutive fork() calls, total processes created $= 2^n$. Number of child processes $= 2^n - 1$.

Problem 2: Process State Identification

Question: Trace the process state transitions for the following scenario:

  1. Process is launched from terminal.
  2. Process calls read(fd, buffer, 1024) from a hard drive file.
  3. Disk controller completes data read into RAM buffer.
  4. Process time slice expires during loop computation.
  5. Process finishes execution.

Solution:

  1. $\text{NEW} \rightarrow \text{READY}$ (Admitted to memory).
  2. $\text{READY} \rightarrow \text{RUNNING} \rightarrow \text{WAITING / BLOCKED}$ (Voluntarily yields CPU waiting for disk I/O).
  3. $\text{WAITING} \rightarrow \text{READY}$ (Disk interrupt fires, ISR marks process ready in scheduler queue).
  4. $\text{READY} \rightarrow \text{RUNNING} \rightarrow \text{READY}$ (Preempted by timer interrupt; moved to ready queue).
  5. $\text{RUNNING} \rightarrow \text{TERMINATED}$ (Calls exit()).

13. High-Yield Interview Questions & Answers

Q1: What happens during a fork() call under Copy-On-Write (COW)?

Answer:

  1. The kernel allocates a new PCB (task_struct) and assigns a unique PID to the child.
  2. The kernel copies the parent's page table entries to the child rather than duplicating physical memory pages.
  3. Both parent and child page table entries are marked as Read-Only.
  4. If either process attempts to modify a page, a hardware page fault exception is raised.
  5. The kernel handles the page fault by allocating a new physical page frame, copying the original data, and mapping the newly allocated page with write permissions to the modifying process.

Q2: What is the difference between a Zombie process and an Orphan process? Which one is dangerous and why?

Answer:

  • A Zombie has completed execution, but its parent has not yet invoked wait(). Its memory is freed, but its PCB slot and PID remain allocated in the kernel.
  • An Orphan is still executing, but its original parent has died. It is automatically adopted by PID 1 (systemd/init).
  • Danger: Zombies are dangerous if leaked in large quantities because the OS has a finite PID capacity (pid_max). If the PID table is exhausted, the OS cannot spawn any new processes, halting system daemons and new incoming user requests.

Q3: Why is Context Switching between Threads faster than between Processes?

Answer:

  1. No Memory Space Switch: Threads of the same process share the same virtual address space. The CPU does not need to reload the page table base register (%CR3).
  2. TLB Preservation: Because %CR3 is not changed, the Translation Lookaside Buffer (TLB) is not flushed, preserving cached virtual-to-physical address translations.
  3. CPU Cache Warmth: L1/L2 data and instruction caches remain valid for the next thread if it accesses common process heap and text segments.

Q4: What are the primary IPC mechanisms in Linux, and which is the fastest?

Answer: The primary mechanisms are Anonymous Pipes, Named Pipes (FIFOs), Message Queues, Unix Domain Sockets, Network Sockets, and Shared Memory.
Shared Memory (shm_open/mmap) is the fastest because once the memory region is mapped into the virtual address spaces of both processes, data reads and writes occur directly via CPU memory bus instructions without crossing into kernel mode or performing buffer copies.


Q5: If a multi-threaded process calls fork(), what gets copied into the child process?

Answer: Under standard POSIX semantics, fork() in a multi-threaded program copies the address space, but only the single thread that invoked fork() is duplicated in the child. All other threads vanish. This is extremely hazardous if another thread was holding a mutex lock at the moment of fork(), because that lock will remain permanently locked in the child process without an owner, causing immediate deadlocks! For this reason, fork() in multi-threaded programs should almost always be immediately followed by exec().


Proceed to Module 03: CPU Scheduling Algorithms!