Virtual Memorymmutranslationprotectioncritical pathhardware

The MMU: Translation and Protection in One Check

The memory management unit is not a lookup table off to the side. It is a gate every load and store passes through, and it answers two questions at once: where does this address really point, and is this process allowed to touch it that way?

Follow the mechanism

Software view, hardware view

The gap between what you wrote and what the machine does is where this whole domain lives.

The question
What does the MMU actually check on every single memory access, and why can it not be skipped when the answer is obvious?
What you wrote
Memory protection feels like something checked occasionally — at allocation, at a boundary, when something goes wrong. A correct program never notices it.
What the hardware does
The MMU checks every access without exception: it resolves the mapping and validates the permission bits in the same operation, before the access is allowed to proceed. There is no fast path that skips it, and no way for software to ask it to.
Because the check is unconditional, it constrains the design of everything around it: the L1 cache is sized so lookup can overlap translation, the TLB exists so the mapping is usually already resolved, and a permission failure surfaces as a precise fault at the exact instruction. Understanding that it is one combined check — not translation followed by a separate security pass — explains why a page cannot be readable-but-untranslatable, and why changing permissions is expensive.
SourceCompilerInstructionsFront EndExecutionRegistersCachesMemoryI/OBehavior

One structure, two answers

A page-table entry does not just say where a page lives. It carries the frame number *and* the permission bits *and* the status bits, and the MMU consumes all of them in one operation. Asking "is this mapped?" and "may I write to it?" are not two lookups; they are one lookup with two failure modes.

That combination is what makes protection cheap enough to be unconditional. If permission checking were a separate structure consulted after translation, it would double the work on the critical path and every design would be tempted to skip it. Fusing them means the check rides along free with a translation you had to do anyway.

It also explains a class of behaviour that looks arbitrary from software: you cannot have a page that is present but unreadable *without* the hardware trapping, because presence and permission live in the same entry and are evaluated together. Marking a page unreadable is how the OS makes the hardware call it on the next access — a mechanism used for guard pages, copy-on-write and lazy allocation alike.

What one page-table entry carries, and who reads it
FieldMeaningConsumed by
Frame numberWhere the physical page actually isMMU, to form the physical address
Present / validWhether a mapping exists at allMMU; absent means trap to the OS
Read / write / executeWhat kinds of access are permittedMMU, on the same lookup
User / supervisorWhich privilege level may access itMMU, against the current mode
Accessed / dirtyWhether it has been read or written since the OS last cleared themSet by hardware, read by the OS for eviction decisions
Cacheability attributesWhether and how the region may be cachedMMU and cache controller, e.g. for device memory

On the critical path, so it must overlap

MICROARCH-SPECIFICVirtually-indexed physically-tagged L1 with overlapped TLB lookup is a common arrangement, not a universal one. Some designs index L1 physically and accept a serialised lookup; some resolve aliasing in other ways entirely.

A naive ordering would be: translate the address, then look up the cache with the physical address. That serialises two lookups on the hottest path in the machine, and the latency would be visible on every access.

The standard escape is that the low bits of a virtual address — the offset within the page — are *not* changed by translation. Only the page number is translated. So the cache can begin its lookup using those untranslated offset bits at the same moment the TLB resolves the frame number, and the two results meet at the tag comparison.

This is not an incidental optimisation. It constrains cache design: for the overlap to work, the bits used to index the cache must come from the untranslated part of the address, which puts a ceiling on how large a simple L1 can be for a given associativity. A surprising amount of cache geometry follows from wanting translation to be free. The scope note matters here — the specific arrangement varies, and some designs use physically-indexed caches with different trade-offs.

low bitshigh bitsindex immediatelytranslate in parallelphysical tagcandidate tagshitVirtual addressPage offset (untranslated)Page numberL1 set lookupTLB → frame numberTag compareData
UserLLMAgentToolDataDecisionHumanGuardrail

When the check fails

A failed check does not return an error code. The instruction is aborted before it takes architectural effect, and control transfers to the OS with enough information to describe exactly what happened: the faulting address, the kind of access attempted, and whether the mapping was absent or merely forbidden.

The precision matters enormously. Because the instruction did not complete, the OS can resolve the situation — allocate a frame, page the data back in, copy a shared page before allowing the write — and then restart the very same instruction, which now succeeds. The program cannot tell that anything happened except by timing.

This is the mechanism underneath a long list of features that look unrelated from software: demand paging, copy-on-write after fork, memory-mapped files, guard pages at the end of a stack, and the segmentation fault you get for dereferencing null. All of them are the OS deliberately arranging for the MMU to trap, then doing something useful in the handler. The OS side of that story is Page Faults.

