Eight pages are free, yet you cannot allocate two contiguous pages; one node has ample resources, but another constrained allocation still fails. Physical allocation must simultaneously satisfy quantity, shape, location, and wait conditions.
We start by splitting and merging in a 16-page buddy model, then gradually reintroduce NUMA, zones, watermarks, and GFP constraints layer by layer.
Phenomenon to Explain
Reading Location
What exactly does order represent in terms of memory?
Sections 1–2: Buddy Model and Experiments
Why can't the current allocation use free pages?
Sections 3–4: Location, Watermarks, and GFP
Why doesn't buddyinfo change immediately after freeing pages?
Section 5: PCP Batch Caching
Are pages reclaimable or offlinable?
Sections 6–7: References, Hotplug, and Observation
1. Understanding Allocation Shape via order
alloc_pages(gfp, order) requests 2^order physically contiguous base pages, returning the corresponding struct page. The base page size is determined by architecture and configuration; if P=4096 bytes, orders 0, 1, and 4 correspond to 4, 8, and 64 KiB respectively. order is not a byte count, nor is it the page count itself.
Assume a managed range spans only PFN 0 to 15, initially a single order 4 free block. Now, request an order 2 allocation, which is 4 pages. The allocator does not need to hand over all 16 pages: it first splits into [0,8) and [8,16), then splits the former into [0,4) and [4,8), retaining two unused buddies for subsequent allocations.
Continuing with two more releases along the diagram clarifies the merge conditions:
Time
[0,4)
[4,8)
Can [0,8) be restored?
After second four-page request
In use
In use
No
After freeing first request
Free
In use
No, buddy not free
After freeing second request
Free
Free
Yes, combined into order 3
If [8,16) is also still free, it can continue merging back to the initial order 4.
Adjacency is not enough: must be same-order, aligned buddies
For block start PFNs aligned to 2^order, the same-order buddy start can be calculated as PFN XOR (1 << order). However, obtaining a number only finds a candidate: you must also verify it belongs to a manageable range, is indeed a same-order free block, and satisfies migration type rules. Two blocks adjacent in address are not necessarily buddies of this order; for example, order 2 blocks [4,8) and [8,12) cannot directly combine into an order 3 block aligned to 8 pages.
Why eight free pages might still not yield two pages
Still using 16 pages, assume all even PFNs are occupied and all odd PFNs are free. Half the memory is free, yet no two pages are contiguous. The total amount is sufficient, but the shape is not. This is external fragmentation, not the applicant requesting more bytes than the total free memory.
If occupied pages are migratable, the kernel might move them to one side, freeing up larger contiguous ranges; if critical positions are occupied by non-migratable objects or long-term pinned pages, compaction may not succeed. If the workload does not fundamentally require physical continuity, the interface design should avoid high orders rather than demanding the allocator create contiguous regions out of any fragmented state. The upper limit for high orders is also determined by the target kernel configuration and should not be hardcoded as MAX_ORDER=11.
If even PFNs are occupied and odd PFNs free, eight pages remain but no free adjacent pair exists. Migration may improve the shape; pinned or unmovable contents can prevent it. Avoid demanding high-order physical continuity when the consumer only needs virtual continuity. Maximum order depends on the target configuration.
Preparing the visual
Why can an allocation fail with enough free pages?
Order k requires 2^k contiguous pages aligned to 2^k. A free-page total does not prove this shape exists; only compatible, aligned free buddies coalesce.
{"id":"buddy-block-shapes","title":"Why can an allocation fail with enough free pages?","summary":"Order k requires 2^k contiguous pages aligned to 2^k. A free-page total does not prove this shape exists; only compatible, aligned free buddies coalesce.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"order\" data-i18n=\"order\"></label><output for=\"order\" id=\"order-value\"></output><input id=\"order\" type=\"range\" min=\"0\" max=\"4\" value=\"2\" step=\"1\"></div><div class=\"presets\" role=\"group\" data-i18n-label=\"layout\"><button data-mode=\"empty\" data-i18n=\"empty\"></button><button data-mode=\"fragment\" data-i18n=\"fragment\"></button></div><div class=\"actions\"><button id=\"allocate\" data-i18n=\"allocate\"></button></div><p class=\"caption\" data-i18n=\"physical\"></p><div id=\"physical\" class=\"physical\"></div><label for=\"allocation\" class=\"caption\" data-i18n=\"allocatedBlocks\"></label><select id=\"allocation\"></select><div class=\"actions\"><button id=\"release\" data-i18n=\"release\"></button></div><p class=\"caption\" data-i18n=\"freeLists\"></p><div class=\"free-lists\"><div class=\"free-row\"><span>o0</span><div id=\"free-0\"></div></div><div class=\"free-row\"><span>o1</span><div id=\"free-1\"></div></div><div class=\"free-row\"><span>o2</span><div id=\"free-2\"></div></div><div class=\"free-row\"><span>o3</span><div id=\"free-3\"></div></div><div class=\"free-row\"><span>o4</span><div id=\"free-4\"></div></div></div><div class=\"metrics\"><div><span data-i18n=\"freePages\"></span><strong id=\"free-total\"></strong></div><div><span data-i18n=\"largest\"></span><strong id=\"largest\"></strong></div></div><div class=\"formula\" id=\"buddy-formula\"></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"initial\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"allocated\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"failed\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"freed\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"fragmentNote\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n.physical{display:grid;grid-template-columns:repeat(16,minmax(0,1fr));gap:2px;margin:12px 0;height:64px}.physical button{min-width:0;min-height:0;padding:3px 0;background:var(--accent-soft);border-color:var(--accent);font:12px var(--viz-font-ui);overflow-wrap:anywhere}.physical button.free{background:var(--surface);border-style:dashed;border-color:var(--rule);color:var(--muted);cursor:default}.physical button[data-small=true]{font-size:0}.free-lists{margin:12px 0}.free-row{display:grid;grid-template-columns:25px minmax(0,1fr);gap:6px;font-size:12px;align-items:center;margin:7px 0}.free-row>div{display:grid;grid-template-columns:repeat(16,minmax(0,1fr));gap:2px;min-height:28px;background:var(--surface);border-radius:3px}.free-row i{background:var(--second-soft);border:1px solid var(--second);border-radius:3px}.formula{min-height:6.4em;white-space:pre-line}","js":"const $=s=>document.querySelector(s);let blocks=[],serial=0,mode='empty',message='initial',last=null;function freeBlocks(){const used=Array(16).fill(false);for(const b of blocks)for(let i=b.start;i<b.start+(1<<b.order);i++)used[i]=true;const free=[];for(let p=0;p<16;){if(used[p]){p++;continue;}let o=0;while(o<4&&p%(1<<(o+1))===0&&p+(1<<(o+1))<=16&&!used.slice(p,p+(1<<(o+1))).some(Boolean))o++;free.push({start:p,order:o});p+=1<<o;}return free;}function draw(){const o=+$('#order').value,free=freeBlocks();$('#order-value').textContent=o+' → '+(1<<o)+' P';const pieces=[...blocks.map(b=>({...b,free:false})),...free.map(b=>({...b,free:true}))].sort((a,b)=>a.start-b.start);$('#physical').replaceChildren(...pieces.map(b=>{const e=document.createElement('button');e.style.gridColumn=(b.start+1)+' / '+(b.start+(1<<b.order)+1);e.textContent=b.order?'['+b.start+','+(b.start+(1<<b.order))+')':b.start;e.dataset.small=String(b.order===0);e.classList.toggle('free',b.free);e.disabled=b.free;e.title='PFN ['+b.start+','+(b.start+(1<<b.order))+')';e.setAttribute('aria-label',e.title);if(!b.free)e.addEventListener('click',()=>{blocks=blocks.filter(x=>x.id!==b.id);last=b;mode='';message='freed';draw();});return e;}));const selected=$('#allocation').value;$('#allocation').replaceChildren(...(blocks.length?blocks:[{id:'',start:0,order:0}]).map(b=>{const e=document.createElement('option');e.value=b.id;e.textContent=blocks.length?'PFN ['+b.start+','+(b.start+(1<<b.order))+') · order '+b.order:'—';return e;}));if(blocks.some(b=>String(b.id)===selected))$('#allocation').value=selected;$('#release').disabled=!blocks.length;for(let j=0;j<5;j++)$('#free-'+j).replaceChildren(...free.filter(b=>b.order===j).map(b=>{const e=document.createElement('i');e.style.gridColumn=(b.start+1)+' / '+(b.start+(1<<j)+1);e.title='['+b.start+','+(b.start+(1<<j))+')';return e;}));$('#free-total').textContent=16-blocks.reduce((n,b)=>n+(1<<b.order),0);$('#largest').textContent=free.length?Math.max(...free.map(b=>1<<b.order)):0;$('#buddy-formula').textContent=last?'PFN '+last.start+' XOR '+(1<<last.order)+' = '+(last.start^(1<<last.order))+'\\norder '+last.order+' → '+(1<<last.order)+' × 4096 B':'order '+o+' → 2^'+o+' = '+(1<<o)+' P';$('#explanation').textContent=viz.t(message);document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}$('#release').addEventListener('click',()=>{const b=blocks.find(b=>String(b.id)===$('#allocation').value);if(!b)return;blocks=blocks.filter(x=>x.id!==b.id);last=b;mode='';message='freed';draw();});$('#order').addEventListener('input',draw);$('#allocate').addEventListener('click',()=>{const order=+$('#order').value,b=freeBlocks().find(b=>b.order>=order);mode='';if(!b)message='failed';else{const n={start:b.start,order,id:++serial};blocks.push(n);last=n;message='allocated';}draw();});document.querySelectorAll('[data-mode]').forEach(e=>e.addEventListener('click',()=>{mode=e.dataset.mode;blocks=mode==='empty'?[]:Array.from({length:8},(_,i)=>({start:i*2,order:0,id:++serial}));last=null;message=mode==='empty'?'initial':'fragmentNote';draw();}));draw();","audio":false,"strings":{"allocate":"Allocate one block","allocated":"The selected contiguous block now belongs to the caller. Other free ranges remain separate; total blank area is not one contiguous block.","allocatedBlocks":"Allocated blocks","empty":"All free","failed":"No aligned contiguous block of this size exists. Even a sufficient total free-page count cannot satisfy this order directly.","fragment":"Alternating occupancy","fragmentNote":"Eight odd-numbered pages are free, but no adjacent free pair exists. Try order 1, then free a neighboring live block and observe the new shape.","freeLists":"Free blocks grouped by order","freePages":"Total free pages","freed":"The original allocation was freed and compatible buddies coalesced upward. Adjacent ranges are not necessarily aligned buddies of the same order.","heading":"Why can an allocation fail with enough free pages?","initial":"A larger free block splits down to the requested size; unused buddies remain in the free lists.","intro":"Choose an order and allocate. Click a live block to free that entire allocation. Compare an alternating layout and watch free blocks split and coalesce by order.","largest":"Largest free block / pages","layout":"Choose a layout snapshot","order":"Requested order","physical":"Physical pages: click a live block to free it","release":"Free selected block","reset":"Reset","scope":"A 16-page single-zone model, 4 KiB pages, no concurrency, lowest-PFN selection. Free lists show fully coalesced ideal state, omitting watermarks, NUMA, migratetypes, and PCP. Freeing releases an entire original allocation, never an arbitrary page inside it. Presets rebuild the model; they do not migrate live objects.","scopeLabel":"Model scope and assumptions"}}
2. An Executable Buddy Model
Below, we manage only 16 pages represented by Python integers, without allocating or moving real physical memory. It demonstrates how splitting, same-order merging, and allocation records constrain each other, omitting NUMA, watermarks, PCP, and concurrent synchronization.
The real allocator must maintain these invariants under locks and accounting protocols; you cannot directly replace the set operations in the model with lock-free lists. When releasing original high-order allocations, you must also adhere to the order and reference contracts of the corresponding API, and cannot arbitrarily release any individual page within it as an independent allocation result.
3. Nodes and Zones Add Location Conditions to "Available"
NUMA nodes express memory access location and cost, while zones express different usage constraints within the same node. A node does not necessarily equal a CPU socket: the system can have nodes containing only memory, nodes spanning multiple physical address ranges, and single-socket machines may also expose NUMA topology. Remote access cost depends on hardware, access patterns, and congestion, and cannot be fixed as 30% slower.
Local allocation preference on normal nodes can reduce remote access, but user memory policies, cpusets, and allocation flags affect the allowed set and fallback. If an application binds threads only to node 0, but its existing pages are still on node 1, it will not automatically migrate all content just because the thread moves. "First touch" should also be understood as the placement behavior when actually establishing a backing page, not treating the malloc return moment as the moment of physical ownership for all pages.
Zone
Purpose
Distinction Needed
DMA / DMA32
Addressing constraint ranges for specific architectures
Boundaries depend on architecture; device DMA should use DMA API to express actual constraints
NORMAL
Memory normally accessible by the kernel
Still subject to node, watermark, and allocation purpose limits
HIGHMEM
Memory without permanent direct mapping in some 32-bit configurations
Does not mean "high memory usage"
MOVABLE
Tries to accommodate migratable content to improve compaction/offlining capability
Not all kernel objects can be placed here, nor is offlining guaranteed
DEVICE
Provides page and other management services for certain device memory
Not equivalent to general RAM managed by the buddy system
In common x86 configurations, DMA/DMA32 relates to low address ranges, but "requesting DMA32 memory" cannot replace device DMA masks, IOMMU mappings, and consistency handling. Some memory provided by CXL may also be used as normal System RAM; seeing CXL does not necessarily mean it belongs to ZONE_DEVICE. Topology and structure definitions can be found in Physical Memory.
4. Watermarks Are Not Three Fixed Failure Switches
Free pages reserve a portion of margin to ensure that burst allocations and the reclaim paths themselves can still obtain resources. If normal allocations exhaust all pages, the I/O or metadata requests required for reclaim may also fail to progress, forming a harder-to-break cycle.
Watermark
Role in Simplified Model
low
Triggers background reclaim for nearby balance conditions
high
Common target for background recovery
min
Participates in emergency reservations and stricter admission checks
Actual checks also include order, low zone protection, unusable free pages, and watermark boost. They are not three fixed switches; you cannot directly deduce "below low means immediate reclaim, below min means OOM."
For example, NORMAL has 1200 free pages, with min/low/high assumed to be 1000/1500/2000. This state suggests background balance should proceed, but you cannot decide based solely on 1200 that an order 4 request will definitely succeed: it might not find 16 contiguous pages, or the request might be constrained by another node or zone. Conversely, some non-waiting allocations fail directly without performing synchronous reclaim on behalf of the caller.
GFP: How Much Work the Caller Allows the Allocator to Do
Flag
Key Allowed Behavior
Caller Still Must Handle
GFP_KERNEL
Can sleep and reclaim in appropriate contexts
Allocation failure and cleanup
GFP_NOWAIT
Avoids waiting caused by direct reclaim
More limited available resources
GFP_ATOMIC
Important allocations that cannot sleep have different reservation access conditions
Reserve exhaustion; not suitable for arbitrary NMI paths
GFP is a behavioral constraint, not a success rate promise.
__GFP_RECLAIM includes direct reclaim and kswapd-related permissions; it is not simply a new name for __GFP_DIRECT_RECLAIM. __GFP_NORETRY does not completely prohibit any lightweight reclaim attempts. If reclaim inside filesystem or block I/O might re-enter resources held by itself, appropriate NOFS/NOIO scope control is needed; casually changing everything to GFP_ATOMIC only shrinks the resource selection and does not solve dependency design issues. See Memory Allocation Guide.
5. How PCP Caches Reduce Lock Contention and Why They Affect Observation
Assume a CPU continuously allocates and releases many small-order pages. If every operation modifies the zone's global free structure, local operations become shared lock contention. PCP pagesets keep a batch of pages on the CPU side; common allocations take from the local batch cache, replenishing when exhausted; releases enter the local cache first, and are returned in batches only after reaching a threshold.
It spreads out global operations, which also means recently freed pages may not immediately appear in the buddy's high-order free lists. Only after PCP is drained can they merge with other buddies. When observing, distinguish between "system free pages" and "blocks listed by order in the current buddy." Which orders PCP supports and what local synchronization it uses vary by version and configuration; it cannot always be described as "only order 0, completely lock-free."
Migration Types: Grouping Similar Purposes Together
Unlike PCP, migration types primarily try to group allocations with similar migratability, preventing non-migratable objects from being scattered across every large range. RECLAIMABLE means the owning subsystem has the opportunity to reclaim objects; MOVABLE means the physical location can be changed while maintaining semantics. These two are not the same. Fallback and runtime pinned references can still weaken the initial classification effect.
6. Page Descriptors, References, and Memory Offlining
struct page is management metadata, not the page content itself. Field meanings vary by page purpose; normal RAM, compound pages, slab, and device pages should not be forced into a fabricated structure layout. Reference counting and mapping counts answer different questions: a page without user PTE mapping may still be held by the page cache or kernel; obtaining a page pointer does not automatically imply holding an infinitely extendable reference.
If the base page is 4 KiB, and assuming a page descriptor occupies 64 bytes in a certain configuration, this single item accounts for a ratio of 64/4096 = 1.5625%; this calculation is for understanding metadata scale and should not be written as a fixed size for all kernels. Large memory systems must also consider vmemmap organization, reserved areas, and other management structures.
Hotplug: Adding Range and Handing Over to Allocator Are Two Steps
Stage
Required State Change
add
Establish memory range and management metadata
online
Allow page allocator to use these resources
offline
Stop further allocation, and migrate or release occupied content
remove
Remove range and management state
MOVABLE increases offlining opportunities, but long-term pins, non-migratable content, or insufficient migration targets can still prevent offline. See Memory Hotplug.
7. Reading buddyinfo for Evidence, Not Success Guarantees
Each column is the number of blocks for that order, not the number of pages; you must multiply by 2^order and then by the base page size. The highest non-empty order only indicates that blocks of that order existed at the time of reading; concurrent allocations, watermarks, migration types, and node policies can still cause your request to fail. Only by combining zoneinfo watermarks/protections, process NUMA policies, and allocation context can you explain the cause of failure.
The next article, Slab and kmalloc, cuts a batch of physical pages into objects; Page Reclaim and Swap explains how the slow path turns used memory back into available resources.