01-process-structure-and-lifecycle.md

🧠 OS 01: Process Structure, Memory Layout & Lifecycle

Targeted for: Juspay Elimination Round, Hackathon Part B & Systems Engineering Interviews
Key Focus: Process memory segments, Stack vs Heap internals, PCB structure, Process/Thread State Transitions, and Zombie/Orphan processes.


šŸ“Œ 1. Process Memory Layout (Virtual Address Space)

When a program (executable file stored on disk) is loaded into memory by the OS loader (execve on Linux), it becomes an active Process. The OS provides every 32-bit (4GB) or 64-bit (128TB virtual user space) process with its own private Virtual Address Space.

High Memory (0xFFFFFFFF / 0x7FFFFFFFFFFF) ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ Kernel Space (Protected) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ Command Line Arguments │ │ & Environment Variables (envp) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ STACK │ │ (Function frames, local variables, return addrs) │ │ │ Grows DOWN │ │ ā–¼ │ │ │ │ ā–² │ │ │ Grows UP │ │ HEAP │ │ (Dynamic allocations: malloc, calloc, new, etc.) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ BSS Segment (Uninitialized) │ │ (Static & Global variables initialized to 0) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ Data Segment (Initialized) │ │ (Static & Global variables with explicit values) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ Text / Code Segment (RO) │ │ (Compiled binary machine instructions) │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ Low Memory (0x00000000)

Segment Breakdown

SegmentMemory TypePermissionsGrowth DirectionContents & Lifetime
Text / CodeFixed sizeRead-Only, Execute (r-x)NoneCompiled binary instructions. Shared among multiple instances of the same executable (e.g., multiple bash processes share one Text segment in physical RAM). Read-only to prevent self-modifying code vulnerabilities.
Initialized Data (.data)Fixed sizeRead-Write (rw-)NoneGlobal, static, and constant variables with explicit non-zero initial values (e.g., static int counter = 10;, int global_flag = 1;). Allocated at compile time, persists for entire program lifecycle.
Uninitialized Data (.bss)Fixed sizeRead-Write (rw-)NoneBlock Started by Symbol. Global and static variables that are uninitialized or initialized to zero (e.g., static int buffer[1024];, int zero_var = 0;). In the ELF binary on disk, .bss takes 0 bytes of storage; the kernel zero-fills this region upon process creation.
HeapDynamic sizeRead-Write (rw-)Grows UP (toward higher addresses)Runtime dynamic memory allocation (malloc, calloc, realloc in C; new in C++/Java). Managed by user-space runtime allocators (glibc ptmalloc, jemalloc, tcmalloc) via OS system calls (brk, sbrk, mmap). Prone to memory leaks and fragmentation.
StackDynamic sizeRead-Write (rw-)Grows DOWN (toward lower addresses on x86/ARM)Stores Stack Frames (Activation Records): function parameters, return addresses, saved frame pointers (RBP/EBP), and local (auto) variables. Allocated and deallocated automatically on function call (call / push) and return (ret / pop). Fast allocation (just shifting RSP register).

šŸ” 2. Deep Dive: Stack vs Heap Internals

The Stack Frame (Activation Record) Anatomy

When a function foo(a, b) is invoked, the CPU pushes a stack frame onto the process stack:

Higher Memory Addresses ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ Function Argument 2 │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ Function Argument 1 │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ Return Instruction PTR │ <-- Where to jump after function ends (RIP) ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ Saved Base Pointer │ <-- Previous frame's RBP ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ <-- Current RBP points here │ Local Variable 1 │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ Local Variable 2 │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ <-- Current RSP (Stack Pointer) points here Lower Memory Addresses
  • Stack Overflow: Occurs when recursion is too deep or massive local arrays (e.g., int arr[10000000];) are declared on the stack, pushing RSP past the stack boundary (typically 8MB on Linux, ulimit -s) into an unmapped page or the Heap guard page, triggering a SIGSEGV (Segmentation Fault).

Dynamic Memory Allocation (Heap Management)

  • Small Allocations ($< 128\text{ KB}$): Managed by expanding the brk pointer using the sbrk() system call. The heap is a contiguous chunk of virtual memory.
  • Large Allocations ($\ge 128\text{ KB}$): Allocated outside the main heap using mmap(MAP_ANONYMOUS | MAP_PRIVATE), creating an isolated memory-mapped page region. When free() is called on an mmap allocation, memory is returned immediately to the OS kernel.
  • Heap Fragmentation:
    • Internal Fragmentation: Memory allocated to a block is slightly larger than requested (e.g., allocator aligns 13 bytes to 16 bytes).
    • External Fragmentation: Free blocks are scattered throughout the heap; total free memory is sufficient, but no single contiguous block is large enough for the requested allocation.

šŸ—‚ļø 3. Process Control Block (PCB)

The PCB (in Linux kernel: struct task_struct in <linux/sched.h>) is the data structure maintained by the OS kernel for every active process. It is stored in protected kernel space.

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ Process Control Block (PCB) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ 1. Process Identifier (PID, PPID, TGID, UID, GID) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ 2. Process State (RUNNING, INTERRUPTIBLE, STOPPED) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ 3. Program Counter (PC / RIP) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ 4. CPU Registers (RAX, RBX, RCX, RDX, RSP, RBP, etc.) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ 5. CPU Scheduling Info (Priority, Nice, vruntime) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ 6. Memory Management Info (Page Table Base / CR3, │ │ Virtual Memory Areas - struct mm_struct) │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ 7. I/O & File Descriptor Table (struct files_struct) │ │ FD 0: stdin, FD 1: stdout, FD 2: stderr, sockets...│ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ 8. Signal Handlers & Pending Signals │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ │ 9. Accounting & Resource Limits (CPU time, max mem) │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