What the hardware hands the OS on a fault (fields shown generically; the exact register set is ISA-specific)
faulting virtual address : 0x7f3c_a940_1000
access type              : write
privilege at fault       : user
mapping present          : yes
permission allowed write : no          <-- cause
instruction pointer      : 0x0000_5561_2f04

  the instruction did NOT retire; architectural state is unchanged.
  the OS may fix the mapping and restart it, or deliver a signal.

  present=no  + any access  -> demand paging, or an invalid pointer
  present=yes + write denied -> copy-on-write, or a genuinely read-only page
  user access to supervisor  -> a privilege violation

Key points

  • The MMU resolves the mapping and validates permissions in a single lookup, on every access, unconditionally.
  • One page-table entry carries the frame number, the permission bits and the status bits together — which is why the combined check is affordable.
  • Translation overlaps the cache lookup because the page offset is not translated; this constrains cache geometry.
  • A failed check aborts the instruction precisely, so the OS can fix the situation and restart it transparently.
  • Demand paging, copy-on-write, guard pages and segfaults are all the same mechanism: the OS arranging for the MMU to trap.

Follow the mechanism

The path through the machine, hop by hop — and the conclusions it invites that are wrong.

  1. 1
    Load/store unit → MMU: the virtual address and the access type (read, write, or execute) are presented together.
  2. 2
    MMU → TLB: a matching entry is sought; the permission and privilege bits come back with the frame number, not separately.
  3. 3
    MMU → permission check: the requested access type is validated against the entry's bits and the current privilege level.
  4. 4
    MMU → cache: on success the physical address is formed and the access proceeds, having overlapped with the L1 set lookup.
  5. 5
    MMU → trap: on failure the instruction is aborted before retirement and the OS receives the faulting address, access type and cause.
What people conclude from this — wrongly
  • Treating protection as a software convention that a sufficiently privileged program can talk its way around; it is a hardware gate on every access.
  • Assuming a segfault means memory corruption. It usually means an access the MMU was correctly configured to refuse.
  • Expecting mprotect to be cheap because it changes no data — it changes tables the hardware caches, which is the expensive part.
  • Believing a page can be "present but silently unreadable". Presence and permission are evaluated together; failing either traps.

Consequences, controls and cost

What it causes
  • • Permission changes are not free: they require editing tables and invalidating cached translations, sometimes across every core.
  • • A null-pointer dereference fails predictably because the OS deliberately leaves the low page unmapped so the MMU will trap.
  • • Copy-on-write after `fork` costs nothing until a write happens, because the trap is what triggers the copy.
  • • Device memory must be marked uncacheable through these same attributes, or writes intended for hardware would sit in a cache.
  • • The precision of the fault is what makes restartable instructions possible, and therefore what makes demand paging viable at all.
What you can do
  • • Avoid frequent permission changes on hot memory — each one costs table edits and translation invalidation across cores.
  • • Batch `mprotect`-style operations over large regions rather than per-page where the interface allows it.
  • • Use guard pages deliberately for stack and buffer boundaries; you get a precise fault instead of silent corruption, at the cost of a page of address space.
  • • For device or shared-memory regions, get the cacheability attributes right — this is a correctness matter, not a performance one.
  • • Otherwise: almost nothing. The check is unconditional and not under program control; what you can control is how often you make the OS rewrite the entries.
How to see it
  • • Count minor page faults with `perf stat`: high minor-fault rates usually mean demand paging or copy-on-write, not I/O.
  • • Watch for TLB shootdown activity when permissions change frequently — it appears as cross-core interrupt traffic disproportionate to the work.
  • • Trace `mprotect` and `madvise` calls; a hot loop of permission changes is usually an allocator or GC behaviour worth knowing about.
  • • On a fault-heavy workload, separate minor from major faults before drawing any conclusion — they differ by orders of magnitude in cost.
What it costs
  • • Fusing translation and protection makes the check affordable but makes permission changes expensive, because both live in the same cached structure.
  • • Overlapping translation with cache lookup removes the latency but constrains L1 geometry, which limits how large a simple L1 can be.
  • • Precise faults enable restartable instructions and demand paging, at the cost of hardware complexity in tracking exactly which instruction faulted.

Scope

§224 — what these claims are specific to.

What these claims are specific to
  • GENERALThe combined translate-and-check model holds across application-class CPUs with an MMU. MMU-less microcontrollers have no such gate and no process isolation to go with it.
  • ISA-SPECIFICThe exact permission bits, fault-reporting registers and privilege encodings differ between x86-64, AArch64 and RISC-V. The structure of the check is the same; the field layout is not.

Misconceptions

Claim
“The MMU is a performance feature that could be turned off for speed.”
Reality
It is the mechanism process isolation is built from. Turning it off does not yield a faster machine, it yields a machine with one address space and no protection — which is exactly what MMU-less embedded targets are.
Claim
“Permission checks happen at allocation, not on every access.”
Reality
Allocation sets the bits. The check happens on every single access thereafter, because nothing else could catch a pointer that wandered.
Claim
“Changing memory protection is cheap because no data moves.”
Reality
No data moves, but page tables are edited and cached translations must be invalidated — potentially on every core that might hold one. That coordination is the cost.