The grand illusion behind process isolation
Three lessons so far have leaned on one promise: each process gets private memory and cannot touch anyone else's, in lessons 1-3, 3-1, and 4-1. This lesson shows the machinery that keeps the promise.
You will meet its symptoms directly, in a machine that crawls to a halt when RAM runs out, the word segfault in a crash log, and memory dashboards showing virtual sizes far bigger than the RAM installed.
The trick is called virtual memory. A process never sees real RAM addresses. Every address in a program, meaning every variable, every stack frame, and every heap object, is a virtual address, a made-up number in the process's own private numbering that starts at zero.
The OS keeps a page table per process, a map from the process's virtual addresses to the physical RAM locations actually holding the data. The CPU translates every single memory access through that map, in hardware, automatically.
To keep the map small, memory is managed in fixed-size chunks called pages, typically 4 KB. The table says that virtual page 0 lives in physical frame 7, virtual page 1 lives in physical frame 2, and so on. Two consequences fall out immediately:
- Isolation for free. Process A's page table simply contains no entry pointing at process B's frames, so A cannot even name B's memory. That is the enforcement mechanism behind everything unit 3 promised.
- Two processes can use the same virtual address for different data, which is why every process can believe it owns a clean, empty address space starting at zero.
A page table you can read
Virtual addresses split into a page number, which chunk, and an offset, the position inside that chunk.
PAGE_SIZE = 100 page_table = {0: 700, 1: 300, 2: 900} def translate(virtual_address): page = virtual_address // PAGE_SIZE offset = virtual_address % PAGE_SIZE return page_table[page] + offset for addr in [0, 42, 150, 250]: print("virtual", addr, "-> physical", translate(addr))
Output
virtual 0 -> physical 700 virtual 42 -> physical 742 virtual 150 -> physical 350 virtual 250 -> physical 950
Virtual address 150 is page 1, from 150 // 100, at offset 50, from 150 % 100. Page 1 maps to physical 300, so the answer is 350.
The virtual addresses are neat and contiguous while the physical locations are scattered, and the process never knows the difference. Real hardware performs exactly this split, with 4096-byte pages and in silicon, on every memory access.
Page faults and swapping, when the map has a hole
A process can touch a virtual page with no entry in the table. The hardware then stops mid-instruction and hands control to the kernel, and that event is a page fault. The kernel decides what it means.
Legal but not loaded. The page exists but its data is currently on disk. The kernel loads it into a free frame, updates the table, and resumes the process as if nothing happened. The process never notices beyond the time it took.
Illegal. The process had no right to that address, so the kernel kills it. This is the segmentation fault from lesson 4-2, finally with its full mechanism visible: a page-table lookup that came up empty-handed.
| Fault kind | Kernel response |
|---|---|
| page is on disk | load it, update the table, resume |
| page is not the process's at all | terminate with a segfault |
The on-disk case enables swapping. When RAM fills up, the kernel evicts pages that have not been used recently to a reserved area of disk called swap space, freeing frames for whoever needs them now.
That is why a machine out of RAM does not instantly die but instead crawls. Pages keep faulting back and forth between RAM and a disk that is a thousand times slower, which is lesson 1-1's ratios turned into a symptom. Engineers call the worst version thrashing, where the machine spends all its time moving pages and none doing work.
When a dashboard shows a process's virtual size far above physical RAM, virtual memory is what makes that legal.
Teaching the translator about page faults
A missing page becomes a reported fault instead of a KeyError.
PAGE_SIZE = 100 page_table = {0: 700, 1: 300, 2: 900} def translate(virtual_address): page = virtual_address // PAGE_SIZE offset = virtual_address % PAGE_SIZE if page not in page_table: return "page fault: page " + str(page) + " is not in RAM" return "physical " + str(page_table[page] + offset) print(translate(42)) print(translate(340))
Output
physical 742 page fault: page 3 is not in RAM
Virtual address 340 is page 3, and the table only holds pages 0, 1, and 2, so the membership check catches it. str(page) puts the number into the message.
Real hardware behaves the same way at this point, with one important difference. The check is not an if in software, it is a hardware lookup that traps into the kernel, which is why a fault costs a context switch rather than a comparison.
The same address in two processes
Processes A and B can both store a variable at address 5000 with no collision, because 5000 is a virtual address in each process's private numbering and each page table maps it to a different physical frame.
Virtual addresses are per-process fictions. Each table translates its own address 5000 to a different physical frame, so the same number names different real memory.
| Process | Virtual address | Physical frame |
|---|---|---|
| A | 5000 | one frame |
| B | 5000 | a different frame |
This is also exactly why process isolation holds. A's table simply has no path to B's frames, so the isolation is not a check that could be bypassed, it is an absence of any route at all.
Naming the eviction of cold pages to disk
Moving not-recently-used pages out to a reserved area of disk to free frames is called swapping, also described as paging out.
The kernel evicts cold pages to swap space on disk and reloads them on a page fault. It keeps the machine alive past physical RAM, at disk speed, which is roughly a thousand times slower.
| Term | Refers to |
|---|---|
| swap space | the reserved disk area |
| swapping or paging out | evicting pages into it |
| thrashing | swapping so heavily that no work gets done |
The disk-speed penalty is why heavy swapping feels like the computer is wading through mud, and it is why a machine out of RAM crawls instead of dying outright.