Virtual memory, the lie every program believes
Open two programs. Both can use a pointer to address 0x400000, at the same time, and they don't collide. Each one believes it has a vast, private, contiguous memory all to itself, starting near zero. That belief is a lie, carefully maintained by the operating system and the CPU. It's also one of the most elegant ideas in computing, and once you see the mechanism it stops being magic.
The one idea: a layer of indirection
Programs use virtual addresses. Physical RAM uses physical addresses. In between sits a translation layer: every time a program touches memory, the CPU translates its virtual address to a real physical one, on the fly. The program never sees the real address.
That one indirection buys almost everything we like about modern computers:
- Isolation. Program A's address
0x400000and program B's0x400000translate to different physical RAM. They can't read or corrupt each other. - The illusion of contiguous, abundant memory, even though physical RAM is fragmented and shared among dozens of processes.
- Flexibility: memory can be moved, shared, or even paged out to disk, all without the program knowing.
How translation works: pages and page tables
You don't translate every byte individually, that'd need a table the size of memory. Instead, memory is chopped into fixed-size pages (commonly 4 KB). Translation maps a virtual page to a physical frame; everything within a page keeps its position.
A virtual address splits cleanly into two parts: the high bits are the page number (which page), the low bits are the offset (where inside the page). With 4 KB pages, the low 12 bits are the offset (because 2¹² = 4096), and the rest is the page number:
#include <stdio.h>
#define PAGE_BITS 12 // 4 KB pages: 2^12 = 4096
#define PAGE_SIZE (1u << PAGE_BITS)
unsigned page_table[256]; // virtual page -> physical frame
unsigned translate(unsigned vaddr) {
unsigned vpage = vaddr >> PAGE_BITS; // high bits: which page
unsigned offset = vaddr & (PAGE_SIZE - 1); // low bits: position in page
unsigned frame = page_table[vpage]; // the OS-maintained mapping
return (frame << PAGE_BITS) | offset; // physical address
}
That's the heart of virtual memory. The OS fills in page_table (which virtual page lives in which physical frame); the CPU runs this translation on every single memory access. Set up two processes with different page tables and the same virtual address lands in different frames, that's isolation, falling straight out of the math.
Two details that matter:
- The offset passes through untouched. Only the page number is translated; position within a page is preserved. That's why a page is the unit, you map 4096 bytes with one table entry.
- Each process has its own page table. Switching processes means switching page tables, which is precisely why two programs can share a virtual address with no conflict.
Why this is fast: the TLB
Translating on every access by reading a page table in RAM would be ruinously slow, every memory access would become two. So the CPU caches recent translations in a tiny, ultra-fast table called the TLB (Translation Lookaside Buffer). Most accesses hit the TLB and translate in essentially zero time; only a miss walks the page table. The whole scheme is fast because of locality, programs touch the same handful of pages over and over.
What a page fault really is
What if a virtual page isn't in physical RAM, because it was never loaded, or got moved to disk? The page table entry is marked "not present," and the CPU raises a page fault, handing control to the OS. The OS finds the data (load it from the executable, or from the swap file on disk), puts it in a free frame, updates the page table, and restarts the instruction. The program never knows it happened, it just sees memory that works.
This is the trick behind "more memory than you physically have": rarely used pages live on disk and get faulted back in on demand. Push it too far, constantly faulting pages in and out, and you get thrashing, the disk-bound crawl when you open too many tabs.
Why this is worth understanding
Virtual memory explains a dozen everyday mysteries: why a segfault happens (you touched a virtual address with no valid mapping), why two programs can't corrupt each other, why your machine slows to mud when RAM runs out, why memory looks contiguous when it physically isn't. All of it follows from one idea, a translation layer between the address a program uses and the byte in RAM, plus a table that defines the map.
And the mechanism is a shift and a lookup, you just wrote it. The OS that maintains those tables, handles the faults, and decides what to evict is the rest of the story, and that's exactly what you build in the operating systems track.