3ページのマッピングの中央の1ページのみをリードオンリーに変更すると、maps には2つの新しいエントリが現れることがあります。中央のページを解除すると、残るホール(隙間)は PROT_NONE とは異なります。ここでは同じ3ページのアドレス空間 を追跡し、範囲がどのように変化するかを確認した上で、競合する検索がこれらの変更をどのように保護するかを説明します。
説明すべき現象 読むべき場所
mmap が成功してもなぜページフォルトが発生するのか? 第1節:VMA、ページテーブル、バックストア
権限変更、マッピング解除、アドレス移動はそれぞれ何を変えるのか? 第2~3節:3ページ推論と実験
Maple Tree はどこが速く、省略できない作業は何か? 第4節:範囲クエリ
VMA を見つけた直後に、別のスレッドが munmap を実行したらどうするか? 第5~6節:同期とページフォルト処理
1. VMA は範囲を記述し、ページテーブルはその範囲内の現在の変換を記述する
通常の VMA は半開区間 [vm_start, vm_end) をカバーし、始点を含み終点を含みません。同じ mm_struct 内の通常の VMA は互いに重なり合いませ ん。アドレス空間を共有するスレッドは通常この mm を共有参照するため、1つのスレッドによる mprotect は、他のスレッドが見るマッピング権限を変更します。
手計算:VMA 内のアドレスがファイルのどこに対応するか
基本ページを P、マッピング開始アドレスを A、vm_pgoff=8 とします。アドレス A + 2P + 17 について、2つのステップで位置を特定します。
ファイルの8番目の基本ページからマッピングが始まり、さらに2ページ進むと、ファイルの10番目のページになります。
ページ内のオフセット 17 を保持し、ファイルのバイトオフセットは 10P + 17 となります。
vm_pgoff の単位は基本ページであり、バイトではありません。また、このファイル記述子以降の read/seek によって進みません。
階層 この例での記録内容 直接答えていないこと
VMA 範囲 A 始まり、マッピング対象ファイル、開始ファイルオフセット、共有/プライベートおよび権限 各ページがメモリに存在するか、物理ページ番号は何か
ページテーブル 確立された仮想ページから物理ページ/ソフトウェア状態へのマッピング 未アクセスの将来のアドレスからどのバックエンドでコンテンツを取得するか
ファイルページキャッシュ ファイルの各範囲の現在のメモリ内コンテンツ どのプロセスが対応する PTE をインストールしたか
rmap などの構造 ページまたはバックストアオブジェクトから関連する可能性のあるマッピングを逆引き 単独でアドレス空間の前方検索を代替できない
ファイルの MAP_PRIVATE VMA は、書き込み後、変更されていないファイルキャッシュページと分離された匿名 COW ページの両方を同時にカバーする可能性があるため、vm_file != NULL は「すべてのダーティページがこのファイルに書き戻される」ことを意味しません。通常の共有匿名マッピングは、カーネル内でも shmem ファイルオブジェクトによって支えられている場合があり、ユーザーフラグの MAP_ANONYMOUS と内部 vm_file が NULL かどうかは単純な1対1対応ではありません。
2. 3ページのマッピングで4つの操作を推論する
同じ3ページのマッピング:権限変更はまず範囲記述を変更する
初期状態
中央ページをリードオンリーに
中央ページをマッピング解除
[A, A + 3P) rw-p
互換性のある1つのアドレス範囲
rw-p
r--p
rw-p
rw-p
ホール:VMAなし
rw-p
P は基本ページサイズ。仮想アドレスが連続していても物理的に連続しているわけではない。1回のシステムコールが必ずしも恒久的なVMAオブジェクトに対応するわけではない。
mmap:まずアドレス空間にコミットメントを作成する
通常の固定されていない mmap はアドレスのヒントを提供できますが、カーネルは既存のマッピングを上書きしない範囲で利用可能な範囲を選択し、権限、ファイル能力、オフセット、リソース制限などのチェックを行い、マッピング関係を確立します。新しい領域が隣接する領域とマージ条件を満たす場合、既存の VMA を直接拡張することがあるため、「1回の mmap で1つの恒久的に識別可能な VMA オブジェクトが作成される」とは限りません。
MAP_FIXED の意味は異なります。これは指定された範囲の使用を要求し、既存のマッピングを置き換えることができます。それは通常のヒントが失敗した場合の無害な強化版ではありません。「このアドレス範囲が完全に空いている場合にのみ占有したい」場合は、MAP_FIXED_NOREPLACE と戻り値のアドレスチェックを理解してください。maps を読んでホールを見つけ、その間に誰も占有していないと仮定して MAP_FIXED を使用しないでください。
通常、データページは必要に応じて作成されますが、MAP_POPULATE、ロック/プリフェッチの要件、デバイスマッピングなどのパスでは追加の作業が行われる可能性があります。ファイル mmap の作成は file_operations の関連インターフェースを介して接続され、vm_operations_struct の fault などのコールバックは後のマッピング操作に使用されます。汎用の vm_ops->mmap() を発明して「すべてのファイルがこれを呼び出す」と主張してはいけません。mmap(2) を参照してください。
mprotect:なぜ1ページだけを変更するのに、2つの記述オブジェクトが増えるのか
[A,A+3P) が元々読み書き可能だったと仮定します。[A+P,A+2P) をリードオンリーに変更すると、これら3セグメントはもはや同じ属性を持たないため、元の1つの VMA では正確に記述できず、前・中・後の3つに分割する必要があります。カーネルは既存のページテーブルに権限変更を適用し、要求に応じて TLB を維持する必要があります。メモリに存在しないページ部分は、新しい VMA のセマンティクスによって将来のページフォルトが制約されます。
これは一見不自然な失敗を説明しています。データメモリを増やすことを要求していないにもかかわらず、mprotect は VMA メタデータの追加が必要になったり、マッピング数の制限を超えたりすることで失敗する可能性があります。「PTE の1ビットを変更するだけ」ではこの現象を説明できません。中央のページを読み書き可能に戻すと、互換性のある隣接セグメントが再結合する可能性がありますが、マージにはバックストアオブジェクト、オフセットの連続性、匿名逆マッピング関係、その他の属性のチェックが必要であり、maps に表示される rw-p の4文字だけを比較することはできません。mprotect(2) を参照してください。
munmap:解除されるのはマッピングであり、データ自体を破棄する必要はない
中央のページを解除すると、中央は真のホールになります。アクセス禁止と比較すると、アドレス空間の占有関係が変化します。
操作 中央範囲にマッピングセマンティクスが残っているか? アドレス空間は依然として占有されているか?
PROT_NONE に変更 はい、ただし現在アクセス禁止 はい
中央ページを munmap いいえ、ホールになる いいえ、後続の割り当てに使用可能
munmap は変換の無効化と対応する参照の解放も行いますが、このステップはデータページの最終寿命を直接決定するものではありません。
ページが他のプロセスと共有されている場合、または通常のファイルページがまだページキャッシュにある場合、このプロセスのマッピング解除は、それを直接 buddy の空きページにはしません。逆に、物理ページがすでに回収されてスワップアウトされている場合、有効な VMA は存在し続けることができます。VMA の寿命、ページテーブルエントリの寿命、およびデータページの寿命は、それぞれ別に推論する必要があります。
mremap:アドレスは可変だが、データはバイト単位でコピーする必要はない
マッピングを拡張する際、隣接範囲が使用できない場合、移動可能な mremap は新しい位置を選択し、ページテーブルマッピングなどの状態を移動することができます。これにより、ユーザーデータの複製を回避できる場合もありますが、メタデータ、ページテーブル、無効化のコストは依然として存在し、すべてのマッピングを自由に移動できるわけではありません。移動に成功すると、旧アドレスおよびそこに保存された絶対自己参照ポインタは、アプリケーションの設計に従って処理する必要があります。「コンテンツが残っている」からといって「すべての元のポインタが有効である」とは限りません。
3つの寿命を同じ画面に置きます。中央のページを最初に読み取り、その後マッピングを解除し、どの範囲、PTE、キャッシュが実際に消去されるかを確認します。マッピングの復元とアクセス権限の復元は、それぞれ別個に操作することもできます。
file_offset = P vm_pgoff + ( a − vm_start )
この式は、この VMA 内のファイルアドレスに使用されます。a はまず範囲とアクセス条件を満たす必要があり、ホールは計算に含まれません。
図を準備しています 再読み込み
1ページの権限を変更すると、どのオブジェクトが一緒に変化するのか? mprotect は範囲を分割できるが、データページを割り当てる必要はない。PROT_NONE は依然として仮想範囲を占有し、munmap はホールを残す。マッピングの取り消しも、ファイルキャッシュを破棄する必要はない。
{"id":"vma-range-edits","title":"1ページの権限を変更すると、どのオブジェクトが一緒に変化するのか?","summary":"mprotect は範囲を分割できるが、データページを割り当てる必要はない。PROT_NONE は依然として仮想範囲を占有し、munmap はホールを残す。マッピングの取り消しも、ファイルキャッシュを破棄する必要はない。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><p class=\"caption\" data-i18n=\"select\"></p><div class=\"page-strip\"><button data-page=\"0\"><strong>0P</strong><span id=\"prot-0\"></span></button><button data-page=\"1\"><strong>1P</strong><span id=\"prot-1\"></span></button><button data-page=\"2\"><strong>2P</strong><span id=\"prot-2\"></span></button></div><div class=\"actions\"><button id=\"rw\" data-i18n=\"rw\"></button><button id=\"r\" data-i18n=\"readOnly\"></button></div><div class=\"actions\"><button id=\"none\" data-i18n=\"none\"></button><button id=\"unmap\" data-i18n=\"unmap\"></button></div><div class=\"actions\"><button id=\"read\" data-i18n=\"read\"></button><button id=\"map\" data-i18n=\"map\"></button></div><p class=\"caption\" data-i18n=\"ranges\"></p><div id=\"ranges\" class=\"range-ribbon\"></div><div class=\"pte-strip\"><div id=\"pte-0\"></div><div id=\"pte-1\"></div><div id=\"pte-2\"></div></div><p class=\"caption\" data-i18n=\"cache\"></p><div class=\"cache-strip\"><div id=\"cache-0\"><strong>file[8]</strong><span></span></div><div id=\"cache-1\"><strong>file[9]</strong><span></span></div><div id=\"cache-2\"><strong>file[10]</strong><span></span></div></div><div class=\"metrics\"><div><span data-i18n=\"count\"></span><strong id=\"vma-count\"></strong></div><div><span data-i18n=\"resident\"></span><strong id=\"pte-count\"></strong></div></div><div class=\"formula\" id=\"file-offset\"></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=\"protected\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"unmapped\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"loaded\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"denied\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"mapped\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></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.page-strip,.pte-strip,.cache-strip{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:7px;margin:12px 0}.page-strip button{display:block;padding:10px 3px;min-width:0;font-size:12px;min-height:5.5em}.page-strip strong,.page-strip span{display:block}.page-strip button.chosen{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}.page-strip button.hole{border-style:dashed;color:var(--muted)}.range-ribbon{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:3px;margin:12px 0;min-height:5.6em;font-size:12px}.range-ribbon div{border:1px solid var(--accent);background:var(--accent-soft);padding:10px 5px;overflow-wrap:anywhere}.range-ribbon div.hole{border-style:dashed;border-color:var(--rule);background:var(--surface);color:var(--muted)}.pte-strip>div{min-width:0;padding:10px 4px;background:var(--surface);border-radius:6px;font:11px/1.6 var(--viz-font-mono);white-space:pre-line;min-height:6em;overflow-wrap:anywhere}.cache-strip>div{border:1px dashed var(--rule);padding:10px 4px;border-radius:6px;min-width:0;font-size:11px;min-height:8em;text-align:center}.cache-strip>div.live{background:var(--second-soft);border-color:var(--second)}.cache-strip strong{font-family:var(--viz-font-mono);font-weight:500;overflow-wrap:anywhere}.cache-strip span{display:block;margin-top:8px}.formula{min-height:6em}","js":"const $=s=>document.querySelector(s);let chosen=1,prot=['RW','RW','RW'],pte=[false,false,false],cache=[false,false,false],message='initial';function draw(){const ranges=[];for(let i=0;i<3;i++){if(i&&prot[i]===prot[i-1])ranges.at(-1).end=i+1;else ranges.push({start:i,end:i+1,p:prot[i]});}for(let i=0;i<3;i++){const b=document.querySelector('[data-page=\"'+i+'\"]');b.classList.toggle('chosen',i===chosen);b.classList.toggle('hole',prot[i]===null);b.setAttribute('aria-current',String(i===chosen));$('#prot-'+i).textContent=prot[i]??viz.t('hole');$('#pte-'+i).textContent='VA '+i+'P\\nPTE '+(pte[i]?prot[i]:'—');$('#cache-'+i).classList.toggle('live',cache[i]);$('#cache-'+i+' span').textContent=viz.t(cache[i]?'cached':'empty');}$('#ranges').replaceChildren(...ranges.map(r=>{const e=document.createElement('div');e.style.gridColumn=(r.start+1)+' / '+(r.end+1);e.textContent='['+r.start+','+r.end+') '+(r.p??viz.t('hole'));e.classList.toggle('hole',r.p===null);return e;}));$('#vma-count').textContent=ranges.filter(r=>r.p!==null).length;$('#pte-count').textContent=pte.filter(Boolean).length;for(const id of ['rw','r','none','unmap'])$('#'+id).disabled=prot[chosen]===null;$('#map').disabled=prot[chosen]!==null;$('#file-offset').textContent=prot[chosen]===null?'VA → '+viz.t('hole'):'VA = A + '+chosen+'P + 17\\nfile offset = (8 + '+chosen+') × 4096 + 17 = '+((8+chosen)*4096+17);$('#explanation').textContent=viz.t(message);}document.querySelectorAll('[data-page]').forEach(e=>e.addEventListener('click',()=>{chosen=+e.dataset.page;draw();}));for(const [id,p]of [['rw','RW'],['r','R'],['none','NONE']])$('#'+id).addEventListener('click',()=>{prot[chosen]=p;message='protected';draw();});$('#map').addEventListener('click',()=>{if(prot[chosen]!==null)return;prot[chosen]='RW';message='mapped';draw();});$('#unmap').addEventListener('click',()=>{prot[chosen]=null;pte[chosen]=false;message='unmapped';draw();});$('#read').addEventListener('click',()=>{if(prot[chosen]===null||prot[chosen]==='NONE')message='denied';else{pte[chosen]=cache[chosen]=true;message='loaded';}draw();});$('#reset').addEventListener('click',()=>{prot=['RW','RW','RW'];pte=[false,false,false];cache=[false,false,false];message='initial';draw();});draw();","audio":false,"strings":{"cache":"同じファイルのページキャッシュ","cached":"キャッシュ済み","count":"示意VMA数","denied":"そのアドレスはホールであるか、PROT_NONE により読み取りが禁止されているため、通常のオンデマンド読み込みとして扱えない。","empty":"キャッシュなし","heading":"1ページの権限を変更すると、どのオブジェクトが一緒に変化するのか?","hole":"ホール","initial":"初期状態では読み書き可能なVMAが1つあるが、データPTEはまだない。ファイル範囲と確立されたハードウェア変換は別物である。","intro":"3ページのいずれかのページを選択し、権限を変更、読み取り、またはマッピング解除を行う。範囲ストリップ、PTE、ファイルキャッシュを照らし合わせ、3つの異なる寿命を観察する。","loaded":"アクセスによりファイルページコンテンツが取得され、このマッピングの PTE が確立される。再度読み取ると再利用できる。ページ権限の変更はファイルオフセットを移動しない。","map":"このページを再マッピング","mapped":"このページは読み書きマッピングを再構築したが、データPTEはまだインストールされていない。既存のファイルキャッシュは引き続き再利用可能である。","none":"PROT_NONE に設定","protected":"権限変更後、互換性のある範囲で再編成される。データは積極的に読み取られない。PROT_NONE は依然としてアドレス空間を占有するマッピングの1つである。","ranges":"VMA範囲 / P","read":"選択したページを読み取る","readOnly":"読み取り専用に設定","reset":"リセット","resident":"インストールされたPTE","rw":"読み書きに設定","scope":"3ページは同じ通常のファイルに属し、基本ページ P=4096、vm_pgoff=8。読み取りのみを行い、ファイルは変更しない。キャッシュを保持し、回収は行わない。隣接セグメントは権限と連続するバックストアのみでマージされるという点は簡略化であり、実際の VMA マージは他の属性にも制限される。マッピング解除されたページの再構築は新しい示意マッピングであり、実際の MAP_FIXED は使用しない。","scopeLabel":"モデル範囲と規約","select":"操作するページを選択","unmap":"マッピング解除","unmapped":"このページはホールとなり、PTE は取り消される。以前に読み込まれたファイルキャッシュは残る可能性があるが、それはこの VMA が独占するものではない。"}}
3. 実行可能な実験:これらの境界を直接観察する
以下では、5ページの PROT_NONE 空間を保持し、中央の3ページを実験用に使用し、両側をアドレス境界として使用します。各ステップで自身の maps を読み取り、指定されたアドレスの属性を検証します。リードオンリーページの書き込みアドレスにアクセスしたり、プロセスのクラッシュを引き起こしたりしません。
#define _GNU_SOURCE
#include < assert.h>
#include < inttypes.h>
#include < stdint.h>
#include < stdio.h>
#include < string.h>
#include < sys/mman.h>
#include < unistd.h>
static int permission ( uintptr_t address , char output [ 5 ] ) {
FILE * file = fopen ( " /proc/self/maps" , " r" ) ;
assert ( file != NULL ) ;
char line[ 1024 ] ;
int found = 0 ;
while ( fgets ( line, sizeof ( line ) , file ) ) {
uintptr_t start, end;
char mode[ 5 ] ;
if ( sscanf ( line, " %" SCNxPTR " -%" SCNxPTR " %4s " ,
& start, & end, mode ) == 3 &&
start <= address && address < end) {
memcpy ( output, mode, 5 ) ;
found = 1 ;
break ;
}
}
assert ( fclose ( file ) == 0 ) ;
return found;
}
static void check ( unsigned char * base , size_t page ,
const char * middle ) {
for ( size_t i = 0 ; i < 3 ; + + i) {
char mode[ 5 ] = " " ;
int found = permission ( ( uintptr_t ) ( base + i * page) , mode) ;
const char * expected = i == 1 ? middle : " rw-p" ;
if ( expected) {
assert ( found && strcmp ( mode, expected ) == 0 ) ;
} else {
assert ( ! found) ;
}
printf ( " page %zu : %s \n " , i, found ? mode : " hole" ) ;
}
}
int main ( void ) {
long size = sysconf ( _SC_PAGESIZE ) ;
assert ( size > 0 ) ;
size_t page = ( size_t ) size;
unsigned char * whole = mmap ( NULL , 5 * page, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, - 1 , 0 ) ;
assert ( whole != MAP_FAILED ) ;
unsigned char * base = whole + page;
assert ( mprotect ( base, 3 * page, PROT_READ | PROT_WRITE ) == 0 ) ;
puts ( " 初期:" ) ; check ( base, page, " rw-p" ) ;
assert ( mprotect ( base + page, page, PROT_READ ) == 0 ) ;
puts ( " 中央ページをリードオンリーに:" ) ; check ( base, page, " r--p" ) ;
assert ( mprotect ( base + page, page, PROT_READ | PROT_WRITE ) == 0 ) ;
puts ( " 権限を復元:" ) ; check ( base, page, " rw-p" ) ;
assert ( munmap ( base + page, page ) == 0 ) ;
puts ( " 中央ページをマッピング解除:" ) ; check ( base, page, NULL ) ;
assert ( munmap ( whole, 2 * page ) == 0 ) ;
assert ( munmap ( whole + 3 * page, 2 * page ) == 0 ) ;
}
cc -std=c11 -Wall -Wextra -Werror vma-demo.c -o vma-demo でコンパイルし、-DNDEBUG を付けません。チェックはアドレスに基づいて行われ、実際の VMA 境界が mmap リクエストの境界と完全に一致すると仮定せず、権限復元後にカーネルが特定のマージレイアウトを選択することを強要しません。maps が表示するのは VMA の属性であり、中央の3ページが実際に物理ページを割り当てられたことを据此断定することはできません。
4. Maple Tree が解決するのは範囲の整理問題
アドレス空間は複数のクエリに答える必要があります。このアドレスがどのマッピングに含まれるか、次のマッピングはどこにあるか、どの区間がどの VMA と交差するか、そして新しいマッピングを配置するのに十分な大きさのホールがどこにあるか。従来の実装は赤黒木と linked list を協調して使用していましたが、Maple Tree は範囲指向の多分岐構造と反復インターフェースを使用してこれらの作業を引き受け、木の高さの削減、局所性の改善、範囲情報の維持において利点を提供します。
まずクエリがどの程度の結果を返すかを明確にし、その上で木の利点を議論します。
クエリ 少なくとも完了すべき作業
1つのアドレスを検索 範囲境界に沿って候補を特定
K 個の重複する VMA を出力 起点を見つけた後、さらに K 個のオブジェクトを出力
全 N 個の VMA をトラバース 少なくとも N 項目を出力する必要があり、O(log N) にはならない
Maple Tree は木の高さ、局所性、範囲の整理を改善しますが、大量のマッピングにまたがる削除とトラバースでは、関連するオブジェクトの規模に比例した作業コストを支払う必要があります。
範囲インデックス:まず分岐を選択し、次にカバー範囲内の VMA を取得
内部ノード:境界に基づいてアドレス範囲を分割
複数のサブスロットで木の高さを削減;ホール情報はアドレス割り当てをサポート
[0x1000, 0x3fff]
VMA A ポインタ
[0x4000, 0x7fff]
ホール
[0x8000, 0xafff]
VMA B ポインタ
図は範囲検索モデルであり、実際のノードメモリレイアウトではない。木はポインタを取得した後、VMA の寿命と安定性を保証する必要がある。
VMA は半開区間を使用しますが、Maple Tree の範囲インターフェースが終端を含むインデックス範囲を使用する場合、[vm_start, vm_end-1] として表現されます。値は VMA ポインタであり、範囲境界は検索方向を決定するために使用されます。ノードタイプ、スロット数、ピボットレイアウトは実装の詳細です。木はページテーブルのように各レベルでアドレスの特定の9ビットを固定して消費するものではなく、ページテーブルの階層に基づいて Maple Tree に固定のアドレスカバーテーブルを発明することもできません。
ホール情報を保存することで、割り当て検索は、リクエストを収容できないサブツリーをスキップする機会があり、各バイトを順に試す必要がありません。例えば、4P の連続する仮想空間が必要で、合計の空きが 8P であっても、複数の 1P のホールで構成されている可能性があります。範囲インデックスは、十分に長い単一のセグメントを探す必要があります。これは仮想アドレスの断片化 であり、buddy 方式で連続する物理ページが見つからない断片化とは異なる問題です。Maple Tree の設計と API を参照してください。
「次の VMA を見つける」ことと「アドレスがカバーされている」ことは等しくない
一部の検索インターフェースは、ホールの中で次の VMA を返すことを許可しています。find_vma(mm, address) の一般的な契約を例にとると、結果は vm_end > address となる最初の VMA になる可能性があります。address < vm_start の場合、アドレスは依然としてホールの中にあります。呼び出し側は、インターフェースのセマンティクスに従ってカバーを再判定する必要があり、戻り値が NULL でないかどうかだけを判断してはいけません。正確なカバーが必要な場合は、対応するインターフェースを選択し、そのロック要件に従ってください。
プローブを区間の末端とホールに停止させ、「どの候補が返されたか」と「候補が実際にカバーしているか」を比較します。終了境界は元の範囲には含まれません。
図を準備しています 再読み込み
VMA ポインタが見つかったら、アドレスは有効なのか? find_vma 風のクエリは、ホールの中で後継を返すことができる。半開範囲のカバーには start ≤ address < end が必要であり、戻り値が NULL でないことはアクセスが合法であることを意味しない。
{"id":"vma-cover-or-successor","title":"VMA ポインタが見つかったら、アドレスは有効なのか?","summary":"find_vma 風のクエリは、ホールの中で後継を返すことができる。半開範囲のカバーには start ≤ address < end が必要であり、戻り値が NULL でないことはアクセスが合法であることを意味しない。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"probe\" data-i18n=\"probe\"></label><output for=\"probe\" id=\"probe-value\"></output><input id=\"probe\" type=\"range\" min=\"0\" max=\"10\" value=\"3\" step=\"0.1\"></div><div class=\"range-view\"><i id=\"probe-line\"></i><div class=\"range-track\"><b id=\"range-0\" style=\"left:0%;width:20%\">A</b><b id=\"range-1\" style=\"left:40%;width:10%\">B</b><b id=\"range-2\" style=\"left:70%;width:20%\">C</b></div><div class=\"ticks\"><span>0</span><span>2</span><span>4</span><span>6</span><span>8</span><span>10 P</span></div></div><div class=\"range-list\"><span>A = [0,2)</span><span>B = [4,5)</span><span>C = [7,9)</span></div><div class=\"metrics\"><div><span data-i18n=\"candidate\"></span><strong id=\"candidate\"></strong></div><div><span data-i18n=\"covered\"></span><strong id=\"covered\"></strong></div></div><div class=\"formula\" id=\"inequality\"></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"hit\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"hole\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"end\"></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.range-view{position:relative;margin:30px 0 14px;padding:14px 0}.range-track{position:relative;height:45px;background:var(--surface);border-bottom:1px solid var(--rule)}.range-track b{position:absolute;height:100%;display:flex;align-items:center;justify-content:center;background:var(--accent-soft);border:1px solid var(--accent);font-size:14px}.range-track b.candidate{background:var(--second-soft);border-color:var(--second)}#probe-line{position:absolute;z-index:1;top:0;bottom:25px;border-left:2px solid var(--ink);transform:translateX(-1px);pointer-events:none;transition:left .2s}.ticks{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin-top:12px}.range-list{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted)}.formula{min-height:4.8em}.metrics strong{min-height:3.2em}","js":"const $=s=>document.querySelector(s),ranges=[[0,2],[4,5],[7,9]];function draw(){const p=+$('#probe').value,i=ranges.findIndex(r=>r[1]>p),covered=i>=0&&ranges[i][0]<=p;$('#probe-value').textContent=viz.number(p,1);$('#probe-line').style.left=p*10+'%';ranges.forEach((r,j)=>$('#range-'+j).classList.toggle('candidate',j===i));$('#candidate').textContent=i<0?'NULL':'ABC'[i];$('#covered').textContent=viz.t(covered?'yes':'no');$('#inequality').textContent=i<0?'end ≤ '+viz.number(p,1):ranges[i][0]+' ≤ '+viz.number(p,1)+' < '+ranges[i][1]+' : '+String(covered);$('#explanation').textContent=viz.t(i<0?'end':covered?'hit':'hole');}$('#probe').addEventListener('input',draw);draw();","audio":false,"strings":{"candidate":"返された候補","covered":"実際にカバーされているか","end":"プローブより大きい終了位置を持つ範囲はもう存在せず、クエリ結果は空である。","heading":"VMA ポインタが見つかったら、アドレスは有効なのか?","hit":"プローブが半開範囲の内部にある。この候補はアドレスをカバーする。終了境界自体は範囲外に属する。","hole":"クエリは後の VMA を返したが、プローブはまだホールの中にある。候補の start はアドレスより大きいため、NULL 以外のポインタをカバーの証拠として扱えない。","intro":"アドレスプローブを移動させ、「終了位置がアドレスより大きい最初の範囲」と「実際にアドレスをカバーする範囲」を比較する。特に境界とホールを試す。","no":"カバーされていない","probe":"検索アドレス / P","reset":"リセット","scope":"アドレスの単位は P、固定範囲は [0,2)、[4,5)、[7,9)。範囲契約のみを示し、Maple Tree ノードレイアウト、ロック、ページフォルトはシミュレートしない。カバーが成立しても、権限とバックストアを確認する必要がある。","scopeLabel":"モデル範囲と規約","yes":"カバー"}}
5. 木を読む、VMA をロックする、ページテーブルをロックする:なぜ1つのロックに合成できないのか
スレッド R がアドレス A でページフォルト し、スレッド W が同時に A に対して munmap を実行しているとします。R が木から VMA ポインタを取得した後、W が範囲を摘出してオブジェクトを回収すると、R がフィールドにアクセスすると use-after-free になる可能性があります。RCU がオブジェクトの回収を遅らせていても、R が読み取る始点、終点、バックストアオブジェクトは、現在のページフォルト 処理を許可する安定した状態を構成する必要があります。
3層の保護、それぞれが異なるオブジェクトを安定させる
同期層 主に保護するもの これに基づいて推論できないこと
mmap_lock mm レベルの範囲関係および関連操作 すべてのページテーブル変更プロトコルを自動的に代替するものではない
RCU 検索および per-VMA ロック パス内の候補検索、VMA 状態の安定化 オブジェクトの内容が永遠に変化しないことを保証するものではない
ページテーブルロック PTE/PMD などの実際の修正 範囲の取り消しとオブジェクトの寿命を単独で解決するものではない
rmap 関連のロックは、ページからマッピングを逆引きするためにも使用されます。サポートされるページフォルト パスは per-VMA の処理経路を試すことができるが、保証できない場合はより広範な調整パスに戻る。1つの層で読み取りロックを取得しても、自動的に別の層での書き込み権限が付与されるわけではない。
競合発生時:検証失敗時は再試行
読者 R はまず候補を見つけ、VMA を安定化させてアドレスを検証しようとする。書き手 W は範囲を取り消す前に、プロトコルに従って排他権を取得する。R が必要な保証を確立できない場合、再試行するか、より保守的な mm レベルのロックパスを採用する。
時間のかかる I/O は、ロックの解放、再取得、再検証を要求する可能性がある。待機中に、以前成立していた「アドレスはまだこの VMA 内にある」という状態が変更されている可能性があり、古い検索結果をそのまま続けて使用することはできない。
Maple Tree の RCU 機能は、その一部のみを解決する。ノードのトラバースの並行安全性は、データオブジェクトが長期的に変化しないことを自動的に証明するものではなく、すべての書き手が木全体をコピーして置き換えることを意味するものでもない。現在サポートされているアーキテクチャには per-VMA ページフォルト パスがすでにあり、将来の構想として扱うべきではない。しかし、逆に mmap_lock がすでに消失したと主張することもできない。ロック契約は Process Addresses を参照、対象となるカーネルは lock_vma_under_rcu() などのエントリから特定できる。
6. 最後にページフォルト 処理をこのモデルに戻す
同じ書き込み保護例外でも、処理結果はソフトウェアセマンティクスに依存する。
現在の状態 この書き込みに対する解釈
VMA が書き込みを許可、PTE は COW により一時的にリードオンリー プライベートページ処理パスに入ることができる
mprotect により VMA がリードオンリーに変更 COW を使用して書き込み禁止のセマンティクスを回避することはできない
ハードウェアは現象を報告し、VMA とバックストアの状態が解釈を決定する。したがって、handle_mm_fault() は「範囲を見つけるとページを割り当てる」という統一アクションではない。
VMA が合法であっても、ファイルマッピングアクセスがバックエンドファイルの有効範囲を超えると SIGBUS が発生する可能性がある。割り当ての失敗、特殊マッピング、スワップインにもそれぞれ処理結果がある。したがって、handle_mm_fault() は「VMA が見つかったら新しいページを割り当てる」と要約することはできない。ページキャッシュの再利用、スワップインの処理、プライベートページの分離、または操作契約に従ったアクセス拒否が行われる可能性がある。
トラブルシューティングでは、まず maps で範囲と権限を判断し、次に smaps/smaps_rollup で残留と共有占有を区別し、最後に fault、I/O、メモリ圧力を組み合わせてコストを特定する。大量の mmap/mprotect は VMA 数とメタデータオーバーヘッドを増加させる可能性があるが、maps が長いこと自体がパフォーマンスボトルネックであることを証明するものではない。物理割り当て側の問題は 物理メモリ管理 に続き、残留ページがどのように回収されるかは ページ回収とスワップ を参照。