On this page
Page Reclaim and Swap
Coverage: LRU (anonymous/file) → kswapd + direct reclaim → swap mechanism → reverse mapping (rmap) → page cache reclaim → compaction Kernel versions: 2.6 ~ 6.x, with emphasis on multi-gen LRU (6.1+)
Overview
When physical memory is insufficient, the kernel must reclaim some pages. Reclaimable objects fall into two categories:
- File-backed pages: Clean pages can be dropped directly (as they have a disk copy), while dirty pages must be written back.
- Anonymous pages: Must be written to swap before they can be reclaimed.
The core of the reclaim strategy is LRU (Least Recently Used)—swapping out cold pages while retaining hot ones. However, determining "hotness" and "coldness" is extremely difficult within a working set of hundreds of gigabytes. Linux has evolved from traditional doubly-linked list LRU to the Multi-Gen LRU (MGLRU) introduced in version 6.1, which significantly improves handling of large working sets and scanning storms.
LRU Lists
Traditional Dual LRU (2.6 ~ 5.x)
// mm/vmscan.c
// Each memcg and each node has a set of LRUs:
;
// Pages move between active and inactive lists:
// First mapping → inactive
// Accessed again → promoted to active
// Not accessed for a long time → demoted to inactive
// Pages in inactive list → reclaim candidates
Reclaim Priority
// Reclaim order (file pages take precedence over anonymous pages):
// 1. Clean file pages (drop, fastest)
// 2. Dirty file pages (writeback)
// 3. Anonymous pages (swap out)
//
// Why do file pages take precedence?
// Clean file pages: Dropped directly, 0 IO
// Anonymous pages: Must be written to swap → IO overhead
// Dirty file pages: Must be written back → IO overhead
//
// /proc/sys/vm/swappiness controls this priority:
// 0: Prefer reclaiming file pages; only swap if absolutely necessary
// 100: Treat file and anonymous pages equally
// Default is 60
Multi-Gen LRU (6.1+)
Problems with traditional LRU:
- Only two generations (active/inactive) → coarse granularity
- Hot/cold judgment relies on the referenced bit + periodic scanning
- Scanning cost is extremely high under large working sets ("scanning storm")
MGLRU Solution:
Pages are grouped by generation, with each generation having its own LRU.
New generation = Active (recently accessed)
Old generation = Cold (reclaim candidates)
Reclaim starts from the oldest generation → naturally excludes hot pages
No need to scan all pages to determine hotness/coldness → scanning volume is significantly reduced
Implementation: mm/vmscan.c (MGLRU build) + /sys/kernel/mm/lru_gen
Both Google and Meta have reported significant CPU savings
(Scanning overhead reduced by 50%~90%)
kswapd and Direct Reclaim
kswapd: Background Reclaim
// mm/vmscan.c
// Each NUMA node has its own kswapd kernel thread
// Wake-up conditions:
// Free pages < high watermark → kswapd wakes up
// kswapd continues reclaiming until free pages > high watermark
static int
Direct Reclaim: Synchronous Reclaim
// When kswapd cannot reclaim fast enough (memory allocation is too fast),
// the allocator reclaims itself:
→
→ →
→ →
→ →
// Same reclaim logic as kswapd, but executed synchronously
// The allocator blocks here until pages become available
Direct reclaim causes sudden spikes in allocation latency—which is why everyone pays attention to "memory pressure" metrics.
Swap
Swap Partition/File
# Configuration
# Priority (multiple swap devices)
Swap Out (Anonymous Pages → Swap)
// mm/vmscan.c
// When reclaiming anonymous pages in shrink_page_list():
if
// Swap cache:
// Cache used during the transition of a page between swap and memory
// If there is a race between swap out and swap in → swap cache provides consistency
Swap In (Page Fault → Swap)
// mm/memory.c
// do_swap_page(): Process accesses an address that has been swapped out
├─ // Check swap cache (might already be in memory)
│ └─ If present: Direct
│
└─ // Pre-read from swap device
└─
→ // Submit IO
→ Page enters swap cache
→ Map PTE
→ User process continues execution
Reverse Mapping (rmap)
// mm/rmap.c
// Reverse mapping answers: "Given a physical page, which processes' virtual addresses map to it?"
// Anonymous pages:
// Traverse all mappings via anon_vma + VMA interval tree
// page->mapping → anon_vma → anon_vma_chain → VMA
// File pages:
// Look up via address_space + page->index
// page->mapping → address_space → i_mmap (interval tree)
// rmap_walk(page, func):
// Traverse all PTE mappings for the page
// Call func for each mapping → e.g., try_to_unmap
// → Mark PTE as swap entry or clear it
// → Flush TLB
// try_to_unmap() is a prerequisite for swap:
// All PTE references must be found and unmapped before the page can be reclaimed
Compaction
// mm/compaction.c
// Problem: The buddy allocator requires contiguous pages (e.g., order 4 = 16 pages = 64KB)
// But memory is fragmented → cannot allocate contiguous blocks
// Compaction: "Move" used pages to free up contiguous free areas
// 1. Scan: Find free pages (target) and used pages (source)
// 2. Migrate: Copy used page content to free page
// 3. Update page tables: Modify all PTEs to point to the new location
// 4. Repeat: Until enough contiguous space is freed
// Trigger methods:
// 1. kcompactd: Background thread (similar to kswapd)
// 2. Direct compaction: Allocator actively compacts in the slowpath
// Check compaction status:
cat /proc/vmstat | grep compact
Debugging and Observation
# Memory pressure overview
# SwapTotal/SwapFree: Swap usage
# Active/Inactive(anon/file): Pages in LRU
# Dirty/Writeback: Pages pending writeback
# Reclaim statistics
|
# pgsteal_*: How many pages were reclaimed
# pgscan_*: How many pages were scanned
# scan >> steal → Low LRU scanning efficiency (improved by MGLRU)
# Check if MGLRU is enabled
&&
# kswapd behavior
|
# Compaction
|
# Trigger manual reclaim (for testing, use with caution in production)
References and Further Reading
- Kernel Documentation:
Documentation/admin-guide/sysctl/vm.rst,Documentation/mm/multigen_lru.rst - LWN:
- "Multi-generational LRU" (lwn.net/Articles/856932/)
- "The case for MGLRU" (lwn.net/Articles/916777/)
- "Swap and the page cache" (lwn.net/Articles/358953/)
- Source Files:
mm/vmscan.c— kswapd + LRU scanning + direct reclaimmm/swap_state.c— Swap cachemm/swapfile.c— Swap partition/file managementmm/rmap.c— Reverse mappingmm/compaction.c— Memory compactionmm/page_io.c— Swap IO
Keywords: LRU, MGLRU, kswapd, direct reclaim, swap, swappiness, rmap, try_to_unmap, compaction, page cache reclaim