空きが8ページあるのに、連続する2ページのメモリを確保できません。あるノードはリソースが十分なのに、別のノードでは制限のある割り当て要求が失敗します。物理的な割り当てには、数量、形状、位置、待機条件 を同時に満たす必要があります。
まず16ページのバディモデルで分割とマージを行い、その後、NUMA、ゾーン、水位、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の16ページしかない場合、最初は order 4 の空きブロックです。ここで order 2(つまり4ページ)を要求します。アロケータは16ページすべてを渡す必要はありません:まず [0,8) と [8,16) に分割し、前者をさらに [0,4) と [4,8) に分割します。その後続の割り当てのために、2つの未使用のバディを保持します。
リクエスト4ページ:分割されるのは連続ブロック、保持されるのは未使用のバディ
order 4:PFN [0,16)、合計16ページ
order 3:[0,8)
order 3:[8,16) 空き
[0,4)
呼び出し元に渡す
[4,8)
order 2 空き
[0,4) を解放した後、同じ階のバディ [4,8) も空きで、マージが許可されている場合にのみ、段階的に大きなブロックに戻せます。
図に従って2回の解放を続けると、マージ条件が明確になります。
時点 [0,4)[4,8)[0,8) を回復できるか
2回目の4ページ要求後 使用中 使用中 できない
1回目の要求を解放 空き 使用中 できない、バディが空きではない
2回目の要求をさらに解放 空き 空き できる、order 3 に合成
[8,16) もまだ空きであれば、最初の order 4 にさらにマージできます。
隣接だけでは不十分:同じ階で、アライメントされたバディでなければならない
2^order でアライメントされたブロックの起点 PFN に対して、同じ階のバディの起点は PFN XOR (1 << order) で計算できます。しかし、数値を得ることは候補を見つけるにすぎません:それがマージ可能な管理範囲に属し、実際に同じ階の空きブロックであり、関連するマイグレーションタイプなどのルールを満たしていることを検証する必要があります。アドレス上で隣接している2つのブロックが、必ずしもこの階のバディであるとは限りません。例えば、order 2 の [4,8) と [8,12) は、直接 8ページでアライメントされた order 3 ブロックに合成することはできません。
なぜ空きが8ページあっても2ページ取得できないのか
引き続き16ページを考え、偶数PFNがすべて占有され、奇数PFNがすべて空きであると仮定します。この場合、メモリ半分が空きですが、連続する2ページの空きはありません。総量は十分ですが、形状が満たされていません。これは外部フラグメンテーションであり、要求者が要求したバイト数が空き総量を超えているわけではありません。
占有ページがマイグレーション可能であれば、カーネルはそれらを片側に移動させて、より大きな連続範囲を空けることができます。重要な位置にマイグレーション不可能なオブジェクトや長期間固定されたページが占有されている場合、整理は成功しないかもしれません。ビジネスロジックが物理的に連続していることを根本的に必要としないのであれば、アロケータに任意のフラグメンテーション状態から連続領域を生成するよう要求するのではなく、インターフェース設計において高い order を避けるべきです。高い order の上限はターゲットカーネルの設定によって決まるため、MAX_ORDER=11 を固定してコピーしてはいけません。
以下では、要求する階数と占有レイアウトを直接変更します。空き総量が十分であっても、1回の要求失敗から、欠けているのがどのような形状なのかを見極めることができます。
図を準備しています 再読み込み
空きページは十分なのに、なぜ要求は失敗するのか? 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":"1ブロックを要求","allocated":"選択された連続ブロックは呼び出し元に渡されました。他の空きブロックの範囲は依然として保持されており、図全体の空白面積をすべて1つのブロックとして数えてはいけません。","allocatedBlocks":"割り当て済みのブロック","empty":"すべて空き","failed":"現在、必要なアライメントされた連続ブロックが見つかりません。空きページの総数が十分であっても、この order を直接満たすことはできません。","fragment":"インターリーブ占有","fragmentNote":"8つの奇数ページが空きですが、連続する空きページのペアはありません。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 ノードはメモリアクセスの位置とコストを表し、ゾーンは同一ノード内の異なる使用制約を表します。ノードが必ずしもCPUソケットに対応するわけではありません。システムにはメモリのみを含むノードや、複数の物理アドレス範囲にまたがるノードが存在でき、単一ソケットマシンでもNUMAトポロジを公開する可能性があります。遠端へのアクセスコストはハードウェア、アクセスパターン、輻輳度によって異なり、固定して「30%遅い」と記述することはできません。
通常ノードでのローカル割り当ての優先度は遠端アクセスを減らしますが、ユーザーのメモリポリシー、cpuset、要求フラグが、許可される集合とフォールバックに影響を与えます。アプリケーションがスレッドをノード0にバインドしても、すでにノード1に存在するページは、スレッドの移動に伴って自動的にすべて移動するわけではありません。「first touch」は、実際にはバックアップページが作成された時点での配置動作と理解すべきであり、malloc の返却時刻をすべてのページの物理的な帰属時刻と見なしてはいけません。
zone 役割 区別を保持する必要がある点
DMA / DMA32 特定のアーキテクチャのアドレス指定制約範囲 境界はアーキテクチャ依存;デバイスDMAは実際の制約を表現するためにDMA APIを使用すべき
NORMAL カーネルが通常アクセスできるメモリ ノード、水位、割り当て用途の制約を依然として受ける
HIGHMEM 一部の32ビット構成では永久直接マッピングがないメモリ 「メモリ使用量が高い」という意味ではない
MOVABLE できるだけマイグレーション可能なコンテンツを収容し、整理/オフライン化能力を改善 任意のカーネルオブジェクトを配置できるわけでも、絶対的にオフライン化できるわけでもない
DEVICE 一部のデバイスメモリに対して page などの管理サービスを提供 通常のバディ管理された汎用RAMとは異なる
一般的なx86構成では、DMA/DMA32 は低アドレス範囲に関連していますが、「DMA32メモリを要求する」ことは、デバイスのDMAマスク、IOMMUマッピング、一貫性処理に取って代わるものではありません。CXL が提供するある範囲のメモリは、通常の System RAM として使用される可能性もあり、CXL が見えたら必ずしも ZONE_DEVICE に属するわけではありません。トポロジと構造の定義は Physical Memory を参照してください。
4. 水位は3つの固定された故障スイッチではない
空きページの一部分は余量として保持されており、これは突発的な要求や、回収を担当するパス自体がリソースを確保できるようにするためです。通常の要求がすべてのページを使い果たすと、回収に必要なI/Oやメタデータ要求も進行できなくなり、より解きにくい循環状態に陥る可能性があります。
水位 簡易モデルでの役割
low 近傍のバランス条件がトリガーされ、バックグラウンド回収が推進される
high 一般的なバックグラウンド回復の目標
min 緊急時の保留およびより厳格なアクセス判断に参加
実際には、order、低ゾーン保護、利用不可の空きページ、watermark boost も含まれます。これらは3つの固定スイッチではなく、「low 以下なら必ず直接回収、min 以下なら必ず OOM」と単純に導き出すことはできません。
例えば、NORMAL に空きページが1200個あり、min/low/high がそれぞれ 1000/1500/2000 と仮定します。この状態はバックグラウンドバランスを推進すべきことを示唆していますが、1200 という数字だけで order 4 が必ず成功すると決定することはできません:連続する16ページが見つからない場合や、別のノードやゾーンの制約によって要求が制限される可能性があります。逆に、待機できない要求が失敗した場合、呼び出し元に代わって同期的な回収を行うことなく、直接戻ることがあります。
1回の割り当て:段階的に使用可能なリソースを狭める
要求形状
order、連続性、用途、呼び出しコンテキスト
1
ノードとゾーンの選択
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が連続して多くの小階ページを要求・解放する場合、毎回ゾーン全体のグローバル空き構造を変更すると、ローカル操作が共有ロック競合に変わってしまいます。PCP pagesets は、一批のページをCPU側に保持し、一般的な要求はローカルのバッチキャッシュから取得し、枯渇時に補充します。解放はまずローカルキャッシュに入り、閾値に達してからバッチで返却されます。
これによりグローバル操作が分散されるため、直前に解放したページがすぐにバディの高位の空きリストに表示されるとは限りません。PCP が排空されて初めて、他のバディとマージされる可能性があります。観測時には、「システム全体の空きページ」と「現在のバディが階ごとに列挙したブロック」を明確に区別する必要があります。PCP がどの階をサポートし、どのようなローカル同期を採用するかは、バージョンと設定によって変化するため、「order 0 のみで完全にロックフリー」と永遠に記述することはできません。
マイグレーションタイプ:類似した用途を一緒にまとめる
PCP とは異なり、マイグレーションタイプは、主にマイグレーション可能性が類似した割り当てをグループ化し、マイグレーション不可能なオブジェクトが広い範囲の各所に散らばるのを防ごうとします。RECLAIMABLE は、所属するサブシステムがオブジェクトを回収する機会があることを示し、MOVABLE は物理的な位置を変更してもセマンティクスを維持できることを示します。これらは同じものではありません。フォールバックやランタイムでの固定参照は、当初の分類効果を弱める可能性があります。
6. page 記述子、参照、メモリオフライン化
struct page は管理メタデータであり、ページの内容そのものではありません。フィールドの意味はページの用途によって異なり、通常のRAM、複合ページ、slab、デバイスページに対して、偽造された構造レイアウトを無理やり適用すべきではありません。参照カウントとマッピングカウントは異なる質問に答えます:ユーザーPTEマッピングがないページでも、ページキャッシュやカーネルによって保持されている可能性があります。page ポインタを取得したからといって、自動的に無制限に延長可能な参照を持つことを意味しません。
基本ページが4 KiB で、ある構成で page 記述子が64バイトを占めると仮定すると、この項目だけでも 64/4096=1.5625% の比率になります。この計算はメタデータの規模を理解するためのものであり、64バイトをすべてのカーネルの固定サイズとして記述してはいけません。大規模メモリシステムでは、vmemmap の構成、保留領域、その他の管理構造も考慮する必要があります。
ホットプラグ:範囲の追加とアロケータへの委譲は2つのステップ
段階 完了すべき状態変化
add メモリ範囲と管理メタデータの構築
online ページアロケータがこれらのリソースを使用可能にする
offline 追加の割り当てを停止し、占有コンテンツを移動または解放する
remove 範囲と管理状態の削除
MOVABLE はオフライン化の機会を高めますが、長期ピン、マイグレーション不可能なコンテンツ、またはマイグレーション先が不足していると、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 では、一批の物理ページをオブジェクトに分割します。ページ回収とスワップ では、スローパスがどのように使用中メモリを再び利用可能なリソースに変えるかを説明します。