[!IMPORTANT] Linux Thread Unification: In Linux, there is no separate "Thread Control Block" entity in the kernel. Both processes and threads are represented by struct task_struct. Threads created via clone() share the same mm_struct (memory), files_struct (file descriptors), and signal_struct, while having distinct PID/TID and registers.


šŸ”„ 4. Process State Lifecycle

A process undergoes 5 classic state transitions throughout its lifetime:

stateDiagram-v2 [*] --> NEW : fork() / execve() NEW --> READY : Admitted by Long-term Scheduler READY --> RUNNING : Dispatched by Short-term Scheduler RUNNING --> READY : Time Quantum Expired (Timer Interrupt) / Preempted RUNNING --> WAITING : I/O Request / Sleep / Wait for Lock / Event WAITING --> READY : I/O Completed / Event Occurred RUNNING --> TERMINATED : exit() / Killed by Signal TERMINATED --> [*] : Parent calls wait() / waitpid()

Detailed State Transitions

  1. NEW (Created): The process is being created (memory allocated, PCB initialized, binary loaded), but has not yet joined the executable pool.
  2. READY / RUNNABLE: The process resides in main memory and is placed in the OS Ready Queue. It is waiting for the CPU scheduler to allocate a core.
  3. RUNNING: The CPU core is currently executing instructions from this process's code segment.
  4. WAITING / BLOCKED / SLEEPING: The process cannot execute even if CPU cores are idle. It is waiting for an external event:
    • I/O operation (disk read, network socket receive).
    • Synchronization primitive (acquiring a Mutex or Semaphore).
    • Explicit sleep timer (sleep(5)).
    • In Linux: TASK_INTERRUPTIBLE (woken up by signals) vs TASK_UNINTERRUPTIBLE (ignores signals, e.g., waiting for direct disk I/O; shows as D state in ps).
  5. TERMINATED (Zombie / Dead): Execution finished (exit(0)), resources (memory, file descriptors) are released, but the process entry remains in the process table so its parent can read its exit status.

šŸ’€ 5. Special Process Types: Zombie vs Orphan vs Daemon

1. Zombie Process (<defunct>)

  • Definition: A child process that has completed execution (exit()), but its parent has not yet called wait() or waitpid() to read its termination exit code.
  • Resource Usage: Consumes zero CPU and zero RAM (its address space is fully deallocated), but retains an entry in the OS Process Table and a PID.
  • Hazard: If a parent forks millions of children without calling wait(), it exhausts the OS process table / PID limit (/proc/sys/kernel/pid_max), preventing any new processes from spawning on the server.
  • How to Kill a Zombie: You cannot kill a zombie with kill -9 <PID> because it is already dead. You must:
    1. Send SIGCHLD to the parent to trigger wait().
    2. Kill the parent process (kill -9 <Parent_PID>), which turns the zombie into an Orphan adopted by init/systemd, which immediately reaps it.

2. Orphan Process

  • Definition: A running process whose parent process terminates before the child finishes.
  • Resolution: The OS automatically reparents the orphan process to PID 1 (init or systemd). When the child eventually finishes, PID 1 automatically calls wait() to reap it. Orphans do not cause system resource leaks.

3. Daemon Process

  • Definition: A background, non-interactive service process running indefinitely without an attached controlling terminal (e.g., sshd, nginx, mysqld, cron).
  • Creation Mechanism (Daemonization):
    1. fork() from the parent and have the parent exit() (ensures daemon is not a process group leader).
    2. Call setsid() to create a new session and detach from controlling TTY.
    3. Change working directory to root (chdir("/")) to avoid locking unmounted file systems.
    4. Reset file mode creation mask (umask(0)).
    5. Close standard file descriptors (FD 0, 1, 2) and redirect them to /dev/null.

šŸ’¬ 6. Verbatim Interview Scripts (Say This Out Loud)

Q: "Explain the difference between .data and .bss segments. Why does .bss exist?"

Verbatim Answer:
"Both .data and .bss store global and static variables. The key distinction lies in initialization and binary size optimization.

The .data segment contains global and static variables that are explicitly initialized with non-zero values (e.g., int x = 42;). The compiler must write the actual bytes 42 into the compiled binary on disk.

The .bss (Block Started by Symbol) segment holds global and static variables that are uninitialized or initialized to zero (e.g., int arr[100000];). If the binary stored 100,000 zeros on disk, executable files would be massive. Instead, the binary header simply records the size of the .bss section (400 KB). When the OS loader maps the process into memory, it allocates zero-filled pages on demand. This saves disk storage and network download time for executables."

Q: "What is a Zombie Process, how do you debug it, and how do you remove it?"

Verbatim Answer:
"A zombie process is a child process that has finished execution via exit(), but its PCB and PID remain in the kernel process table because its parent hasn't collected its exit status using wait() or waitpid().

A zombie uses no CPU or RAM, but it leaks PID table entries. You identify them using ps aux | grep 'Z' or looking for [defunct].

You cannot terminate a zombie using kill -9 because the process is already terminated. To clear it, you either signal the parent with SIGCHLD so it executes a wait handler, or you kill the parent process. Once the parent dies, the init process (PID 1 / systemd) adopts the zombie and immediately calls wait() to reap it."