Run cat /proc/self/maps and you’ll see your process’s memory laid out: code near
the bottom, heap above it, stack near the top, libraries scattered between. Run it
twice and the addresses differ (ASLR), but the shape is the same for every process
on the machine — and every one of them believes it owns addresses like
0x7f8a2c000000. They can’t all own it. None of them do. Every address your code
ever touches is virtual, and the kernel plus the MMU translate each one to wherever
the data physically lives — RAM, disk, or nowhere at all.
The Mapping Is Per-Page
Translating arbitrary bytes would need a translation entry per byte. Instead, memory is managed in pages — 4 KB on most systems — and the kernel keeps a per-process page table mapping virtual pages to physical page frames:
virtual page 0x7f8a2c000 ──▶ physical frame 0x1a2f3 (in RAM)
virtual page 0x7f8a2c001 ──▶ physical frame 0x00b71 (in RAM)
virtual page 0x7f8a2c002 ──▶ (not present — on disk, or never touched)The MMU walks this table in hardware on every access. Two consequences fall out immediately: physical memory doesn’t need to be contiguous (your “contiguous” 1 GB buffer is confetti scattered across RAM), and two processes can map the same physical frame — which is how shared libraries exist in memory exactly once.
The Page Fault Is a Feature
Touch a page whose entry says “not present” and the MMU raises a page fault. This sounds like an error; it’s actually the kernel’s main memory-management mechanism working as designed:
- Demand paging.
malloc(1 << 30)returns instantly because nothing was allocated — the kernel just recorded a reservation. Physical frames are assigned one at a time, at first touch, via page faults. This is why a program can “allocate” far more than it uses. - Copy-on-write.
fork()doesn’t copy the parent’s memory. Both processes map the same frames, marked read-only. Whoever writes first faults, and only that page gets copied. A 10 GB process forks in microseconds. - Swap and file mappings. A page evicted to disk, or an
mmaped file page not yet read, is just “not present.” The fault handler reads it back in and the faulting instruction retries, none the wiser.
A fault serviced from RAM (a “minor” fault) costs microseconds. One that goes to
disk (“major” fault) costs milliseconds — five orders of magnitude above a cache
hit. ps won’t show you this; perf stat -e page-faults,major-faults will.
Why Your Memory Numbers Never Agree
Virtual memory is why “how much memory is this process using” has no single answer:
- VSZ (virtual size): everything mapped — reservations, files, untouched
mallocs. Nearly meaningless as a cost measure. - RSS (resident set size): pages actually in RAM right now. Closer, but shared library pages are counted fully in every process using them.
- PSS (proportional set size, in
/proc/<pid>/smaps_rollup): shared pages divided among their users. The honest number for “what does killing this free.”
A Chrome tab showing 300 MB RSS may cost 80 MB PSS. Capacity planning off RSS systematically overcounts.
Protection Comes Free
Each page table entry carries permission bits — readable, writable, executable.
The MMU checks them on every access, which is where segfaults actually come from:
a segfault is a page fault the kernel couldn’t or wouldn’t satisfy (unmapped
address, write to read-only page). It’s also the mechanism behind W^X (no page
both writable and executable), guard pages that catch stack overflows, and
mprotect-based tricks like garbage collector write barriers.
The translation itself has a cost — every load needs a page-table lookup — and the TLB, the cache that makes this survivable, deserves its own post. But the mental model to keep is this: memory in a modern OS is not a thing you have, it’s a set of promises the kernel keeps lazily, one page fault at a time.
Takeaways
- Every address in user code is virtual; the kernel + MMU translate per 4 KB page.
- Page faults aren’t errors — they implement demand paging, copy-on-write fork, swap, and mmap.
mallocreserves; first touch allocates. Major faults (disk) cost ~1000× minor.- VSZ overcounts wildly, RSS double-counts shared pages, PSS tells the truth.
- Segfaults are page faults with no legitimate resolution — permissions live in the page table too.