Changing only the middle page of a three-page mapping to read-only may result in two additional entries in maps; after unmapping the middle page, the resulting hole differs from PROT_NONE. Below, we track how the range changes around the same three-page address space, and then explain how concurrent lookups protect these changes.
Phenomenon to Explain
Reading Location
Why does a successful mmap still cause a page fault?
Section 1: VMA, Page Tables, and Backing Content
What do changing permissions, unmapping, and moving addresses each change?
Sections 2–3: Three-Page Deduction and Experiments
Where is Maple Tree fast, and what work cannot be skipped?
Section 4: Range Queries
What if another thread calls munmap right after you find the VMA?
Sections 5–6: Synchronization and Page Fault Handling
1. VMA Describes the Range, Page Tables Describe Current Translations Within the Range
A standard VMA covers a half-open interval [vm_start, vm_end), including the start but excluding the end. Normal VMAs within the same mm_struct do not overlap; threads sharing the address space typically share references to this mm, so one thread's mprotect changes the mapping permissions seen by other threads.
Manual Calculation: Where in the File Does an Address in VMA Map?
Let the base page size be P, the mapping start at address A, and vm_pgoff=8. For address A + 2P + 17, locate it in two steps:
The mapping starts at the 8th base page of the file; advancing 2 pages leads to the file's 10th page.
Keep the page offset of 17; the file byte offset is 10P + 17.
The unit of vm_pgoff is base pages, not bytes; it does not advance with subsequent read/seek operations on this file descriptor.
Level
What is Recorded in This Example
What is Not Directly Answered
VMA
Range starting at A, mapped file, starting file offset, shared/private, and permissions
Whether each page is resident, what the physical page number is
Page Table
Established mappings from virtual pages to physical pages/software state
Where to fetch content for future untouched addresses
File Page Cache
Content currently in memory for various file ranges
Which processes have installed the corresponding PTEs
rmap and other structures
Reverse lookup of possible associated mappings from a page or backing object
Cannot independently replace forward address space lookups
A MAP_PRIVATE file VMA can simultaneously cover unmodified file cache pages and already separated anonymous COW pages after a write, so vm_file != NULL does not mean "every dirty page will be written back to this file." Ordinary shared anonymous mappings may also be backed by shmem file objects in the kernel; the user flag MAP_ANONYMOUS and whether the internal vm_file is null do not have a simple one-to-one correspondence.
2. Deducting Four Operations Using a Three-Page Mapping
mmap: Establishing a Commitment in the Address Space First
Ordinary non-fixed mmap can provide address hints. The kernel selects an available range without overwriting existing mappings, performs checks on permissions, file capabilities, offsets, resource limits, etc., and establishes the mapping. If the new region meets merge conditions with neighbors, it may directly expand an existing VMA, so the statement "one mmap creates a permanently identifiable VMA object" is not always true.
MAP_FIXED has a different meaning: it requires using the specified range and can replace existing mappings. It is not a harmless strong version that fails gracefully on ordinary hints. If the goal is "only occupy if this address range is completely free," one should understand MAP_FIXED_NOREPLACE and return address checks; do not read maps to see a hole and then assume no one else occupies it while using MAP_FIXED.
Usually, data pages are created on demand, but paths like MAP_POPULATE, locking/prefetching requirements, and device mappings may perform additional work. File mmap establishment connects via relevant interfaces in file_operations; callbacks like fault in vm_operations_struct are used for subsequent mapping operations; one cannot invent a generic vm_ops->mmap() and claim all files call it. See mmap(2).
mprotect: Why Changing One Page Adds Two Description Objects
Assume [A,A+3P) was originally read-write. After changing [A+P,A+2P) to read-only, these three segments no longer share the same attributes. The original single VMA cannot faithfully describe them, so it needs to be split into front, middle, and back segments. The kernel must also apply permission changes to existing page tables and maintain the TLB as required; parts without resident pages are constrained by the new VMA semantics for future page faults.
This explains an apparently anomalous failure: even if no additional data memory is requested, mprotect may fail due to the need for more VMA metadata or exceeding mapping count limits. Merely "changing one bit in the PTE" cannot explain this phenomenon. After restoring the middle page to read-write, compatible adjacent segments may merge again, but merging also checks backing objects, offset continuity, anonymous reverse mapping relationships, and other attributes; one cannot simply compare the four characters rw-p displayed in maps. See mprotect(2).
munmap: Unmapping Removes the Mapping, Not Necessarily the Data Itself
After unmapping the middle page, the middle becomes a true hole. Compared to simply prohibiting access, it changes the occupation relationship of the address space:
Operation
Does the middle range still have mapping semantics?
Is the address space still occupied?
Changed to PROT_NONE
Yes, just currently prohibited from access
Yes
munmap middle page
No, becomes a hole
No, can be occupied by subsequent allocations
munmap also tears down translations and releases corresponding references; this step does not directly determine the final lifetime of data pages.
If a page is shared by another process, or if ordinary file pages are still in the page cache, unmapping the current process's mapping does not directly turn them into buddy free pages. Conversely, when physical pages have been reclaimed and swapped out, valid VMAs can still exist. VMA lifetime, page table entry lifetime, and data page lifetime must be reasoned about separately.
mremap: Addresses Can Change, Data Need Not Be Byte-for-Byte Copied
When expanding a mapping, if adjacent ranges are unavailable, a movable mremap can choose a new location and move page table mappings and other states. It can avoid copying some user data, but there are still metadata, page table, and invalidation costs, and not all mappings can be moved arbitrarily. After a successful move, old addresses and any absolute self-referencing pointers stored within them need to be handled according to application design; "content is still there" does not mean "all original pointers remain valid."
Putting the three lifetimes in the same view: read the central page first, then unmapping it, and see which ranges, PTEs, and caches truly disappear. Restoring mappings and restoring access permissions can also be done separately.
file_offset=Pvm_pgoff+(a−vm_start)
This formula is used for file addresses within this VMA; a must first satisfy range and access conditions; holes do not participate in calculation.
Preparing the visual
Changing one page's permission, which objects change together?
mprotect can split ranges but need not allocate data pages. PROT_NONE still occupies the virtual range, munmap leaves a hole; tearing down the mapping need not discard the file cache.
{"id":"vma-range-edits","title":"Changing one page's permission, which objects change together?","summary":"mprotect can split ranges but need not allocate data pages. PROT_NONE still occupies the virtual range, munmap leaves a hole; tearing down the mapping need not discard the file cache.","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":"Page cache for the same file","cached":"Cached","count":"Illustrative VMA count","denied":"This address is a hole or PROT_NONE prohibits reading; it cannot be treated as a normal on-demand read.","empty":"Not cached","heading":"Changing one page's permission, which objects change together?","hole":"Hole","initial":"Initially one read-write VMA, but no data PTEs yet. File ranges and established hardware translations are not the same thing.","intro":"Click one of the three pages, then modify permissions, read, or unmap. Compare range strips, PTEs, and file caches to observe the different lifetimes of the three.","loaded":"Access retrieves the file page content and establishes the PTE for this mapping. Reading again can reuse it; changing page permissions does not move the file offset.","map":"Remap this page","mapped":"This page re-establishes a read-write mapping, but its data PTEs are not yet installed; existing file cache can still be reused.","none":"Set to PROT_NONE","protected":"Permissions changed, reorganized by compatible ranges. It does not actively read data; PROT_NONE is still a mapping occupying address space.","ranges":"VMA Range / P","read":"Read selected page","readOnly":"Set to read-only","reset":"Reset","resident":"Installed PTEs","rw":"Set to read-write","scope":"The three pages belong to the same ordinary file, base page P=4096, vm_pgoff=8. Only read operations are performed, no file modifications; cache is retained without reclamation. Adjacent segments are merged only by permission and contiguous backing for simplicity; real VMA merging is also restricted by other attributes. Rebuilding unmapped pages is a new illustrative mapping, not using real MAP_FIXED.","scopeLabel":"Model Scope and Conventions","select":"Select page to operate on","unmap":"Unmap","unmapped":"This page becomes a hole, PTEs are torn down. File cache read in previously may remain; it is not exclusively owned by this VMA."}}
3. Runnable Experiment: Directly Observing These Boundaries
Below, we reserve five pages of PROT_NONE space, use the middle three pages for experiments, and use the sides as address boundaries. At each step, read /proc/self/maps to verify the attributes of the specified address; do not write to read-only pages' addresses, nor cause process crashes.
Compile with cc -std=c11 -Wall -Wextra -Werror vma-demo.c -o vma-demo, without -DNDEBUG. Checks are location-based by address; do not assume actual VMA boundaries exactly equal mmap request boundaries, nor force-assert that the kernel chooses a specific merge layout after restoring permissions. maps presents VMA attributes; one cannot assert that the middle three pages have actually allocated physical pages based on this.
4. Maple Tree Solves the Range Organization Problem
Address spaces need to answer various queries: which mapping does this address fall into, where is the next mapping, which VMAs intersect with a certain interval, and where are there large enough holes to place new mappings. The old implementation used a collaboration of red-black trees and linked lists; Maple Tree uses a range-oriented multi-branch structure and iteration interfaces to undertake this work, providing benefits in reducing tree height, improving locality, and maintaining range information.
First clarify how many results a query needs to return, then discuss the tree's benefits:
Query
Minimum Work Required
Look up one address
Locate candidates along range boundaries
Output K overlapping VMAs
After finding the start, still need to output K objects
Traverse all N VMAs
Must output at least N items; cannot become O(log N)
Maple Tree improves tree height, locality, and range organization; deletions and traversals spanning many mappings still incur work proportional to the scale of involved objects.
VMAs use half-open intervals. When Maple Tree range interfaces use inclusive end index ranges, they are correspondingly expressed as [vm_start, vm_end-1]. The values are VMA pointers; range boundaries are used to determine search direction. Node types, slot counts, and pivot layouts are implementation details; the tree does not consume a fixed 9 bits of the address at each level like a page table, nor can one invent a fixed address coverage table for Maple Tree based on page table hierarchy.
Storing hole information allows allocation searches to skip subtrees that cannot possibly accommodate the request, without trying every byte sequentially. For example, when 4P of contiguous virtual space is needed, 8P of total free space may still consist of multiple 1P holes; the range index needs to find a single segment long enough. This is virtual address fragmentation, which is a different problem from buddy system fragmentation where no contiguous physical pages are found. See Maple Tree Design and API.
"Finding the Successor" Does Not Equal "Address is Covered"
Some lookup interfaces allow returning the next VMA within a hole. Taking the common contract of find_vma(mm, address) as an example, the result can be the first VMA with vm_end > address; if address < vm_start, the address is still in a hole. The caller must judge coverage according to interface semantics and cannot simply judge based on non-null return. When only exact coverage is needed, choose the corresponding interface and adhere to its locking requirements.
Stop the probe at the interval end and in the hole, comparing "which candidate is returned" with "whether the candidate covers." The end boundary does not belong to the original range.
Preparing the visual
Finding a VMA pointer, is the address definitely valid?
find_vma-style queries can return successors in holes. Coverage of half-open ranges also requires start ≤ address < end; a non-null return does not equal legal access.
{"id":"vma-cover-or-successor","title":"Finding a VMA pointer, is the address definitely valid?","summary":"find_vma-style queries can return successors in holes. Coverage of half-open ranges also requires start ≤ address < end; a non-null return does not equal legal access.","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":"Returned candidate","covered":"Truly covers?","end":"There are no ranges with end positions greater than the probe; query result is null.","heading":"Finding a VMA pointer, is the address definitely valid?","hit":"Probe is inside the half-open range. This candidate covers the address; the end boundary itself is outside the range.","hole":"Query returned the subsequent VMA, but the probe is still in the hole. The candidate's start is greater than the address, so a non-null pointer cannot be taken as proof of coverage.","intro":"Move the address probe, comparing \"the first range with end position greater than address\" with \"the range that truly covers the address.\" Specifically try boundaries and holes.","no":"Not covered","probe":"Query Address / P","reset":"Reset","scope":"Address units are P, fixed ranges [0,2), [4,5), [7,9). Only demonstrates range contracts, does not simulate Maple Tree node layout, locks, or page faults; even if coverage holds, check permissions and backing content.","scopeLabel":"Model Scope and Conventions","yes":"Covers"}}
5. Why Reading the Tree, Locking VMA, and Locking Page Tables Cannot Be Combined into One Lock
Consider thread R faulting at address A, while thread W simultaneously executes munmap on A. After R obtains the VMA pointer from the tree, if W removes the range and reclaims the object, R accessing fields later may cause use-after-free. Even if RCU delays object reclamation, the start, end, and backing object R reads must still form a stable state allowing current page fault processing.
Three Layers of Protection, Each Stabilizing Different Objects
Sync Layer
Mainly Protects
Cannot Infer From This
mmap_lock
mm-level range relationships and related operations
Does not automatically replace all page table modification protocols
RCU lookup and per-VMA lock
Candidate lookup and VMA state stability in the path
Does not guarantee object content never changes
Page table lock
Actual modifications of PTE/PMD, etc.
Does not independently solve range revocation and object lifetime
rmap-related locks also serve reverse lookup of mappings from pages. Supported page fault paths can try per-VMA schemes; if guarantees cannot be established, fall back to broader coordination paths; obtaining a read lock on one layer does not automatically grant write permissions on another.
When Contention Occurs: Retry on Validation Failure
Reader R first finds the candidate, attempts to stabilize the VMA and validate the address; writer W obtains exclusive rights according to protocol before revoking the range. If R cannot establish the required guarantees, it retries or adopts the more conservative mm-level lock path.
Time-consuming I/O may also require releasing and re-acquiring locks and re-validating. During the wait, the previously established "address is still within this VMA" may have changed; old lookup results cannot be directly continued.
Maple Tree's RCU capabilities only solve part of this: concurrent safety of traversing nodes does not automatically prove data objects remain unchanged long-term, nor does it mean all writers copy the entire tree before replacing. Currently supported architectures already have per-VMA page fault paths; it cannot be written off as a distant future; but one also cannot claim mmap_lock has disappeared. Lock contracts can be found in Process Addresses; target kernels can locate entry points such as lock_vma_under_rcu().
6. Finally, Connecting Page Fault Handling Back to This Model
For the same write protection exception, the handling result depends on software semantics:
Current State
Interpretation of This Write
VMA allows write, PTE is temporarily read-only due to COW
Can enter private page handling path
mprotect has changed VMA to read-only
Cannot bypass write-prohibited semantics using COW
Hardware reports phenomena; VMA and backing state determine interpretation. handle_mm_fault() is therefore not a unified action of "finding a range and allocating a page."
Even if the VMA is legal, file mapping access beyond the backend file's valid range may generate SIGBUS; allocation failures, special mappings, and swapping in have their own handling results. handle_mm_fault() therefore cannot be generalized as "finding a VMA and allocating a new page." It may reuse page cache, handle swapping in, separate private pages, or refuse access according to operation contracts.
When troubleshooting, first use maps to judge ranges and permissions, then use smaps/smaps_rollup to distinguish resident and shared usage, and finally combine faults, I/O, and memory pressure to locate costs. A large number of mmap/mprotect calls may increase VMA count and metadata overhead; but a long maps file does not independently prove it is a performance bottleneck. Issues on the physical allocation side connect to Physical Memory Management; how resident pages are reclaimed connects to Page Reclaim and Swap.