A program successfully maps a large chunk of memory, yet RSS does not grow synchronously; a page fault occurs on the first write. Following a single value = *p memory access instruction allows us to separate valid address, page table translation, and actual content into three distinct layers.
Below, we manually calculate addresses using x86-64 with 4 KiB base pages, then follow a single page of data through first use, fork, and unmapping. Page sizes and permission encodings for other architectures may differ.
Phenomenon to Explain
Reading Location
Does a TLB miss always require entering the kernel?
Section 1: Memory Access Path
How do multi-level page tables save space?
Sections 2–3: Address Calculation and Permissions
Why can we read the same content after fork but modify them separately?
Section 4: COW and Experiments
Why must other CPUs be notified upon unmapping?
Sections 5–8: TLB, Huge Pages, and Observations
1. Why a load instruction sometimes doesn't enter the kernel, and sometimes causes a page fault
Assume the program executes value = *p. The CPU receives the virtual address and the read operation; the MMU needs to verify the mapping and access permissions. Typically, it first utilizes the translation cached in the TLB; if hit and permissions are satisfied, there is no need to read the entire page table from scratch for this access. If the TLB does not have a suitable entry, the hardware attempts a page walk; the page table cache and the regular CPU cache may further accelerate this process.
If the walk yields a valid mapping, a TLB miss can be resolved entirely by hardware. If the mapping is missing or permissions do not match, the CPU generates an exception, and the kernel examines the address range and access semantics on the software side: this could be a legitimate first allocation, swap-in, copy-on-write, or the program accessing an address it does not own. After successful handling, the original instruction is usually retried; user accesses that cannot be satisfied result in SIGSEGV, SIGBUS, etc., depending on the situation.
Placing "page fault" back into a specific scenario: File content has already been read into the page cache by another process. The current process may still page fault on first access because the corresponding PTE has not yet been installed. This time, it can reuse existing content without reading from disk again.
Thing to Confirm
Which Layer to Look At
Is this address allowed to be read?
VMA range and access semantics
Can the CPU complete address translation directly?
Current page table and TLB state
Does content need to be fetched from the backend again?
Residency status in page cache, swap backing, etc.
Thus, having a VMA, having a PTE, and data being resident cannot substitute for each other.
2. Why page tables are multi-level: Calculate the space cost first
A 48-bit address with 4 KiB pages means 2^48 / 2^12 = 2^36 virtual pages. If each page reserves a fixed 8-byte entry, a single linear page table would require 2^39 bytes, which is 512 GiB. Most processes use only a tiny fraction of this space; pre-allocating page tables for all holes is very wasteful.
Multi-level page tables allow an empty upper-level entry to represent an entire unmapped range, allocating lower-level table pages only for branches that are actually needed. For x86-64's 4 KiB table pages, 512 entries of 8 bytes exactly fill one page, so each level uses 9 bits for indexing. Four levels of indexing total 36 bits, plus 12 bits for page offset equals 48 bits.
"Folded" means Linux generic code can still be written against the 5-level interface; the layer not needed for the target configuration does not correspond to an independent hardware table. The PGD in 4-level mode corresponds to hardware PML4; in 5-level mode, PGD corresponds to PML5. Names cannot be mechanically bound forever to the same hardware layer. See Page Table Levels.
Manually calculating how an address locates a physical page
Construct an address such that the four-level indices are sequentially 1, 2, 3, 4, and the page offset is 0x567. CR3 provides the physical base address of the top-level table and related control information. The hardware reads the 1st entry of the top level to get the base address of the next table; then takes the 2nd, 3rd, and 4th entries. The address of each step's entry is "base address of that level + index × 8", not adding the four indices and multiplying by the page size.
Assume the final leaf entry points to physical page base address 0x12345000; the target physical address is 0x12345567. The page offset comes from the lower 12 bits of the virtual address; the lower bits of the PTE are flags and do not store this offset.
indices=[1,2,3,4]shifts=[39,30,21,12]va=sum(index<<shiftforindex,shiftinzip(indices,shifts))|0x567assert[(va>>shift)&0x1ffforshiftinshifts]==indicesphysical_base=0x12345000# Hypothetical result from traversal, not reading actual page table
physical=physical_base|(va&0xfff)assertphysical==0x12345567assert(1<<48)//4096*8==512*(1<<30)print(f"virtual={va:#x}, physical={physical:#x}")
Below, we split the address into indices and offset, directly change the leaf size, and observe where the traversal terminates. The diagram selects 0x40000000 as the aligned data base address; the calculation rules are the same as the manual calculation above.
PA=leaf_base+(VAmod2b)
b is the offset bit width covered by the leaf: in this diagram, we take 12, 21, and 30 respectively.
Preparing the visual
Which bits in the address are used at this level?
Multi-level indices select entries layer by layer; physical address is obtained by adding page offset to the leaf page base address. Huge pages end traversal at a higher level, with more lower bits directly becoming the offset.
{"id":"vm-address-walk","title":"Which bits in the address are used at this level?","summary":"Multi-level indices select entries layer by layer; physical address is obtained by adding page offset to the leaf page base address. Huge pages end traversal at a higher level, with more lower bits directly becoming the offset.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"address-label\" for=\"address\" data-i18n=\"address\"></label><input id=\"address\" type=\"text\" value=\"0x8080604567\" spellcheck=\"false\" autocomplete=\"off\"><div class=\"presets\" role=\"group\" data-i18n-label=\"leaf\"><button data-mode=\"12\" data-i18n=\"small\"></button><button data-mode=\"21\" data-i18n=\"medium\"></button><button data-mode=\"30\" data-i18n=\"large\"></button></div><div class=\"bits\" id=\"bits\" aria-hidden=\"true\"></div><div class=\"fields\" id=\"fields\"></div><div class=\"walk\"><div class=\"walk-row\" id=\"walk-0\"><strong>PGD</strong><code id=\"entry-0\"></code></div><div class=\"walk-row\" id=\"walk-1\"><strong>PUD</strong><code id=\"entry-1\"></code></div><div class=\"walk-row\" id=\"walk-2\"><strong>PMD</strong><code id=\"entry-2\"></code></div><div class=\"walk-row\" id=\"walk-3\"><strong>PTE</strong><code id=\"entry-3\"></code></div></div><div class=\"answer\"><span data-i18n=\"pa\"></span><strong id=\"physical\"></strong><code id=\"sum\"></code></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"smallNote\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"hugeNote\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"invalid\"></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.address-label{display:block;font-size:13px;margin-bottom:6px}#address{display:block;width:100%;font-family:var(--viz-font-mono);margin-bottom:14px}.bits{height:24px;display:flex;gap:2px;margin:20px 0 10px}.bits i{background:var(--accent);transition:width .25s}.bits i:last-child{background:var(--second)}.fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:80px;gap:8px;font-size:12px;min-height:8.4em}.fields div{min-height:0;padding:5px 0;overflow-wrap:anywhere}.fields strong{color:var(--accent);display:block;font-weight:500}.fields div.unused{opacity:.5}.fields div.offset strong{color:var(--second)}.walk{margin-top:12px}.walk-row{display:grid;grid-template-columns:40px minmax(0,1fr);gap:10px;padding:12px 8px;border-top:1px solid var(--rule);font-size:12px;min-height:7.2em}.walk-row code{overflow-wrap:anywhere;white-space:pre-line}.walk-row.unused{color:var(--muted);opacity:.6}.answer{padding:14px;background:var(--accent-soft);border-radius:8px;font-size:12px}.answer strong{display:block;font:500 21px var(--viz-font-ui);margin:7px 0;overflow-wrap:anywhere}.answer code{display:block;overflow-wrap:anywhere;min-height:3.2em}@media(min-width:481px){.fields{grid-template-columns:repeat(5,minmax(0,1fr));min-height:5.6em}.walk-row{min-height:5.6em}}","js":"const $=s=>document.querySelector(s);let shift=12;const hex=n=>'0x'+n.toString(16);function draw(){let va=0n,valid=false;try{const s=$('#address').value.trim();if(/^(?:0x)?[0-9a-f]+$/i.test(s)){va=BigInt(s.startsWith('0x')||s.startsWith('0X')?s:'0x'+s);valid=va<0x800000000000n;}}catch{}$('#address').setAttribute('aria-invalid',String(!valid));const levels=(48-shift)/9,indices=[39,30,21,12].map(s=>Number((va>>BigInt(s))&511n)),offset=va&((1n<<BigInt(shift))-1n),labels=['PGD','PUD','PMD','PTE'];$('#bits').replaceChildren(...[...Array(levels).fill(9),shift].map(n=>{const e=document.createElement('i');e.style.flex=String(n);return e;}));$('#fields').replaceChildren(...Array.from({length:5},(_,i)=>{const e=document.createElement('div');e.classList.toggle('offset',i===4);e.classList.toggle('unused',i>=levels&&i<4);e.textContent=i===4?viz.t('offset')+' ['+(shift-1)+':0]':labels[i]+' ['+(47-i*9)+':'+(39-i*9)+']';const b=document.createElement('strong');b.textContent=!valid?'—':i===4?hex(offset):i<levels?indices[i]:'→ '+viz.t('offset');e.append(b);return e;}));for(let i=0;i<4;i++){$('#walk-'+i).classList.toggle('unused',i>=levels);$('#entry-'+i).textContent=!valid?'—':i>=levels?viz.t('notWalked'):hex(BigInt((i+1)*4096))+' + '+indices[i]+' × 8 = '+hex(BigInt((i+1)*4096+indices[i]*8))+'\\n→ '+hex(i===levels-1?0x40000000n:BigInt((i+2)*4096));}$('#physical').textContent=valid?hex(0x40000000n+offset):'—';$('#sum').textContent=valid?'0x40000000 + '+hex(offset)+' = '+hex(0x40000000n+offset):'—';$('#explanation').textContent=viz.t(!valid?'invalid':shift===12?'smallNote':'hugeNote');document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(+e.dataset.mode===shift)));}$('#address').addEventListener('input',draw);document.querySelectorAll('[data-mode]').forEach(e=>e.addEventListener('click',()=>{shift=+e.dataset.mode;draw();}));draw();","audio":false,"strings":{"address":"Virtual Address (hex)","heading":"Which bits in the address are used at this level?","hugeNote":"Traversal ends at a higher-level leaf; lower tables are no longer needed. More lower address bits are directly retained as offset, so one translation covers a larger range.","intro":"Input a virtual address, then select the leaf page size. The width of address fields corresponds to bit counts; the position for each table lookup is given below the arrows.","invalid":"Please enter a hexadecimal address in the range 0 to 0x7fffffffffff. The current input does not participate in translation.","large":"1 GiB","leaf":"Leaf Size","medium":"2 MiB","notWalked":"Traversal no longer proceeds at this level; relevant lower bits are merged into the leaf offset.","offset":"Page Offset","pa":"Physical Address","reset":"Reset","scope":"4-level x86-64, lower half canonical addresses; P4D folded. Entries are 8 bytes; illustrative table base addresses are sequentially 0x1000, 0x2000, 0x3000, 0x4000; data base address is 0x40000000. Each parameter adjustment constructs a corresponding valid path; does not read real page tables; ignores permissions, cache, and page faults. The bit width diagram is not a diagram of access counts or latency.","scopeLabel":"Model Scope and Conventions","small":"4 KiB","smallNote":"Four indices select entries in four tables respectively. Finally, only the lower 12 bits are added to the data page base address; do not add indices to form the physical address."}}
Address Width: Separate Capability, Enabled State, and Actual Allocation
x86-64 addresses must also satisfy the canonical rules of the current mode: unused high bits are extended according to the effective highest address bit. 5-level paging brings a larger virtual space, but does not require processes to immediately allocate more physical memory. The CPU exposing LA57 only indicates capability; it also requires kernel configuration and runtime mode. Linux, also considering application compatibility, usually does not default to placing ordinary mmap outside the 47-bit window. See 5-Level Paging Description.
3. Permissions exist in the mapping path, not just as a switch in the last entry
First, distinguish several types of information using common x86 leaf entries:
Information
Function
Boundary when reading bit definitions
Present
Participates in mapping validity judgment
Non-present can express various software states, not just swap
R/W, U/S
Write permission and user access control
Effective permissions are also constrained by upper-level entries and architecture state
NX
Non-executable semantics
Common x86 encoding is at bit 63; bit 11 is not NX
Accessed, Dirty
Provide usage and write clues
Belong to mapping-level information; cannot alone prove content recoverability
Huge page leaves and regular PTEs interpret some bits differently, and physical address bit widths must also be handled according to CPU capabilities.
For example, if the upper level denies user access, reading cannot succeed even if the leaf entry's U/S allows it. Similarly, if a PTE's Dirty=0, it is not sufficient to prove that anonymous content has a valid backing copy: hardware access clues and data recovery sources are different issues.
The VMA permissions maintained by the kernel describe "how the program is allowed to use this range"; the hardware page table permissions describe "whether this current access can be completed directly." The two are intentionally not always identical; this is precisely the foundation for COW to work in the next section. To locate specific bit definitions, target the kernel's arch/x86/include/asm/pgtable_types.h; do not treat simplified teaching diagrams as directly portable bitmasks.
4. Following a page of data through demand paging and COW
When a program maps a private anonymous read-write space, the kernel first registers the address range and semantics. It is usually not necessary to immediately allocate data pages for every address. The first read might use optimizations like sharing the zero page; the first actual write requires a page capable of saving this process's data and ensures that newly allocated user data content does not leak information from previous users.
Then fork is called. Parent and child need to see the same private memory content as at fork time, but if the child process quickly execs, copying all data pages immediately would waste a lot of work. The typical COW approach is to let both sides temporarily share the physical page, set the relevant hardware mappings to write-protected, while retaining the writable semantics of both VMA. True writes force the kernel to determine if separation is necessary.
Reading the diagram: VMA allows write, why still write-protect exception?
When the child process writes, the kernel finds "VMA allows write, current shared mapping prohibits direct write," so it establishes a writable page satisfying private semantics. If copying is indeed needed, the child's new page receives the original content, then executes the original modification; the parent continues mapping the old page. If the old page already satisfies exclusive conditions, it might only restore write permissions. Specific paths are also affected by huge pages, pinned references, etc.; you cannot deduce how many memcpys occurred just from seeing COW semantics from the user perspective.
File MAP_PRIVATE mappings can also produce private COW pages: the modified parts are not written back to the original file. MAP_SHARED expresses shared modification semantics; file pages may enter the writeback path. Both can have page faults; the number of page faults itself does not tell you who the data ultimately belongs to.
Below, we keep two mappings for the same page, attempting writes in "the other party is still present" and "the other party has unmapped" scenarios. Compare complete content and copy counts, not just whether the arrow color changed.
Preparing the visual
Why does changing one byte split an entire page?
Private pages after fork can be temporarily shared and write-protected. If still shared during write, the other party's content must be preserved; if already exclusive, write permission might only be restored.
{"id":"vm-cow-ownership","title":"Why does changing one byte split an entire page?","summary":"Private pages after fork can be temporarily shared and write-protected. If still shared during write, the other party's content must be preserved; if already exclusive, write permission might only be restored.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"byte\" data-i18n=\"byte\"></label><output for=\"byte\" id=\"byte-value\"></output><input id=\"byte\" type=\"range\" min=\"0\" max=\"3\" value=\"1\" step=\"1\"></div><label class=\"caption\" for=\"symbol\" data-i18n=\"symbol\"></label><select id=\"symbol\"><option>X</option><option>Y</option><option>Z</option></select><div class=\"two mappings\"><section class=\"card\" id=\"map-p\"><header data-i18n=\"parent\"></header><code id=\"pte-p\"></code><div class=\"actions\"><button id=\"write-p\" data-i18n=\"write\"></button></div><button id=\"unmap-p\" data-i18n=\"unmap\"></button></section><section class=\"card\" id=\"map-c\"><header data-i18n=\"child\"></header><code id=\"pte-c\"></code><div class=\"actions\"><button id=\"write-c\" data-i18n=\"write\"></button></div><button id=\"unmap-c\" data-i18n=\"unmap\"></button></section></div><svg class=\"links\" viewBox=\"0 0 600 60\" aria-hidden=\"true\"><path id=\"link-p\"/><path id=\"link-c\"/></svg><div class=\"two physical\"><section class=\"card page\" id=\"page-0\"><header>F0</header><div class=\"bytes\" id=\"bytes-0\"></div><small id=\"refs-0\"></small></section><section class=\"card page\" id=\"page-1\"><header>F1</header><div class=\"bytes\" id=\"bytes-1\"></div><small id=\"refs-1\"></small></section></div><div class=\"metrics\"><div><span data-i18n=\"pages\"></span><strong id=\"page-count\"></strong></div><div><span data-i18n=\"copies\"></span><strong id=\"copy-count\"></strong></div></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=\"copied\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"exclusive\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"removed\"></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.mappings{margin-bottom:0}.mappings code{display:block;min-height:6.4em;white-space:pre-line}.links{width:100%;height:60px;overflow:visible}.links path{fill:none;stroke:var(--accent);stroke-width:2;transition:d .3s}.links #link-c{stroke:var(--second)}.physical{margin-top:0}.page{background:var(--accent-soft);transition:opacity .2s}.page.free{opacity:.35;background:var(--surface)}.bytes{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));margin:12px 0}.bytes span{font:500 17px var(--viz-font-mono);text-align:center;padding:8px 0;border:1px solid var(--rule);background:var(--paper)}.bytes span.changed{background:var(--second-soft);color:var(--second)}.page small{min-height:3.2em}.mappings .actions button{flex-basis:100%}","js":"const $=s=>document.querySelector(s);let maps={p:0,c:0},rw={p:false,c:false},pages=[['A','B','C','D'],null],copies=0,message='initial';const refs=i=>Object.values(maps).filter(v=>v===i).length;function draw(){$('#byte-value').textContent=$('#byte').value;for(const k of ['p','c']){const present=maps[k]!==null;$('#pte-'+k).textContent=present?'VMA: RW\\nPTE: '+(rw[k]?'RW':'R')+' → F'+maps[k]:'VMA: —\\nPTE: —';$('#write-'+k).disabled=$('#unmap-'+k).disabled=!present;const x=k==='p'?150:450;$('#link-'+k).style.visibility=present?'visible':'hidden';$('#link-'+k).setAttribute('d','M'+x+',0 C'+x+',30 '+(maps[k]===1?450:150)+',30 '+(maps[k]===1?450:150)+',60');}for(let i=0;i<2;i++){$('#page-'+i).classList.toggle('free',!refs(i));$('#bytes-'+i).replaceChildren(...Array.from({length:4},(_,j)=>{const e=document.createElement('span');e.textContent=pages[i]?.[j]??'—';e.classList.toggle('changed',!!pages[i]&&pages[i][j]!=='ABCD'[j]);return e;}));$('#refs-'+i).textContent=refs(i)?viz.t('refs')+' '+refs(i):viz.t('free');}$('#page-count').textContent=[0,1].filter(i=>refs(i)>0).length;$('#copy-count').textContent=copies;$('#explanation').textContent=viz.t(message);}for(const k of ['p','c']){$('#write-'+k).addEventListener('click',()=>{let i=maps[k];if(refs(i)>1){const next=1-i;pages[next]=pages[i].slice();maps[k]=next;i=next;copies++;message='copied';}else message='exclusive';rw[k]=true;pages[i][+$('#byte').value]=$('#symbol').value;draw();});$('#unmap-'+k).addEventListener('click',()=>{const i=maps[k];maps[k]=null;rw[k]=false;if(!refs(i))pages[i]=null;message='removed';draw();});}$('#byte').addEventListener('input',draw);$('#reset').addEventListener('click',()=>{maps={p:0,c:0};rw={p:false,c:false};pages=[['A','B','C','D'],null];copies=0;message='initial';draw();});draw();","audio":false,"strings":{"byte":"Byte to modify","child":"Child Process","copied":"The original page is still referenced by the other party. First preserve complete content to establish a new page, then modify the selected byte; the other party's data remains unchanged.","copies":"Whole-page copies in this model","exclusive":"This page is already exclusive. This model restores write permission and modifies directly, without increasing the whole-page copy count.","free":"Free Page Slot","heading":"Why does changing one byte split an entire page?","initial":"Both parties' VMA allow write, but PTE is temporarily read-only. Two arrows point to the same page; copying has not occurred.","intro":"Select a byte within the page and a value, then let parent or child write. You can also unmap one party first, comparing results for shared and exclusive states.","pages":"Referenced Data Pages","parent":"Parent Process","refs":"Mapping References","removed":"Only revoke the mapping for the selected party. Pages with remaining references continue to be retained; when the last mapping reference disappears, this model page slot becomes free.","reset":"Reset","scope":"Initially two private anonymous mappings after fork; each four-grid diagram shows only four bytes of one page. Two physical page slots are used to show ownership; real pages are not allocated; copy count is determined by reference count, which is a teaching simplification, omitting pin, huge pages, and other kernel checks. Unmapping includes necessary translation revocation.","scopeLabel":"Model Scope and Conventions","symbol":"Value to write","unmap":"Unmap this mapping","write":"Write to this mapping"}}
Experiment: Observing isolation results, not guessing copy counts
Below, we simultaneously observe first-write and post-fork visibility, using only 64 base pages:
#define_GNU_SOURCE#include<assert.h>#include<stdio.h>#include<sys/mman.h>#include<sys/resource.h>#include<sys/wait.h>#include<unistd.h>intmain(void){long page =sysconf(_SC_PAGESIZE);assert(page >0);size_t length =64*(size_t)page;unsignedchar*memory =mmap(NULL, length, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,-1,0);assert(memory != MAP_FAILED);assert(madvise(memory, length, MADV_NOHUGEPAGE)==0);struct rusage before, after;assert(getrusage(RUSAGE_SELF,&before)==0);volatileunsignedchar*data = memory;for(size_t i =0; i < length; i +=(size_t)page)
data[i]=7;assert(getrusage(RUSAGE_SELF,&after)==0);printf("First sequential page write minor faults diff: %ld\n",
after.ru_minflt- before.ru_minflt);fflush(stdout);pid_t child =fork();assert(child >=0);if(child ==0){assert(data[0]==7);
data[0]=9;assert(data[0]==9);_exit(0);}int status;assert(waitpid(child,&status,0)== child);assert(WIFEXITED(status)&&WEXITSTATUS(status)==0);assert(data[0]==7);assert(munmap(memory, length)==0);puts("Child process private modification did not change parent process content: Passed");}
Compile with cc -std=c11 -Wall -Wextra -Werror vm-demo.c -o vm-demo, without -DNDEBUG. The minor fault difference is for observation only; do not assert it must equal 64; measurements may also include other intra-process activities. Minor faults usually do not require backend reads for this handling, but this does not mean the overhead is negligible. The experiment does not read PFNs, so it verifies user-visible isolation, not direct evidence of physical page sharing.
5. Why TLB consistency is part of memory safety
Assume CPU 0 executes munmap, clearing the PTE for address A; CPU 1 is running another thread in the same address space, and its TLB still caches the translation from A to physical page P. Ordinary data cache coherence can propagate modifications to page table memory, but it will not automatically cause each TLB entry to re-query the page table. If P is allocated to another object at this time, CPU 1's old translation might continue to read/write the new object's data.
Therefore, before an old physical page is handed to someone else again, dangerous old translations must be invalidated. Revoking mappings, tightening permissions, and page migration all require coordinating translation maintenance with page lifespan.
The traditional x86 path often uses IPI to coordinate relevant CPUs, but indiscriminate broadcasting is not required. Address space usage records, batch invalidation, deferred processing, and hardware capabilities can reduce workload; the specific function chain varies by implementation, but security boundaries remain unchanged. See TLB Invalidation Interface.
PCID/ASID attaches address space tags to cached translations, allowing results from different address spaces to potentially remain in the TLB simultaneously. It reduces unnecessary clearing, but cannot make already-expired mappings permanently valid; tag reuse and page table updates still require handling invalidation. The overhead of frequent mprotect/munmap also varies with the number of active CPUs for the address space and batch size; it should not be estimated solely by how many PTEs were modified.
6. Where do the benefits of huge pages come from, and who pays the cost?
First, calculate the number of leaf entries required to fully map 1 GiB of data:
Mapping Granularity
Number of Leaf Entries
Changes to Lowest-Level Table
4 KiB Base Page
262,144 PTEs
Just PTE storage is approx. 2 MiB
2 MiB PMD Huge Page
512 PMD Leaves
Saves corresponding lower-level PTE tables
One huge page translation also covers a larger virtual range, potentially reducing TLB pressure for certain working sets. Benefits come from changes in translation coverage and page table workload; the actual magnitude still depends on the CPU and access patterns.
However, TLB capacity and organization for different page sizes may not be identical; you cannot simply multiply by a fixed number of entries to assume a CPU's actual performance. Huge pages also require corresponding contiguous physical ranges; allocation, zeroing, migration, and compaction may concentrate costs into a single page fault. Ranges using only a small number of bytes may cause internal waste; partial unmapping or permission changes may also require splitting.
Mechanism
How to Obtain Huge Pages
Main Trade-offs
HugeTLB
Explicit pool and mapping interfaces, with management like reservation/actual allocation
Capacity management is clearer, but reservation does not equal infinite resources; cannot use normal swap
PMD THP
Page fault attempts or background merging, decided by policy
Reduces translation and page fault overhead, may fall back to base pages or incur compaction latency
mTHP
Larger folios of various sizes, often still mapped by PTE
Processes more pages in batches, reducing some page fault overhead; does not mean saving a level of page tables entirely
THP is not simply "changing a flag on 512 originally contiguous pages": background merging can allocate new huge pages and move content; the page fault path can also directly attempt huge pages. Whether PTE mapping for mTHP gains extra hardware translation benefits depends on the architecture.
Actual sizes and enablement policies should be checked via interfaces like hugepages-*kB/enabled; do not judge all paths solely by the top-level switch; explicit MADV_COLLAPSE has its own independent contract. See THP Management Documentation and HugeTLB.
7. Why PTI switches page tables
Traditional shared layouts allow user and kernel mappings to reside in the same address translation structure, with normal permission checks preventing user access to kernel pages. However, Meltdown-class issues show that accesses denied by architecture permissions on specific CPUs may still leave observable side-channel traces in transient execution. The idea of PTI is to shrink the set of kernel mappings visible in the user-space page table, so that most sensitive addresses are not in that mapping at all.
Switch to the full view when entering the kernel, switch back to the reduced view when returning to user space; the minimal region required for entry must still be accessible. The two views can share the lower-level page tables for the user part; this does not mean every process's entire user data and every level of page table are completely copied twice. Overhead includes page table switching and related translation maintenance; PCID can mitigate some costs; it cannot be fixed as "exactly two TLB misses per syscall." See x86 PTI Design.
8. Separating four quantities when reading observation results
When reading monitoring data, keep four quantities separate:
Quantity
Typical Observation
What it Explains
Virtual Range
VmSize
How much address space is registered for the program
Resident Data
RSS, smaps
Which content currently occupies memory
Page Table Overhead
VmPTE
Memory cost of the translation structure itself
Page Fault Events
fault count
Number of times access requires kernel intervention
Large VmSize with small RSS can come from untouched mappings. Whether huge pages are actually used should be combined with fields like smaps; do not look only at configuration switches.
/proc/<pid>/pagemap is a binary interface; PFNs may be hidden due to permission restrictions, and bit interpretations should be read according to documentation versions; do not directly cat into text. The next article VMA and Maple Tree continues to answer: how the kernel knows a page fault address is valid, and why modifying a range of permissions might split out more VMAs.