Memory Management

Virtual memory gives every process its own private address space (0 to 2^48 on x86-64). The MMU (Memory Management Unit) translates virtual addresses to physical frame addresses using page tables, with a TLB cache for speed.

Why It Matters

Virtual memory enables process isolation (one process can’t read another’s memory), lets you run programs larger than physical RAM (demand paging), and makes fork() fast (copy-on-write). Understanding it explains segfaults, mmap behavior, and why your process shows 2GB RSS but the system is fine.

Virtual → Physical Translation

Virtual address (48 bits on x86-64):
┌─────┬─────┬─────┬─────┬────────────┐
│PML4 │PDPT │ PD  │ PT  │  Offset    │
│ 9b  │ 9b  │ 9b  │ 9b  │   12b      │
└──┬──┴──┬──┴──┬──┴──┬──┴─────┬──────┘
   │     │     │     │        │
   └──→ 4-level page table walk
         │
         ↓
   Physical frame number + Offset = Physical address

Each level is a 512-entry table (9 bits = 512 entries). Leaf entries contain the physical frame number plus permission bits (read/write/execute/user).

TLB (Translation Lookaside Buffer)

A hardware cache of recent virtual→physical translations. TLB hit = ~1 cycle. TLB miss = page table walk (~10-100 cycles). TLB flush on context switch is one reason switches are expensive.

perf stat -e dTLB-loads,dTLB-load-misses ./program  # measure TLB misses

Page Faults

When the CPU accesses a virtual page with no valid mapping:

Fault TypeCauseKernel Action
MinorPage allocated but not yet mapped (first access to mmap’d region)Map a zero page, no IO
MajorPage swapped to diskRead from swap, expensive
InvalidAccess to unmapped addressSIGSEGV → segfault
/usr/bin/time -v ./program 2>&1 | grep "page faults"
# Minor (reclaiming):   1234
# Major (requiring I/O): 0

Copy-on-Write (COW)

fork() doesn’t physically copy memory. Parent and child share all pages, marked read-only. On the first write:

  1. CPU raises a (minor) page fault — page is read-only
  2. Kernel sees it’s a COW page
  3. Kernel copies just that one page (4KB)
  4. Both processes get their own writable copy

This is why fork() is O(page table size), not O(memory size).

Key Memory Regions

High addresses
┌────────────────────┐
│ Kernel space        │  [not accessible from user mode]
├────────────────────┤  0x7FFF...
│ Stack ↓            │  grows downward, RLIMIT_STACK (8MB default)
│                    │
│ mmap region ↓      │  shared libraries, mmap'd files
│                    │
│ Heap ↑             │  grows upward via brk/sbrk
├────────────────────┤
│ BSS                │  uninitialized globals (zeroed)
├────────────────────┤
│ Data               │  initialized globals
├────────────────────┤
│ Text (r-x)         │  code, read-only + execute
└────────────────────┘
Low addresses

Inspect live: cat /proc/PID/maps shows every mapped region with permissions.

$ cat /proc/self/maps
00400000-00401000 r-xp  program (text)
00601000-00602000 rw-p  program (data)
7f8a1000-7f8c3000 r-xp  /lib/libc.so.6
7ffd4000-7ffd6000 rw-p  [stack]

Swapping and Page Replacement

When physical memory is full, the kernel evicts pages to swap (disk). Replacement policies:

AlgorithmHowNotes
LRUEvict least recently usedIdeal but expensive to track exactly
Clock (second chance)Circular list with reference bitApproximation of LRU, used in practice
LFUEvict least frequently usedGood for some workloads

Linux uses a two-list approach: active and inactive page lists with aging.

free -h                    # check swap usage
swapon --show              # list swap devices
vmstat 1                   # watch page-in/page-out in real time