还有八页空闲,却申请不到两页连续内存;某个节点资源充足,另一个受限申请仍失败。物理分配需要同时满足数量、形状、位置和等待条件 。
先在 16 页的伙伴模型中拆分、合并,再把 NUMA、zone、水位和 GFP 约束逐层加回来。
要解释的现象 阅读位置
order 到底表示多少内存? 第 1~2 节:伙伴模型与实验
有空闲页,为何当前申请不能用? 第 3~4 节:位置、水位与 GFP
刚释放的页,为什么 buddyinfo 没立即变化? 第 5 节:PCP 批量缓存
页面是否可回收、可下线? 第 6~7 节:引用、热插拔与观测
1. 从 order 理解申请形状
alloc_pages(gfp, order) 申请 2^order 个物理连续基础页,返回相应 struct page。基础页大小由架构与配置决定;若 P=4096 字节,order 0、1、4 分别是 4、8、64 KiB。order 不是字节数,也不是页数本身。
假设一段受管理范围只有 PFN 0 到 15,初始是一块 order 4 空闲块。现在申请 order 2,也就是 4 页。分配器不必把 16 页全交出去:先拆成 [0,8) 与 [8,16),再把前者拆成 [0,4) 与 [4,8),保留两块未用伙伴供后续分配。
请求四页:分裂的是连续块,保留的是未使用伙伴
order 4:PFN [0,16),共 16 页
order 3:[0,8)
order 3:[8,16) 空闲
[0,4)
交给调用者
[4,8)
order 2 空闲
释放 [0,4) 后,只有同阶伙伴 [4,8) 也空闲且允许合并,才可逐级恢复大块。
沿图继续做两次释放,合并条件就清楚了:
时刻 [0,4)[4,8)能否恢复 [0,8)
第二次申请四页后 使用中 使用中 不能
释放第一次申请 空闲 使用中 不能,伙伴未空闲
再释放第二次申请 空闲 空闲 可以,合成 order 3
若 [8,16) 也仍空闲,就能继续合并回最初的 order 4。
相邻不够:还要是同阶、对齐的伙伴
对于按 2^order 对齐的块起点 PFN,同阶伙伴起点可按 PFN XOR (1 << order) 计算。但得到一个数字只是找到候选:还需验证它属于可合并的管理范围、确实是同阶空闲块,并满足相关迁移类型等规则。两个在地址上相邻的块不必是这一阶的伙伴,例如 order 2 的 [4,8) 与 [8,12) 不能直接合成一个按 8 页对齐的 order 3 块。
为什么空闲八页仍可能拿不到两页
仍取 16 页,假设所有偶数 PFN 被占用,所有奇数 PFN 空闲。此时一半内存空闲,却没有任何两页连续空闲。总量足够,形状不满足。这是外部碎片,而不是申请者要求的字节数超过空闲总量。
如果占用页可迁移,内核可能把它们搬到一侧,腾出更大的连续范围;若关键位置被不可迁移对象或长期固定的页占据,整理未必成功。若业务根本不需要物理连续,就应从接口设计上避免过高 order,而不是要求分配器在任意碎片状态下制造连续区域。高 order 的上限也由目标内核配置决定,不应固定抄写 MAX_ORDER=11。
下面直接改变申请阶数与占用布局。空闲总量保持足够时,也可以通过一次申请失败看出缺少的究竟是哪一种形状。
正在呈现知识画面 重新加载
空闲页够多,为什么申请仍然失败? order k 需要按 2^k 对齐的连续 2^k 页。空闲总量不足以证明存在这种形状;释放后只有同阶、对齐的空闲伙伴才能合并。
{"id":"buddy-block-shapes","title":"空闲页够多,为什么申请仍然失败?","summary":"order k 需要按 2^k 对齐的连续 2^k 页。空闲总量不足以证明存在这种形状;释放后只有同阶、对齐的空闲伙伴才能合并。","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":"申请一块","allocated":"选中的连续块已交给调用者。其他空闲块的范围仍然保留,不能把整张图的空白面积都算成一块。","allocatedBlocks":"已分配的块","empty":"全部空闲","failed":"当前找不到所需的对齐连续块。即使空闲页总数足够,也无法直接满足这个 order。","fragment":"交错占用","fragmentNote":"八个奇数页空闲,但没有一对连续空闲页。尝试 order 1,再释放一个相邻占用块,观察形状改变。","freeLists":"按 order 组织的空闲块","freePages":"空闲页总数","freed":"已按原申请大小释放,再逐阶合并可用伙伴。相邻的范围不一定是同阶、对齐的伙伴。","heading":"空闲页够多,为什么申请仍然失败?","initial":"从较高阶块拆分到申请大小;没被取走的伙伴仍在空闲表中。","intro":"选择申请的阶数并分配;点击已占用的块可释放整次分配。对比交错布局,观察各阶空闲块怎样拆分和合并。","largest":"最大空闲块 / 页","layout":"选择布局快照","order":"申请 order","physical":"物理页:点击占用块以整块释放","release":"释放所选块","reset":"重置","scope":"16 页单一管理区、无并发,基础页 4 KiB;按较低 PFN 优先选择。空闲表显示完整合并后的理想状态,省略水位、NUMA、迁移类型和 PCP。释放的是原分配整块,不任意释放高阶分配内部的单页。布局预设会重建模型,不搬迁真实对象。","scopeLabel":"模型范围与约定"}}
2. 一个可执行的伙伴模型
下面只管理 Python 整数表示的 16 页,不分配或移动真实物理内存。它展示拆分、同阶合并和分配记录如何互相约束,省略 NUMA、水位、PCP 与并发同步。
free = [ set ( ) for _ in range ( 5 ) ]
free [ 4 ] . add ( 0 )
allocated = { }
def alloc ( order ) :
if not 0 <= order < len ( free ) :
raise ValueError ( " invalid order" )
source = next ( ( n for n in range ( order , len ( free ) ) if free [ n ] ) , None )
if source is None :
return None
start = min ( free [ source ] )
free [ source ] . remove ( start )
while source > order :
source -= 1
free [ source ] . add ( start + ( 1 < < source ) )
allocated [ start ] = order
return start
def release ( start ) :
order = allocated . pop ( start ) while order + 1 < len ( free ) :
buddy = start ^ ( 1 < < order )
if buddy not in free [ order ] :
break
free [ order ] . remove ( buddy )
start = min ( start , buddy )
order += 1
free [ order ] . add ( start )
a , b = alloc ( 2 ) , alloc ( 2 )
assert ( a , b ) == ( 0 , 4 )
assert free [ 3 ] == { 8 }
release ( a )
assert free [ 2 ] == { 0 }
release ( b )
assert free [ 4 ] == { 0 } and not allocated
assert all ( not blocks for blocks in free [ : 4 ] )
print ( " 分裂、伙伴阻塞与逐级合并:通过" )
真实分配器必须在锁和记账协议下维护这些不变量,不能把模型中的 set 操作直接替换为无锁链表后使用。释放原始高阶申请时也要遵守对应 API 的阶数和引用契约,不能自行把其中任意页当独立分配结果释放。
3. Node 和 zone 给“可用”增加了位置条件
NUMA node 表达内存访问位置与代价,zone 表达同一节点内不同的使用约束。节点不一定等于 CPU socket:系统可以有只含内存的节点、跨多段物理地址的节点,单插槽机器也可能暴露 NUMA 拓扑。远端访问代价取决于硬件、访问模式和拥塞,不能固定写成慢 30%。
普通节点本地分配偏好可以减少远端访问,但用户内存策略、cpuset 和申请标志会影响允许集合及回退。应用只把线程绑到节点 0,而其已存在页面仍在节点 1,不会因为线程迁移就立即自动搬完所有内容。“first touch”也应理解为实际建立后备页时的放置行为,不能把 malloc 返回时刻当作所有页的物理归属时刻。
zone 作用 需要保留的区分
DMA / DMA32 特定架构的寻址约束范围 边界依架构;设备 DMA 应使用 DMA API 表达实际约束
NORMAL 内核可常规访问的内存 仍受节点、水位与分配用途限制
HIGHMEM 部分 32 位配置没有永久直接映射的内存 不是“内存占用高”的意思
MOVABLE 尽量容纳可迁移内容,改善整理/下线能力 不是任意内核对象都可放入,也不是绝对能下线
DEVICE 为某些设备内存提供 page 等管理服务 不等于普通 buddy 管理的通用 RAM
在常见 x86 配置中 DMA/DMA32 与低地址范围有关,但“申请 DMA32 内存”不能替代设备 DMA mask、IOMMU 映射及一致性处理。CXL 提供的某段内存也可能作为普通 System RAM 使用,不是看见 CXL 就一定属于 ZONE_DEVICE。拓扑与结构定义见 Physical Memory 。
4. 水位不是三条固定的故障开关
空闲页保留一部分余量,是为了让突发申请及负责回收的路径本身还能取得资源。若普通申请耗尽所有页,回收所需的 I/O 或元数据申请也可能无法推进,形成更难解的循环。
水位 在简化模型中的作用
low 附近的平衡条件触发后台回收推进
high 常用的后台恢复目标
min 参与紧急保留及更严格的准入判断
实际检查还包括 order、低 zone 保护、不可用空闲页和 watermark boost。它们不是三个固定开关,不能直接推出“低于 low 必直接回收、低于 min 必 OOM”。
例如 NORMAL 有 1200 个空闲页,min/low/high 假设为 1000/1500/2000。这个状态提示后台平衡应推进,但不能仅凭 1200 就决定 order 4 一定成功:可能找不到连续 16 页,也可能申请受另一个节点或 zone 的约束。反过来,某些不能等待的申请失败时会直接返回,不会替调用者进行同步回收。
一次分配:逐层收窄允许使用的资源
申请形状
order、连续性、用途与调用上下文
1
节点和 zone 选择
NUMA 策略、cpuset 与寻址约束
2
尝试现成资源
PCP / buddy;同时满足水位和保留规则
3
慢路径按允许条件推进
回收、整理、重试或节点回退
4
成功或返回失败
可睡眠不等于必成功;紧急分配也有资源上限
5
这里表示约束关系;具体慢路径顺序依内核、order 和 GFP 而变。
GFP:调用者允许分配器做多少工作
标志 允许行为的重点 调用者仍须处理
GFP_KERNEL 合适上下文中可睡眠并回收 申请失败与清理
GFP_NOWAIT 避免直接回收造成等待 更受限的可用资源
GFP_ATOMIC 不能睡眠的重要申请有不同的保留访问条件 储备耗尽;不适用于任意 NMI 路径
GFP 是行为约束,不是成功率承诺。
__GFP_RECLAIM 包含直接回收与 kswapd 相关许可,不是 __GFP_DIRECT_RECLAIM 的简单新名字;__GFP_NORETRY 也不是完全禁止任何轻量回收尝试。文件系统或块 I/O 内部若回收可能重入自己持有的资源,需要合适的 NOFS/NOIO 范围控制;随手全改成 GFP_ATOMIC 只是缩小资源选择,未解决依赖设计。见 内存分配指南 。
5. PCP 缓存如何降低锁竞争,又为何影响观测
假设一个 CPU 连续申请、释放很多小阶页,每次都去修改 zone 的全局空闲结构,会把本地操作变成共享锁竞争。PCP pagesets 将一批页留在 CPU 侧,常见申请从本地批量缓存取,耗尽时再补充;释放先进入本地缓存,达到阈值后再批量归还。
它将全局操作摊薄,也意味着刚释放的页未必立即出现在 buddy 的高阶空闲链表里。PCP 排空后才可能与其他伙伴合并,观测时要分清“系统空闲页”与“当前 buddy 按阶列出的块”。PCP 支持哪些阶、采用哪种本地同步依版本与配置变化,不能永远描述成“只有 order 0,完全无锁”。
迁移类型:把相近用途放在一起
与 PCP 不同,迁移类型主要试图把具有相近可迁移性的分配聚在一起,避免不可迁移对象散落在每个大范围中。RECLAIMABLE 表示所属子系统有机会回收对象,MOVABLE 表示可以改变物理位置而维持语义,二者不是一回事。回退与运行时固定引用仍可削弱最初的分类效果。
6. page 描述符、引用和内存下线
struct page 是管理元数据,不是页内容本身。字段含义随页用途而变,普通 RAM、compound page、slab 和设备页也不应硬套一个伪造结构布局。引用计数与映射计数回答不同问题:没有用户 PTE 映射的页仍可能被页缓存或内核持有;拿到一个 page 指针也不自动代表拥有可无限延长的引用。
若基础页为 4 KiB,假设某配置的 page 描述符占 64 字节,单看这一项就是 64/4096=1.5625% 的比例;这个计算用于理解元数据规模,不能把 64 字节写成所有内核的固定大小。大型内存系统还要考虑 vmemmap 组织、保留区和其他管理结构。
热插拔:加入范围与交给分配器是两步
阶段 必须完成的状态变化
add 建立内存范围及管理元数据
online 让页分配器可以使用这些资源
offline 停止继续分配,并迁移或释放占用内容
remove 移除范围与管理状态
MOVABLE 提高下线机会,但长期 pin、不可迁移内容或迁移目标不足仍可能阻止 offline。见 内存热插拔 。
7. 读 buddyinfo,得到证据而非成功保证
from pathlib import Path
import os
import re
page_size = os . sysconf ( " SC_PAGE_SIZE" )
for line in Path ( " /proc/buddyinfo" ) . read_text ( ) . splitlines ( ) :
match = re . match ( r " Node\s + ( \d + ) ,\s + zone\s + ( \S + ) \s + ( . * ) " , line )
if not match :
continue
node , zone , rest = match . groups ( )
blocks = [ int ( value ) for value in rest . split ( ) ]
pages = sum ( count * ( 1 < < order ) for order , count in enumerate ( blocks ) )
highest = max ( ( i for i , count in enumerate ( blocks ) if count ) , default = None )
print ( f " node= { node } zone= { zone } buddy_bytes= { pages * page_size } "
f " highest_nonempty_order= { highest } " )
每列是该 order 的块数,不是页数,要乘 2^order 再乘基础页大小。最高非空阶只说明读取时存在该阶块;并发申请、水位、迁移类型和节点策略仍可使你的请求失败。结合 zoneinfo 的水位/保护、进程 NUMA 策略与分配上下文,才能解释失败原因。
下一篇 Slab 与 kmalloc 把一批物理页切成对象;页面回收与交换 则解释慢路径怎样把已用内存重新变成可用资源。