On this page
VMA and Maple Tree
Coverage: vm_area_struct → VMA operations (mmap/munmap/mprotect/mremap) → Maple Tree replacing Red-Black Tree (6.1+) → VMA merging → Page fault and VMA lookup Kernel version: 2.6 ~ 6.x, with a focus on the Maple Tree migration
Overview
The address space of each process is described by a set of VMAs (Virtual Memory Areas). A VMA represents a contiguous range of virtual addresses with identical protection attributes and mapping types (anonymous memory, file mappings, device mappings, etc.). mmap() creates a VMA, munmap() destroys a VMA, and mprotect() modifies VMA attributes.
Traditionally, VMAs were managed using a Red-Black Tree plus a doubly linked list (O(log n) lookup, O(n) traversal). The Maple Tree, introduced in version 6.1, reduces operations to O(log n) while supporting lockless read traversal—one of the most significant data structure changes in the memory management subsystem in recent years.
vm_area_struct
// include/linux/mm_types.h
;
Key vm_flags
| Flag | Meaning |
|---|---|
VM_READ / VM_WRITE / VM_EXEC | Access permissions |
VM_SHARED | MAP_SHARED mapping (writes visible to others) |
VM_MAYSHARE | Allowed to be converted to SHARED |
VM_GROWSDOWN / VM_GROWSUP | Stack auto-growth (downward/upward) |
VM_HUGETLB | HugeTLB mapping |
VM_LOCKED | Memory locked by mlock() (not swappable) |
VM_IO | MMIO mapping |
VM_PFNMAP | Device mapping without page structs |
Anonymous vs. File-backed VMA Classification
Anonymous VMA:
vm_file == NULL
Examples: Heap/stack allocated by malloc/mmap(MAP_ANONYMOUS)
Data has no corresponding disk file
Written to swap partition/file during swapping
File-backed VMA:
vm_file != NULL
Examples: mmap(regular file), code segments (ELF LOAD segments from exec)
Pages in the page cache match file contents
Dirty pages are written back to the corresponding file
VMA Operations
mmap: Creating a VMA
flowchart TD
SYSCALL["mmap() System Call"] --> AREA["get_unmapped_area()<br/>Find free address range"]
AREA --> ARCH{"Architecture-specific search"}
ARCH -->|"x86"| TOPDOWN["Top-down search<br/>(top-down, ASLR)"]
ARCH -->|"legacy"| BOTTOMUP["Bottom-up<br/>(mmap_legacy_base)"]
AREA --> REGION["mmap_region()"]
REGION --> CHECK{"Check relationship with existing<br/>VMAs"}
CHECK -->|"Exact match"| MERGE["Reuse or merge"]
CHECK -->|"Partial overlap"| SPLIT["Split VMA"]
CHECK -->|"No overlap"| NEW["Create new VMA"]
NEW --> VM_OPS["Call vm_ops->mmap()<br/>Filesystem callback<br/>e.g., ext4: register filemap_fault()"]
VM_OPS --> DEMAND["⚠️ Page table NOT updated!<br/>Demand paging:<br/>Actual allocation deferred to page fault"]
DEMAND --> RET["Return mapping start address ✅"]
classDef sys call fill:#e3f2fd,stroke:#1565c0
classDef step fill:#f3e5f5,stroke:#7b1fa2
classDef decision fill:#fff3e0,stroke:#ef6c00
classDef done fill:#e8f5e9,stroke:#2e7d32
class SYSCALL syscall
class AREA,TOPDOWN,BOTTOMUP,REGION,MERGE,SPLIT,NEW,VM_OPS,DEMAND step
class ARCH,CHECK decision
class RET done
munmap: Destroying a VMA
→
:
├─ // Find VMAs covering this range
├─ Split
├─
│ └─ Detach from the mm's VMA set
├─ unmap_region()
│ └─ unmap_vmas → Iterate VMA list
│ └─ unmap_page_range()
│ → Clear corresponding page table entries (zap_pte_range)
│ → Free physical pages (put_page → drop to page cache if not the last reference)
│ → TLB shootdown (multi-core)
└─ remove_vma_list()
└─ Free vm_area_struct (kmem_cache_free)
mprotect: Changing Permissions
→
// No need to reallocate physical pages; only PTE flags need changing:
// Change VM_WRITE → PTE R/W bit
// Change VM_EXEC → PTE NX bit
// Requires page table walk → set_pte_at() → flush TLB
mremap: Resizing/Moving
→
// MREMAP_MAYMOVE: Allows moving
// → If insufficient space after current address:
// get_unmapped_area() finds a new location
// move_page_tables() → Copies page table entries to new location (does NOT copy physical pages!)
// → TLB flush
//
// Interaction with realloc:
// glibc uses mremap to expand large allocations → avoids copying physical memory
Maple Tree: The New Engine for VMA Management
Why Replace the Red-Black Tree
Old Design (Red-Black Tree + Linked List, 2.6 ~ 6.0):
VMAs existed in both a Red-Black Tree (O(log n) lookup) and a doubly linked list (O(n) traversal).
Drawbacks:
- Two data structures must be kept in sync → Complex
- All operations require mmap_lock → Lock contention
- Linked list traversal is O(n) in dense VMA scenarios (e.g., many mmaps)
New Design (Maple Tree, 6.1+):
VMAs exist only in the Maple Tree.
Advantages:
- All operations are O(log n): lookup/insert/delete/range query
- Supports Lockless read traversal (RCU-protected): future page faults may not need locks
- Efficient range operations: directly finds all overlapping VMAs
- B-tree variant: Cache-line friendly, variable node sizes
Maple Tree Structure
// lib/maple_tree.c
// The Maple Tree is a B-tree variant, with up to 16 slots per node
// Node types:
// MAPLE_NODE_DENSE: All slots contain data (similar to an array)
// MAPLE_NODE_RANGE: Slots carry range information (for sparse storage)
// Each level can encode different key ranges:
// Node 0: [0, 4096) (4K)
// Node 1: [0, 16777216) (16M)
// Node 2: [0, 68719476736) (64G)
// ...
// Up to 8 levels total (for 64-bit keys)
// For VMAs:
// key = Virtual address (start)
// value = vm_area_struct *
// Lookup: mas_find() → Finds the VMA covering a specific address
Lockless Traversal
// RCU-safe design of the Maple Tree:
// Modifiers: Acquire spinlock → Modify node (if node is full → split + copy)
// Readers: Do not acquire lock → Read node via rcu_dereference
// Nodes are not modified in-place by writers; instead, they are copied and then modified → Old nodes are reclaimed via RCU
//
// This is the biggest value of the Maple Tree:
// Future do_page_fault() can look up VMAs under an RCU read lock
// → No need for mmap_lock (for readers) → Eliminates mmap_lock contention
Page Fault and VMA Lookup
// arch/x86/mm/fault.c
// do_page_fault() is the hottest VMA lookup path
static void
VMA Merging
Adjacent VMAs with identical attributes are automatically merged:
// mm/mmap.c
// vma_merge():
// Compare new mmap request with preceding and succeeding VMAs:
// Are vm_flags identical?
// Is vm_file identical? (or both are anonymous)
// Is vm_pgoff contiguous?
// Do PROT settings match?
// Are anon_vmas compatible?
// → If conditions are met: Merge into a single larger VMA
//
// Why merge?
// Reduce VMA count → Less lookup time
// Reduce slab allocations (vm_area_struct)
// /proc/<pid>/maps displays more cleanly
//
// Classic example:
// brk() expands heap → Multiple small expansions → Automatically merged into one large [heap] VMA
Debugging
# VMA Layout
# VMA Statistics
|
# VmPeak: Historical maximum virtual memory
# VmSize: Current total virtual memory
# VmRSS: Physical memory usage (Resident Set Size)
# VmData/VmStk/VmExe/VmLib: Data/Stack/Code/Libraries
# Monitor VMA count (excessive VMAs degrade performance)
References and Further Reading
- Kernel Documentation:
Documentation/core-api/maple_tree.rst,Documentation/mm/mmap.rst - LWN:
- "Introducing the Maple Tree" (lwn.net/Articles/867525/)
- "The state of the Maple Tree" (lwn.net/Articles/905317/)
- "mmap_lock and VMA locking" (lwn.net/Articles/909840/)
- Source Files:
lib/maple_tree.c— Maple Tree implementationmm/mmap.c— VMA management (mmap/munmap/mprotect)include/linux/mm_types.h— vm_area_struct definitionarch/x86/mm/fault.c— Page fault handler
Keywords: vm_area_struct, VMA, mmap, munmap, Maple Tree, page fault, find_vma, VM_MERGEABLE, mmap_lock