Many tenants, one apartment block
In the earlier rungs you learned that a running program is a process — a recipe being cooked, not the cookbook on the shelf — and that each process believes it has its own address space stretching from address 0 upward. We took that for granted. Now we earn it. Open your laptop's task list right now: a browser, an editor, a music player, a dozen background helpers, all live at the same moment. Each one was compiled as if it owned the machine, and each refers to a byte 'at address 1000'. Yet there is only one bank of physical RAM, and address 1000 can only be one place. How can all of them be telling the truth?
Picture an apartment block. Every tenant has flats numbered '1', '2', '3' on their own front door — flat 1 to one family means something completely different from flat 1 to another. The trick is that those door numbers are private to each unit; the building's real wiring uses different, global numbers that no tenant ever sees. The building manager — our kernel — keeps a quiet mapping from 'tenant Lee's flat 3' to 'the actual unit on the fourth floor, east wing'. This is the whole idea of memory management: let every process keep its own simple, private numbering, while the system maps those private numbers onto the one shared block of real memory. We will lean on this exact picture for the rest of the rung.
Two kinds of address
So there are genuinely two different things that we have both been sloppily calling 'an address'. The number the program generates — the door number on the tenant's flat — is a logical address (also called a virtual address). It is what the CPU's instructions actually work with: 'load the value at 1000', 'jump to 4096'. The number that finally lands on the memory bus and selects a real cell of RAM — the building's true wiring number — is a physical address. The set of all logical addresses a process can name is its logical (or virtual) address space; the set of real cells is the physical address space. They are not the same set, and they almost never line up one-to-one.
Here is the single most important sentence of this guide: the CPU never sees physical addresses, and the RAM chips never see logical ones. The processor speaks only in logical addresses; the memory hardware understands only physical ones. Between them sits a translator that converts every logical address into its physical counterpart, on every single memory reference, while the program runs. We will meet that translator — the memory management unit, the MMU — properly in the next guide. For now, just hold the picture: the program says '1000', the translator turns it into, say, '15384', and only 15384 ever touches the real RAM.
When does '1000' get pinned to a real cell?
If the program just says '1000', when does the system decide which real cell that means? This question of when a name gets tied to a physical location is called address binding (address binding), and it can happen at three different moments, each more flexible than the last. The earliest is compile time: if you somehow knew the program would always load at physical address 8000, the compiler could bake the real numbers straight in. This is what tiny embedded firmware does, but it is rigid — move the program and every address is wrong.
The middle option is load time. The compiler leaves the addresses 'relocatable' — written relative to a starting point of 0 — and when the loader places the program in memory, it adds the chosen starting address to each, a step called relocation. This is more flexible, but once loaded the program is stuck at that spot; you cannot move it afterward without redoing the arithmetic. The last and most powerful option is execution time, where binding is deferred until each reference actually runs and is done by hardware. Only this last choice lets the OS pick a program up and drop it somewhere else in RAM while it is running — and it is exactly what the MMU buys us.
Three moments a name can become a real cell: compile time | real addresses baked in | rigid, never moves load time | loader adds a start offset | fixed once placed execution time | hardware translates each ref| can move while running <-- MMU Program says: logical 1000 At run time: 1000 --[ translate ]--> physical 15384
Why bother? Isolation, relocation, and sharing
This indirection is not idle cleverness; it buys three things we cannot live without. First, isolation and protection. Because the browser's logical 1000 and the editor's logical 1000 translate to different physical cells, neither can read or scribble on the other's memory by accident — or on purpose. The translator can also refuse a translation entirely if a process reaches outside its allotment, which is how a stray pointer earns a clean crash instead of silently corrupting a neighbour. One faulty program no longer takes the whole machine down with it.
Second, free placement. Since the program names addresses relative to 0 and the hardware adds the real offset, the OS can load it anywhere there happens to be room, and even shuffle it elsewhere later — the same flexibility that, in the next few guides, lets us pack many processes into one RAM and even set sleeping ones aside on disk. Third, sharing. Two processes can be handed translations that point at the very same physical cells — for instance one read-only copy of a common library — so a single physical block serves many tenants at once. The same machinery that walls processes off from each other can, when the OS chooses, let them safely overlap.
What this rung will build
You now hold the load-bearing distinction for everything that follows: logical address (what the program says) versus physical address (where the byte really lives), joined by a translation that happens on every access. The rest of this rung makes that translation concrete and then watches it strain. Next we meet the MMU in its simplest honest form — a base register that holds where the process starts and a limit register that holds how big it is, adding the base to translate and checking the limit to protect.
From there the questions cascade, and each later guide in this rung answers one. If we give each process a single contiguous block, how do we choose which free hole to use — first fit, best fit, or worst fit — and why does memory end up riddled with unusable gaps, both the wasted slivers inside a block and the scattered holes between them (fragmentation, perhaps healed by compaction or by swapping a process out to disk)? How does the linker stitch your code together with libraries — statically baked in, or dynamically shared at run time — and the loader bring the finished image to life? And finally, what if we stop pretending memory is one flat block and instead give the program a logical view of several meaningful pieces — code, data, stack — through segmentation and its segment table? Every one of these is just a further answer to today's question: how do many programs honestly share one memory?