ContentsThe library

Memory, All the Way Down

Two Programs Can Hold the Same Address and Never Collide

Print a pointer in two programs and you can get the same number twice. Neither is lying and neither is the real address. Here is what sits in between.

The same number twice

Start two copies of the same program and have each print the address of a

variable. On a machine with the hardening features turned off, they print the

same number. Both programs then write different values through that address,

and neither one sees the other's write.

That is not a trick and nothing was faked. The number is real, the write went

through, and the two writes landed in different places in the memory chips.

The resolution is that the number in your pointer was never a location. It is

an index into a map, the map belongs to the process, and each process has its

own. When the processor executes your load, it consults the map belonging to

whichever process is currently running, finds the hardware location, and reads

that. Change which process is running and the same number resolves somewhere

else.

FIG 1The same address in two processes
address block the prograhardware block in proceshardware block in processhared
heap start10927410
heap next10938420
heap after that10949430
shared library text204816161
shared library text204917171
stack region409633880
The first column is what both programs see and agree on. The next two are where those addresses actually resolve, and they differ everywhere except the marked rows, where both processes point at one copy of the same library. Sharing is not a separate feature here. It is two entries in two maps holding the same number.

Notice what the marked rows make possible. Forty processes running the same

library hold forty maps, all of which name one copy of the code in hardware.

Nothing was copied, nothing is kept in sync, and the saving is not a clever

optimisation layered on top. It is what a map does.

The map is not per byte

The obvious way to build this map is a table with one entry per address. Count

the cost before dismissing it. A 64-bit machine has more addresses than there

are atoms worth caring about, and even a program using four gigabytes would

need four billion entries, each of them large enough to hold a hardware

location. The map would be many times the size of the memory it describes, and

every process would need one.

So the map does not work per byte. It works in fixed-size blocks, four

kilobytes on most machines. An address splits into two parts: the high bits

name a block and are looked up, and the low bits are an offset inside that

block and pass through translation untouched.

FIG 2An address is a block and an offset
the address your program holds, the number a pointer prints
the block number, which is what gets looked up in the map
the block size, four thousand and ninety six bytes on most machines
the offset inside the block, which translation never touches
Because the block size is a power of two, the split is free: the low twelve bits are the offset and everything above them is the block number. No arithmetic is done. The hardware simply reads different bits of the same number for different purposes.

Two consequences follow immediately and both matter later. The map now needs

one entry per four thousand bytes rather than per byte, which is four thousand

times smaller. And translation happens once per block rather than once per

byte, so two addresses in the same block cost one lookup between them.

The second point is worth sitting with. Walking through a block of memory in

order does one translation and then three thousand and ninety-five free

accesses. Walking through memory in steps of four thousand and ninety-six does

a translation every single time. Same number of bytes read, wildly different

cost, and the whole difference is the map.

Following one address down

Even with blocks, a single flat table is too large for a 64-bit address space.

So the map is a tree. Each level is itself a block of memory holding entries,

each entry pointing at the next level down, and the address is cut into pieces

that index one level each.

FIG 3Where a load actually goes
The top path is the one taken for the overwhelming majority of loads. The lower path is what the hardware does when the shortcut misses, and it is the reason the shortcut exists at all.

Read the lower path again. Your program issued one load. The hardware performed

four memory reads to find out where to do the fifth. If that were the normal

case, every program would run at a fifth of its speed, and the entire scheme

would never have been built. It is not the normal case, for a reason that is

the subject of the next lesson.

FIG 4One translation, level by level
steplevelbits of the address usedentries scannedwhat was foundwhat happened
1147 down to 391address of the level 2 tableNine bits index five hundred and twelve entries, which is exactly one block of eight-byte entries. The table sizes are not arbitrary; each level is one block.
2238 down to 301address of the level 3 tableA second memory read. Note that each level narrows the region of address space being described by a factor of five hundred and twelve.
3329 down to 211address of the level 4 tableBy here the entry describes a two megabyte region. Some systems stop at this level and map the whole two megabytes as one large block, which cuts a level off the walk.
4420 down to 121the hardware block number at lastFour reads done. The remaining twelve bits are the offset, which is appended unchanged, and only now can the actual load be issued.
4 steps
Four levels, one entry examined at each, nine bits consumed per level. Nothing is searched and nothing is compared: each step is a direct index, which is why this is done by hardware rather than by code.

What the indirection buys

It is tempting to read all this as a tax, something clever people would have

removed if they could. The opposite is true. Four features that nobody would

give up are the same mechanism viewed from different angles.

Isolation comes first. A process cannot name memory that is not in its map, so

it cannot read another process's data even by accident, even with a wild

pointer, even deliberately. This is not checked by software on each access. It

is enforced by the fact that the address has no meaning outside the map.

Sharing is the marked rows in the first figure. One copy of a library in

hardware, named by every map that wants it. The same mechanism lets two

processes agree to share a region on purpose, which is the cheapest way for

them to communicate.

A heap that grows is the third. When a program asks for more memory, no memory

has to move and nothing has to be contiguous in hardware. Entries are added to

the map, pointing at whatever blocks happen to be free. The program sees one

continuous region; the hardware holds it scattered.

And files you read by dereferencing a pointer are the fourth. Map a file into

your address space and the entries initially point nowhere at all; touching

them causes the system to fetch the data. The file is read by the act of

reading memory, with no request made and no buffer copied. That is a strange

thing to be able to do, and it falls out of the map being an editable object

rather than a fixed property of the machine.

The cost of all four is one translation per block, which is nearly free when a

small cache is doing its job and expensive when it is not. What that cache

costs, how big it is, and what happens to your program when your working set

outgrows it, is where this course goes next.

Recap

  • The address in your pointer is an index into a private map, and the map is different for every process, which is why the same number can mean two different places.
  • Translation is a lookup in a table the hardware walks for you, and the table is itself in memory, so every access you make is secretly several.
  • The indirection is not overhead that somebody failed to remove: isolation, shared libraries, growing a heap and memory-mapped files are all one mechanism.

This is the reading half

Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.

The contents

NextWhat Translation Costs →

The rest of this course

  1. 01Two Programs Can Hold the Same Address and Never Collideyou are here
  2. 02A Few Hundred Entries Decide Whether Your Program Falls Off a Cliffopening only
  3. 03Most Faults Cost a Microsecond and One Costs Ten Thousandopening only
  4. 04If a Cache Hit Took One Second, Main Memory Would Take Four Minutesopening only
  5. 05You Asked for Four Bytes and Sixty-Four Arrivedopening only
  6. 06Predict the Speedup on Paper Before You Change a Lineopening only
  7. 07One Allocator Adds a Number, the Other Goes Lookingopening only
  8. 08Two Threads, No Shared Variables, and One of Them Is Ten Times Sloweropening only

Read alongside