K11 — The Full Path of Physical Page Allocation: alloc_pages and the Buddy System
Key Insight: Every call to alloc_pages embarks on a complete journey from request to physical page frame.
The alloc_pages Function Signature
alloc_pages is the core function for allocating physical pages in Linux:
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);
Parameters:
- gfp_mask: Allocation flags (GFP_KERNEL / GFP_ATOMIC, etc.)
- order: Allocate 2^order pages (order=0 → 1 page, order=2 → 4 pages)
Return value:
- Success: Returns a pointer to struct page
- Failure: Returns NULL
Key gfp_mask flags:
Zone selection:
__GFP_DMA: Allocate only from ZONE_DMA
__GFP_HIGHMEM: Allocate from ZONE_HIGHMEM
(Default: ZONE_NORMAL + ZONE_HIGHMEM)
Behavior:
__GFP_WAIT: Allow sleeping to wait for memory (slow path)
__GFP_HIGH: High priority, emergency allocation
Special:
__GFP_ZERO: Return zeroed page
__GFP_NOFAIL: Must not fail (keeps retrying)
GFP_KERNEL = (__GFP_WAIT | __GFP_IO | __GFP_FS)
→ Normal allocation, can wait, can do disk I/O, can access filesystemFull Allocation Path
alloc_pages call chain:
alloc_pages(gfp_mask, order)
→ __alloc_pages(gfp_mask, order, 0, NULL)
→ get_page_from_freelist(gfp_mask, order, ...)
→ __alloc_pages_nodemask(gfp_mask, order, ...)
→ try_this_node(node, gfp_mask, order)
→ Buddy system allocation
Actual path (with watermarks and reclaim):
alloc_pages()
→ check_new_pages() // Fast path: check current CPU cache
→ if (NULL) __alloc_pages()
→ Watermark check (pages_high / pages_low / pages_min)
→ if (watermark OK) buddy allocation (fast path)
→ if (watermark FAIL) {
// Wake up kswapd
// Try direct reclaim
// Retry buddy allocation
// Still failing? Return NULL or trigger OOM
}Buddy System core allocation function:
static inline struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
int preferred_nid, nodemask_t *nodemask)
{
struct zone *zone;
struct plist_head *queues;
enum zone_type classzone_idx;
// 1. Find the highest-priority available node and zone
// 2. Check watermarks
// 3. If OK, call rmqueue_pcplist (per-CPU page lists)
// 4. If fails, call __rmqueue (actual buddy allocation)
// 5. If buddy fails, try fallback nodes
// __rmqueue (actual allocation):
// for (order = requested; order <= MAX_ORDER; order++) {
// page = __rmqueue_smallest(zone, order, migratetype);
// if (page) {
// if (order > requested)
// expand(zone, page, order, requested, migratetype);
// return page;
// }
// }
}Watermark Checking
Linux divides memory into three watermark levels for each zone:
pages_min: Minimum reserved pages (cannot be allocated for non-urgent requests)
pages_low: Low watermark (wake up kswapd when below this)
pages_high: High watermark (kswapd sleeps when above this)
Watermark levels:
┌─────────────┐ ← pages_high
│ │
│ Normal │ ← pages_low
│ Zone │
│ │ ← pages_min
├─────────────┤
│ Reserved │ Below pages_min: only __GFP_HIGH can touch it
└─────────────┘
User/kernel allocation path:
1. If watermark >= pages_high: allocate directly (fast path)
2. If watermark < pages_low: wake up kswapd (background reclaim)
3. If watermark < pages_min: direct reclaim (block caller)
4. Still not enough: OOM killer
View watermarks:
$ cat /proc/zoneinfo
Node 0, zone Normal
pages free 4061
min 451
low 563
high 676The Buddy System Core Algorithm
Buddy system algorithm:
1. Find a block of size 2^order from the free_area[order] list
2. If not found, go to the next order (2^(order+1)):
a. Split the larger block into two "buddies"
b. Put one half back in the smaller free list
c. Use the other half
3. Repeat until a sufficiently large block is found
Example: Request order=0 (1 page)
→ Free list for order 0 is empty
→ Try order 1 (2 pages):
┌──────────┬──────────┐
│ Buddy A │ Buddy B │ ← Split: Buddy A used, Buddy B returned to order 0
└──────────┴──────────┘
→ Try order 2 (4 pages):
┌──────┬──────┬──────┬──────┐
│ A* │ B │ C │ D │ ← Split: A used, B→order1, C+D→order1
└──────┴──────┴──────┴──────┘ Then B split, C+D stays
Comments