Memory Virtualization â Virtual Memory Guide
In this tutorial, you'll learn about Memory Virtualization. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Memory virtualization creates an abstraction of physical memory, giving each Process its own virtual address space and enabling efficient sharing, protection, and oversubscription of physical RAM.
What You'll Learn
In this tutorial, you'll learn how virtual memory works â virtual addresses, page tables, TLBs, demand paging, page replacement algorithms (LRU, FIFO, Clock), swap space, NUMA architecture, and memory-mapped files â with Python simulations for each concept.
Why It Matters
Every modern OS uses virtual memory. Without it, a buggy Process could overwrite another Process's memory, or a 4GB phone couldn't run multiple 2GB apps simultaneously. Understanding virtual memory helps you diagnose performance problems (page faults, thrashing), tune database memory settings, and write memory-efficient code. DodaTech's Durga Antivirus Pro uses memory scanning that must understand virtual memory layout to detect stealth malware.
Real-World Use
Databases like PostgreSQL tune shared_buffers based on virtual memory page sizes. Chrome uses memory-mapped files for fast loading. Kubernetes memory limits rely on the OS's virtual memory overcommit handling. Redis uses virtual memory to store datasets larger than RAM.
flowchart LR
subgraph "Virtual Address Space"
VA1[Page 0] --> PT1[Page Table Entry]
VA2[Page 1] --> PT2[Page Table Entry]
VPN[...]
end
subgraph "Physical Memory"
PF1[Frame 0]
PF2[Frame 1]
PF3[Frame 2]
PF4[...]
end
subgraph "Disk"
SWAP[Swap Space]
end
PT1 --> PF2
PT2 --> SWAP
PT2 -.-> PF3
Prerequisites: Python basics. Understanding of Process Scheduling and Operating Systems fundamentals helps.
What Is Virtual Memory?
Think of virtual memory like a hotel room key system. Each guest (Process) has a key that only works for their room (virtual address space). The front desk (MMU) translates the room number to a physical location. If the room is being cleaned (swapped out), the desk asks the guest to wait (page fault) while it's prepared.
Address Translation
The CPU generates virtual addresses. The Memory Management Unit (MMU) translates them to physical addresses using page tables.
import random
class PageTableEntry:
def __init__(self, valid=False, frame=None, dirty=False):
self.valid = valid
self.frame = frame
self.dirty = dirty
self.referenced = False
self.age = 0
class PageTable:
def __init__(self, num_pages):
self.entries = [PageTableEntry() for _ in range(num_pages)]
def map_page(self, virtual_page, physical_frame):
self.entries[virtual_page].valid = True
self.entries[virtual_page].frame = physical_frame
def translate(self, virtual_address, page_size=4096):
vpn = virtual_address // page_size
offset = virtual_address % page_size
if self.entries[vpn].valid:
physical_address = self.entries[vpn].frame * page_size + offset
self.entries[vpn].referenced = True
return physical_address
else:
raise PageFaultException(f"Page fault at VPN {vpn}")
class PageFaultException(Exception):
pass
pt = PageTable(8)
pt.map_page(0, 5)
pt.map_page(1, 3)
pt.map_page(7, 1)
addresses = [0, 4096, 8192, 32768]
for addr in addresses:
try:
phys = pt.translate(addr)
print(f"Virtual 0x{addr:04x} â Physical 0x{phys:04x}")
except PageFaultException as e:
print(f"Virtual 0x{addr:04x} â {e}")
Expected output:
Virtual 0x0000 â Physical 0x5000
Virtual 0x1000 â Physical 0x3000
Virtual 0x2000 â Page fault at VPN 2
Virtual 0x8000 â Physical 0x1000
TLB (Translation Lookaside Buffer)
The TLB caches recent page table lookups, avoiding expensive memory accesses on every translation.
class TLB:
def __init__(self, size=4):
self.size = size
self.entries = []
def lookup(self, vpn):
for i, (v, frame) in enumerate(self.entries):
if v == vpn:
self.entries.pop(i)
self.entries.insert(0, (v, frame))
return frame
return None
def insert(self, vpn, frame):
if len(self.entries) >= self.size:
self.entries.pop()
self.entries.insert(0, (vpn, frame))
class MMU:
def __init__(self, page_table, tlb_size=4):
self.pt = page_table
self.tlb = TLB(tlb_size)
self.tlb_hits = 0
self.tlb_misses = 0
def translate(self, virtual_address, page_size=4096):
vpn = virtual_address // page_size
offset = virtual_address % page_size
frame = self.tlb.lookup(vpn)
if frame is not None:
self.tlb_hits += 1
else:
self.tlb_misses += 1
if self.pt.entries[vpn].valid:
frame = self.pt.entries[vpn].frame
self.tlb.insert(vpn, frame)
else:
raise PageFaultException(f"Page fault at VPN {vpn}")
return frame * page_size + offset
mmu = MMU(pt)
for addr in [0, 4096, 0, 8192, 4096, 0]:
try:
phys = mmu.translate(addr)
print(f"VA 0x{addr:04x} â PA 0x{phys:04x}")
except PageFaultException as e:
print(f"VA 0x{addr:04x} â {e}")
print(f"\nTLB hits: {mmu.tlb_hits}, misses: {mmu.tlb_misses}, hit rate: {mmu.tlb_hits/(mmu.tlb_hits+mmu.tlb_misses)*100:.0f}%")
Expected output:
VA 0x0000 â PA 0x5000
VA 0x1000 â PA 0x3000
VA 0x0000 â PA 0x5000
VA 0x2000 â Page fault at VPN 2
VA 0x1000 â PA 0x3000
VA 0x0000 â PA 0x5000
TLB hits: 3, misses: 3, hit rate: 50%
Demand Paging
Pages are loaded from disk only when accessed. If physical memory is full, a resident page must be evicted.
class DemandPagingSimulator:
def __init__(self, num_frames=4):
self.frames = [None] * num_frames
self.page_faults = 0
self.accesses = 0
def access_page(self, vpn):
self.accesses += 1
if vpn in self.frames:
self.on_hit(vpn)
else:
self.page_faults += 1
self.on_fault(vpn)
def on_hit(self, vpn):
pass # Subclasses implement replacement policy
def on_fault(self, vpn):
pass
class FIFOPaging(DemandPagingSimulator):
def __init__(self, num_frames=4):
super().__init__(num_frames)
self.queue = []
def on_fault(self, vpn):
if None in self.frames:
idx = self.frames.index(None)
else:
victim = self.queue.pop(0)
idx = self.frames.index(victim)
self.frames[idx] = vpn
self.queue.append(vpn)
print(f" Page fault: loaded {vpn} â frames {self.frames}")
fifo = FIFOPaging(3)
refs = [1, 2, 3, 4, 1, 2, 5, 1, 2, 3]
for r in refs:
fifo.access_page(r)
print(f"\nFIFO: {fifo.page_faults} page faults in {fifo.accesses} accesses")
Expected output:
Page fault: loaded 1 â frames [1, None, None]
Page fault: loaded 2 â frames [1, 2, None]
Page fault: loaded 3 â frames [1, 2, 3]
Page fault: loaded 4 â frames [4, 2, 3]
Page fault: loaded 1 â frames [4, 1, 3]
Page fault: loaded 2 â frames [4, 1, 2]
Page fault: loaded 5 â frames [5, 1, 2]
Page fault: loaded 1 â frames [5, 1, 2]
Page fault: loaded 2 â frames [5, 1, 2]
Page fault: loaded 3 â frames [5, 3, 2]
FIFO: 10 page faults in 10 accesses
LRU Page Replacement
class LRUPaging(DemandPagingSimulator):
def __init__(self, num_frames=4):
super().__init__(num_frames)
self.order = []
def on_hit(self, vpn):
self.order.remove(vpn)
self.order.append(vpn)
def on_fault(self, vpn):
if None in self.frames:
idx = self.frames.index(None)
else:
victim = self.order.pop(0)
idx = self.frames.index(victim)
self.frames[idx] = vpn
self.order.append(vpn)
lru = LRUPaging(3)
for r in refs:
lru.access_page(r)
print(f"LRU: {lru.page_faults} page faults in {lru.accesses} accesses")
Expected output:
LRU: 8 page faults in 10 accesses
Working Set and Thrashing
The working set is the set of pages a Process is actively using. If it doesn't fit in memory, constant page faults occur â this is thrashing.
class ThrashingDetector:
def __init__(self, working_set_size, available_frames):
self.wss = working_set_size
self.frames = available_frames
self.page_fault_rate = []
def simulate_run(self, iterations=100):
import random
faults = 0
pages = list(range(self.wss))
memory = set()
for _ in range(iterations):
page = random.choice(pages)
if page not in memory:
faults += 1
if len(memory) >= self.frames:
memory.pop()
memory.add(page)
rate = faults / iterations
self.page_fault_rate.append(rate)
status = "THRASHING" if rate > 0.3 else "OK"
print(f"WSS={self.wss}, Frames={self.frames}: fault rate={rate:.0%} â {status}")
return rate
detector = ThrashingDetector(10, 12)
detector.simulate_run()
detector.wss = 10
detector.frames = 4
detector.simulate_run()
Expected output:
WSS=10, Frames=12: fault rate=0% â OK
WSS=10, Frames=4: fault rate=60% â THRASHING
Memory-Mapped Files
Memory-mapped files allow files to be accessed as part of the virtual address space:
import mmap
import os
class MemoryMappedFile:
def __init__(self, filename, size=4096):
self.filename = filename
with open(filename, 'wb') as f:
f.write(b'\x00' * size)
def map_and_modify(self, offset, data):
with open(self.filename, 'r+b') as f:
with mmap.mmap(f.fileno(), 0) as mm:
original = mm[offset:offset + len(data)]
mm[offset:offset + len(data)] = data
mm.flush()
print(f"Mapped file '{self.filename}'")
print(f" Original at {offset}: {original}")
print(f" Written at {offset}: {data}")
return bytes(mm[offset:offset + len(data)])
mmf = MemoryMappedFile("test_mmap.bin")
result = mmf.map_and_modify(100, b"HELLO MMAP!")
print(f" Read back: {result}")
os.remove("test_mmap.bin")
Expected output:
Mapped file 'test_mmap.bin'
Original at 100: b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
Written at 100: b'HELLO MMAP!'
Read back: b'HELLO MMAP!'
Common Mistakes
1. Confusing Virtual and Physical Addresses
Developers sometimes assume addresses are physical. Virtual addresses are per-Process and always go through the MMU. Only the OS kernel deals with physical addresses.
2. Ignoring TLB Misses
Frequent TLB misses cause significant slowdown. Use huge pages (2MB or 1GB) for large memory regions to reduce TLB pressure.
3. Overcommitting Memory Without Understanding
Linux overcommits memory by default. If processes actually use all allocated memory, the OOM killer terminates processes unexpectedly. Disable overcommit for critical workloads.
4. Not Tuning for NUMA
On multi-socket systems, accessing memory on a remote socket is slower. Use numactl to bind processes to specific sockets.
5. Assuming Page Size Is Always 4KB
Modern systems support huge pages (2MB, 1GB). Databases and JVMs benefit significantly from huge pages for reduced TLB misses.
Practice Questions
1. What is the difference between a virtual address and a physical address? A virtual address is a Process's view of memory. The MMU translates it to a physical address in RAM. Virtual addresses are per-Process; physical addresses are global.
2. What happens during a page fault? The Process accesses a page not in physical memory. The OS: traps to kernel, finds a free frame (or evicts one), loads the page from disk, updates the page table, and resumes the Process.
3. How does LRU differ from FIFO page replacement? LRU evicts the least recently used page, using temporal locality. FIFO evicts the oldest page regardless of usage. LRU has better performance but higher overhead to track access order.
4. What is thrashing and how do you prevent it? Thrashing occurs when the working set of all processes exceeds physical memory. The system spends all time paging. Prevent by reducing multiprogramming, increasing RAM, or adjusting the page replacement policy.
5. Challenge: Implement the Clock (Second Chance) page replacement algorithm and compare its performance to LRU and FIFO on a random access pattern.
Mini Project: Virtual Memory Simulator
class VirtualMemorySimulator:
def __init__(self, num_pages=16, num_frames=4, policy="LRU"):
self.num_pages = num_pages
self.frames = [None] * num_frames
self.policy = policy
self.page_faults = 0
self.order = []
def access(self, vpn):
if vpn in self.frames:
if self.policy == "LRU":
self.order.remove(vpn)
self.order.append(vpn)
return True
self.page_faults += 1
if None in self.frames:
idx = self.frames.index(None)
elif self.policy == "FIFO":
victim = self.order.pop(0)
idx = self.frames.index(victim)
elif self.policy == "LRU":
victim = self.order.pop(0)
idx = self.frames.index(victim)
self.frames[idx] = vpn
if self.policy in ("FIFO", "LRU"):
self.order.append(vpn)
return False
def run(self, access_pattern):
for vpn in access_pattern:
self.access(vpn)
return self.page_faults
sim_lru = VirtualMemorySimulator(16, 4, "LRU")
sim_fifo = VirtualMemorySimulator(16, 4, "FIFO")
pattern = [0, 1, 2, 3, 0, 1, 4, 5, 1, 2, 3, 4, 5, 6]
print(f"LRU faults: {sim_lru.run(pattern)}")
print(f"FIFO faults: {sim_fifo.run(pattern)}")
FAQ
Related Concepts
What's Next
You now understand memory virtualization! Next, explore File Systems for how data is stored on disk, and learn about Virtualization for how entire machines share physical memory.
- Practice daily â Run
free -m,vmstat 1, andcat /proc/meminfoon Linux - Build a project â Create a working set estimator that detects thrashing
- Explore related topics â Check out NUMA tuning for database servers
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro