Aprelius logo
uptime: 00:00:00
Computer Science

Virtual memory and address space

Virtual memory is how OS lets multiple programs to have their own private address space, making every program feel like they own whole machine, even though the underlying data may be scattered across physical RAM, or if swapped - on the disk. The OS gives each process full virtual address range and maintains page tables that the MMU uses to translate virtual to physical addresses on every memory access.

Program Memory Layout

When a program runs, the OS lays out regions in its virtual address space:

text
High addresses
┌─────────────────┐
│     Stack       │ ← grows downward, function calls & local vars
├─────────────────┤
│       ↓         │
│    (free)       │ ← collision here causes stack overflow
│       ↑         │
├─────────────────┤
│      Heap       │ ← grows upward, dynamic allocation (malloc/new)
├─────────────────┤
│  .data/.bss     │ ← global/static variables (read-write)
├─────────────────┤
│    .rodata      │ ← constants, string literals (read-only)
├─────────────────┤
│   .text (code)  │ ← program instructions
└─────────────────┘
Low addresses

The stack grows down from high addresses and the heap grows up from low ones. When they meet in the middle there's no room left:

  • Stack overflow → program crashes
  • Heap allocation fails → malloc returns NULL or throws an exception
  • OS does not automatically relocate the program (would break all pointers)

Understanding Memory Addresses

Memory addresses are just numbers (byte locations), not literal bit patterns. Address arithmetic is plain number math:

text
0xffffd5cc - 0xffffd5c8 = 0x4 = 4 bytes

The hex/binary representation is just a display format for the byte address number:

  • 0xd0 (hex) = 208 (decimal) = 11010000 (binary) — all refer to the same byte location
  • Subtracting addresses gives the distance in bytes, regardless of display format

The CPU does this arithmetic with the LEA instruction, which computes addresses without touching memory.

Virtual Memory

Addresses like 0xffffd5cc are virtual addresses handed out by the OS, not physical RAM locations. It's a layer of abstraction between programs and physical RAM that:

  • Gives each process its own isolated address space
  • Lets every program see a full, contiguous address space (e.g. 0x0 to 0x7FFF... on 64-bit)
  • Gets translated to physical memory by the MMU
  • Allows address arithmetic: address + 4 bytes = new_address
  • Lets multiple programs use the same virtual address

Why doing this? Four main benefits:

  1. Isolation: programs cannot access each other's memory (security sandbox)
  2. Simplification: every program thinks it has the full memory space
  3. Overcommitment: total virtual memory can exceed physical RAM
  4. Protection: OS can mark pages as read-only, executable, no-access

Similar to the NAT concept in networking:

  • Virtual addresses = private IPs (e.g. 10.10.10.10)
  • Physical addresses = public IPs (e.g. 148.102.13.177)
  • Page tables = NAT router

Page Tables

Data structures mapping virtual pages (typically 4KB chunks) to physical pages. The MMU uses these for address translation.

Lazy Allocation

OS optimization technique:

c
char* buffer = malloc(1000000);  // Returns pointer immediately
                                 // Physical RAM not yet allocated!
buffer[0] = 'A';                 // Page fault triggers here
                                 // OS allocates physical page NOW

Programs often allocate more than they use, so the OS only commits physical memory when it's actually accessed. The first touch is where the real allocation happens.

Swapping/Paging

When physical RAM is full:

  1. OS writes rarely-used pages to disk (swap space)
  2. Frees physical RAM for new allocations
  3. If swapped pages are accessed later, reads them back from disk

Swapping is a trade of speed for capacity, and how bad it hurts depends on the access pattern: On HDD, the penalty is brutal — sequential throughput might be in the hundreds of MB/s, but random reads can collapse to single-digit MB/s, a gap that can reach ~100,000x at the extreme (seek time dominates: 5-15ms per random access) [1]. On modern NVMe SSD, the gap narrows drastically — sequential reads hit several GB/s, while random 4K reads land in the tens of MB/s, more like a 50-100x gap rather than five orders of magnitude, since flash has no mechanical seek penalty [2]. Latency-wise, NVMe random access is ~20-70µs versus HDD's 5-10ms — roughly 100-200x faster [3].

References