Paging vs Segmentation
[!NOTE] This module explores the core principles of Paging vs Segmentation, deriving solutions from first principles and hardware constraints to build world-class, production-ready expertise.
1. The Old Way: Segmentation
In the early days, the OS divided memory into variable-sized chunks based on logic.
- Code Segment: Where instructions live (Read-Only).
- Stack Segment: Grows downwards.
- Heap Segment: Grows upwards.
The Problem: External Fragmentation
Imagine you have 10GB of free RAM, but it’s scattered in tiny 1KB holes. If you need to allocate a contiguous 2GB block for a game, you can’t. The memory exists, but it’s unusable because it’s not contiguous.
2. The Modern Way: Paging
To solve fragmentation, we divide everything into fixed-sized chunks.
- Page: A chunk of Virtual Memory (usually 4KB).
- Frame: A chunk of Physical RAM (also 4KB).
The OS acts as a “Frame Broker.” It doesn’t matter if Frame #1 is at address 0x0 and Frame #2 is at 0xFFFFF. As long as the Page Table maps Page #1 to Frame #1 and Page #2 to Frame #2, the process sees them as contiguous.
[!NOTE] Internal Fragmentation: If you only need 10 bytes, the OS still gives you a full 4KB page. The remaining 4086 bytes are wasted. This is the cost of paging.
3. The Hardware Reality: Multi-Level Page Tables
If we had a single massive array mapping every 4KB page in a 64-bit address space, the Page Table itself would be petabytes in size!
To save space, x86-64 processors use 4-Level Paging (PML4). It’s a tree structure.
- CR3 Register: Points to the root (Level 4 Table).
- PML4 (Page Map Level 4): Points to a PDP.
- PDPT (Page Directory Pointer Table): Points to a PD.
- PD (Page Directory): Points to a PT.
- PT (Page Table): Points to the actual Physical Frame.
Why? If a process only uses 10MB of RAM, we only allocate the branches of the tree that cover those 10MB. The rest of the tree doesn’t exist.
4. Interactive: The Page Table Walker
Visualize the hardware walking the tree to find your data.
- VA: Virtual Address (Input)
- CR3: Root Pointer
- PA: Physical Address (Output)
5. Code Example: Querying Page Size
Most systems use 4KB pages. Some use “Huge Pages” (2MB or 1GB) for performance (less TLB pressure). Let’s ask the OS what the default is.
package main
import (
"fmt"
"os"
)
func main() {
// In Go, we usually rely on the OS package
pageSize := os.Getpagesize()
fmt.Printf("System Page Size: %d bytes\n", pageSize)
fmt.Printf("System Page Size: %.1f KB\n", float64(pageSize)/1024.0)
if pageSize == 4096 {
fmt.Println("Standard 4KB Pages detected.")
} else {
fmt.Println("Non-standard page size.")
}
}
import java.lang.foreign.Arena; // Java 22+ (Preview)
// OR using Unsafe (Legacy)
public class PageSize {
public static void main(String[] args) {
// Since Java 22, the Foreign Function & Memory API
// allows cleaner native access.
// Historically, Unsafe.pageSize() was used.
try {
// Using reflection to access Unsafe for demonstration
// DO NOT DO THIS IN PRODUCTION
java.lang.reflect.Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) f.get(null);
int pageSize = unsafe.pageSize();
System.out.printf("System Page Size: %d bytes (%.1f KB)%n",
pageSize, pageSize / 1024.0);
} catch (Exception e) {
System.out.println("Could not access Unsafe: " + e.getMessage());
}
}
}