From two address worlds to one translator
In guide 1 we drew the great divide of this rung: the logical address a program speaks (counted as if it started at zero, in its own private world) versus the physical address that actually travels on the bus to the RAM chips. We also said that for any of this to work at runtime, something must turn one into the other on every memory reference. That something is a piece of hardware, and this whole guide is about it.
Meet the memory management unit, or MMU. Picture a fast, tireless translator standing between someone who speaks only "program language" and a warehouse clerk who speaks only "real shelf-number language." Every time the program says "fetch item 1000," the translator instantly converts it to the real shelf and passes the request on — and slaps the program's hand away if it reaches for a shelf it has no right to touch. The MMU is that translator, built into the CPU as hardware. It does its work at silicon speed, while your program runs, so the cost is invisible.
Why does it have to be hardware? Recall from the previous guide that modern systems use execution-time address binding: the program is never rewritten with real addresses, so the logical-to-physical conversion must happen freshly on every single access. A program touches memory billions of times a second. If the OS had to translate each reference in software, every memory access would crawl. The MMU exists precisely so that this translation is essentially free — it is the hardware foundation that makes safe sharing of one machine practical at all.
Two numbers describe your whole room
The simplest possible MMU needs just two registers. Think of a hotel handing a guest a room: "Your space begins at door 90000, and it is 5000 doors wide." Two numbers fully describe where the guest may go — where it starts and how big it is. The guest may roam freely inside, but security stops them at the edge. These are the base and limit registers. The base register (also called the relocation register) holds where your space begins; the limit register holds how big it is.
Here is the beautiful part: those same two numbers do two jobs at once. They give you relocation — adding the base turns a logical address into the right physical spot, so the program can be loaded anywhere RAM is free without touching its code. And they give you protection — checking the limit guarantees the program can never name an address outside its own region. The MMU performs both, in a fixed order, on every access. First it checks; only if the check passes does it add. The order matters, because you must catch a trespass before you compute a physical address from it.
logical address from CPU --> [ MMU ]
step 1 (protect): is addr < limit ?
| |
yes no
| |
step 2 (relocate): phys = addr + base TRAP to OS
| (illegal access)
v
to RAM chips
base = 90000 limit = 5000
addr 1200 -> 1200 < 5000 ok -> phys 91200
addr 6000 -> 6000 < 5000 NO -> trapWalking one address through, step by step
Let us make it concrete with a single process. Suppose the OS has placed process P in physical memory with base = 90000 and limit = 5000. That means P is allowed to produce logical addresses 0 through 4999, which the MMU will map to physical 90000 through 94999, and nothing else. Now watch what happens when P executes an instruction that loads the value at logical address 1200.
- The CPU, running P's code, generates the logical address 1200 and presents it to the MMU. The program thinks of this purely as "location 1200 in my own world."
- The MMU compares 1200 against the limit, 5000. Since 1200 is less than 5000, the access is inside P's region and is allowed to continue.
- The MMU adds the base, 90000, to the logical address: 90000 + 1200 = 91200. That is the real physical address.
- Physical address 91200 is sent to the RAM chips, which return the byte stored there. The chips only ever saw 91200; they never knew about 1200. P, for its part, never knew about 91200.
Now the unhappy path. Suppose P, perhaps through a bug, tries to load logical address 6000. The MMU compares 6000 against the limit 5000, sees that 6000 is at or above the limit, and refuses. Instead of adding the base and touching memory, it raises a trap to the OS — an addressing error. The OS typically responds by killing P with what you may know as a segmentation fault. Notice the protection is total: P cannot reach physical 96000 (which might belong to another process) because the check happens before any addition, in hardware, with no software in the loop.
Who is allowed to set the fence?
A fence is only as strong as the rule about who may move it. If a program could load its own base and limit registers, it could simply widen its own room until it covered the whole building — and protection would collapse instantly. So loading these registers is a privileged operation: it uses a privileged instruction that runs only in kernel mode. This is exactly the dual-mode operation you met in an earlier rung, now doing real work. A user-mode program physically cannot change its own boundaries; only the trusted kernel can.
This connects straight back to the context switch from earlier rungs. When the kernel switches the CPU from process P to process Q, P's base and limit no longer describe the running process — they describe the wrong room. So part of every context switch is the kernel reloading the base and limit registers (in modern paging systems, a page-table pointer instead) with Q's values. If it forgot, Q would run with P's fence and could read P's memory. The MMU's protection is real, but it is only as correct as the values the OS loads into it on each switch.
What this simple scheme buys, and what it cannot
The base-and-limit MMU is the textbook starting point, and it is genuinely powerful for how little it costs. It delivers dynamic relocation: because translation happens live on every access, the OS can pick up a process and drop it at a new physical address even while it is suspended, just by changing its base — no rewriting of code, no recompiling. That freedom is what later lets the OS shuffle processes together to reclaim scattered space, and set an idle process aside on disk and bring it back to a different spot.
But be honest about its one big assumption: it gives each process exactly one base and one limit, so each process must occupy a single, unbroken run of physical memory. That is contiguous allocation, and it is the model's hard ceiling. A process cannot be split across two separate holes, and it cannot grow past its limit into space that happens to be free just beyond its neighbor. The next guide shows how this single assumption leads straight to the fragmentation problem — the reason free memory can be plentiful in total yet useless in practice.