What is virtual memory and how do pages, page tables, and the TLB work?
Virtual memory gives each process the illusion of a large, contiguous address space, even though physical RAM is finite and shared.
Pages: Virtual memory is divided into fixed-size blocks called pages (typically 4KB on x86). Physical memory is divided into page frames of the same size. The OS maps virtual pages to physical frames.
Page table: A per-process data structure that maps virtual page numbers to physical frame numbers. On 64-bit systems, page tables are multi-level (4-level on x86_64) to avoid storing entries for unmapped regions.
Each page table entry (PTE) contains:
- Physical frame number
- Present/valid bit (is the page in RAM?)
- Read/Write/Execute permission bits
- Dirty bit (has the page been written to?)
- Accessed bit (has it been read?)
TLB (Translation Lookaside Buffer): A small, fast hardware cache of recent page table lookups. Without TLB, every memory access would require 4 page table lookups (on x86_64). TLB hit rate is typically >99% due to locality of reference.
Page fault: When a virtual page is not in RAM (present bit = 0), the MMU raises a page fault. The OS loads the page from disk (swap) into RAM, updates the page table, and retransmits the instruction. This is transparent to the process.
Benefits: Process isolation (each process has its own address space), memory overcommit (more virtual memory than physical RAM), demand paging (load pages only when accessed), shared libraries (same physical page mapped into multiple processes).
Virtual address -> MMU:
1. Split address: [VPN (virtual page number)] [offset]
2. Check TLB for VPN:
- TLB hit: get physical frame number instantly
- TLB miss: walk the page table (4 levels on x86_64)
3. If PTE present bit = 0: page fault
- OS loads page from disk to RAM
- Updates PTE and TLB
4. Physical address = [frame number][offset]
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
/* Each process sees its own virtual address space */
int x = 42;
printf("Virtual address of x: %p\n", (void *)&x);
/* Same virtual address in different processes
maps to different physical frames */
/* Page size is typically 4096 bytes */
long page_size = sysconf(_SC_PAGESIZE);
printf("Page size: %ld bytes\n", page_size); /* 4096 */
/* Total physical pages */
long phys_pages = sysconf(_SC_PHYS_PAGES);
printf("Physical memory: %ld MB\n",
(phys_pages * page_size) / (1024 * 1024));
/* Allocate memory -- pages are NOT allocated until accessed */
/* (demand paging) */
size_t size = 100 * 1024 * 1024; /* 100 MB */
char *p = malloc(size);
if (!p) { perror("malloc"); return 1; }
/* At this point, no physical pages are allocated.
Only virtual address space is reserved. */
/* Touching a page triggers a page fault,
which allocates a physical frame */
p[0] = 'A'; /* page fault -> allocate 1 page */
p[page_size] = 'B'; /* page fault -> allocate another page */
p[2 * page_size] = 'C'; /* page fault -> allocate another page */
/* Only 3 physical pages used, not 100MB worth */
free(p);
return 0;
}
- •Virtual memory maps virtual pages to physical frames via page tables
- •Pages are typically 4KB; x86_64 uses 4-level page tables
- •TLB caches recent translations for fast address lookup (>99% hit rate)
- •Page fault occurs when a page is not in RAM -- OS loads it transparently
- •Demand paging: physical memory is not allocated until a page is first accessed
- •malloc does not allocate physical memory -- only virtual address space is reserved until first access
- •TLB is per-CPU and flushed on context switch (or per-ASID on modern CPUs) -- frequent switches hurt performance
- •Huge pages (2MB/1GB) reduce TLB misses for large allocations but increase internal fragmentation
- •Virtual address space is much larger than physical RAM -- processes can map more than available memory