The Problem Virtual Memory Solves
Without virtual memory, every process would need to coordinate physical RAM addresses with every other process — a nightmare for isolation and allocation. Virtual memory gives each process the illusion that it owns the entire address space (0 to 2⁶⁴ on x86-64), and the hardware + OS cooperate to make that illusion fast.
Pages and the Page Table
Physical RAM is divided into fixed-size pages (typically 4KB on x86-64, 16KB on Apple Silicon). Virtual addresses are also page-sized chunks. The page table is the mapping from virtual page numbers to physical page frames.
Virtual address: 0x0000_7fff_abcd_1234
├── VPN (virtual page number) = 0x7fff_abcd_1
└── offset within page = 0x234
Page table lookup: VPN → PFN (physical frame number)
Physical address: PFN × 4096 + 0x234
x86-64 uses a 4-level (or 5-level) page table. The CPU’s MMU walks all four levels on a miss. This would be catastrophically slow if done for every memory access — hence the TLB.
TLB — Translation Lookaside Buffer
The TLB is a small, fast hardware cache of recent virtual→physical mappings (typically 64–1024 entries). On a TLB hit, address translation is ~1 cycle. On a TLB miss, the MMU walks the page table (~50–100 cycles). On a page fault (page not in RAM), the OS must load it from disk — microseconds to milliseconds.
TLB shootdowns are why process context switches are expensive: switching the page table (CR3 register) flushes the entire TLB.
Demand Paging and Overcommit
The OS doesn’t allocate physical frames when you call malloc. It allocates virtual pages. Physical frames are assigned on first access (the page fault handler allocates a frame and maps it). This is demand paging.
Linux overcommits by default: you can malloc(100GB) on a 16GB machine. The kernel bets you won’t actually touch all of it. If you do, the OOM killer starts terminating processes.
Copy-on-Write (CoW)
When fork() is called, the OS doesn’t copy all physical pages. Both parent and child point to the same physical frames, marked read-only. On the first write, a fault fires, the page is copied, and both processes get their own copy. This makes fork() + exec() cheap — exec() replaces the image before most pages are ever written.
Memory Mapping
mmap() maps a file (or anonymous memory) directly into the virtual address space. Reading from a mapped file page-faults in the data; writing to it marks the page dirty and the OS flushes it to disk later. Databases use this extensively — SQLite, LMDB, and many others memory-map their data files.
Practical Implications
- Stack overflow is a page fault on the guard page below the stack. The OS maps a non-readable page at the stack boundary.
- Shared libraries (.so/.dylib) are mapped once into physical memory but appear in many processes’ virtual spaces — the same physical pages mapped read-only into different virtual addresses.
- Huge pages (2MB or 1GB) reduce TLB pressure for large working sets. Databases and ML frameworks use them explicitly.