Why does any of this exist?
For most of human history, calculation was something people did by hand. The word computer once meant a person employed to do arithmetic, and from the late nineteenth century that person was often a woman. The machine you are reading this on replaced an entire profession.
Replacing the human computer took a particular insight: that thinking itself could be mechanised. It applies to one kind of thinking only: following fixed rules step by step, so that the same input always gives the same output. That was exactly the work human computers did.
The 1936 paper that changed everything
In 1936, a 24-year-old Cambridge mathematician named Alan Turing published a paper titled On Computable Numbers, with an Application to the Entscheidungsproblem. The title's last word is German for "decision problem" and names a question David Hilbert had posed in 1928: is there a mechanical procedure that, given any mathematical statement, will decide whether the statement is true or false?
Turing's answer was no. To prove it, he first pinned down what "mechanical procedure" meant by imagining a device: an endless tape of cells, a head that reads and writes one cell at a time as it moves, and a finite set of rules saying what to do for each symbol under the head and each state the machine can be in. A machine's rules, its program, can be written as symbols on a tape for another machine to read. The modern form of his proof narrows Hilbert's question to one case: can a machine read any program and decide whether it will ever stop? A program is only symbols and rules, so "this program stops" is a mathematical statement, and a procedure that decides every statement would settle it. Suppose such a decider exists. Build a program that runs the decider's rules on a copy of itself, asking "will this very program stop?", and then does the opposite: told it will stop, it loops forever; told it will never stop, it stops. The decider is wrong about this program either way, so it cannot exist, and neither can Hilbert's procedure.
He called his imagined device an a-machine, for automatic machine; we now call it a Turing machine. He also showed that one machine, with the right rules, can read any other machine's rules from its tape and do whatever that machine would do. He called such a machine universal. It is the first formal description of what we now call a computer.
The head reads one cell at a time and does what the matching rule says: on a mark it moves right, on an empty cell it writes a mark and stops. Following just these two rules, it has added one.
How it works
- Turing's model. In 1936 Turing analysed what a person does when computing by rules with pencil and paper: looks at a few squares, keeps a state of mind, writes, moves on. His machine is that person reduced to parts: tape for the paper, head for the eye and pencil, rules for the rulebook.
- Tape. An endless row of cells, one symbol in each; the head sees one cell at a time.
- Rule. For the current state and the symbol under the head, a rule says what to write, where to move and which state comes next.
- This program. The marks count like tallies, one mark for each unit: walking right to the first empty cell and writing a mark there adds one.
- Universal. A machine whose rules read another machine's rules from its tape can run any of them: the universal machine of the text.
"We can only see a short distance ahead, but we can see plenty there that needs to be done."
Alan Turing, 1950The war that built the first computers
Theory met urgency in 1939. Britain needed to read German messages scrambled by the Enigma machine (Fig 1.2), whose settings changed every day and could be chosen in more than 158 quintillion ways, far too many to try by hand. At Bletchley Park, Turing designed the Bombe, an electromechanical machine built to find the day's setting. Codebreakers could often guess a few words of a message, such as a weather report, and Enigma had a flaw that narrowed the search: it never turned a letter into itself, so the guessed words could only sit where none of their letters matched the scrambled letter beneath. The Bombe did not try every setting. It raced through the positions of Enigma's turning wheels, the rotors, and threw out each one that could not have turned the guessed words into the scrambled text, leaving a handful for people to check. Later, his colleague Tommy Flowers built Colossus, the first programmable electronic digital computer, set up with switches and plugs for different tests against the Lorenz cipher of Hitler's high command. Each machine served one purpose only, so neither was universal in Turing's sense, but together they proved that mechanical computation worked at scale.
The same key, pressed three times, lights three different lamps: each press steps the fast rotor, so the current takes a new path out and, after the reflector, a different one back. The reflector also guarantees that no letter is ever enciphered as itself, the flaw the Bombe exploited.
How it works
- Press. The fast rotor steps one position first, so its wiring meets the contacts differently every time.
- Out. The current runs through the three rotors, each a fixed scramble of 26 wires.
- Turn. The reflector pairs contacts and sends the current back through the rotors on another wire.
- Lamp. It arrives at a different letter, and never at the one pressed.
- The settings. Rotor order, start positions and plugboard pairs gave more than 158 × 1018 possible settings, changed daily.
Some historians estimate that the codebreaking at Bletchley Park shortened the war in Europe by two years or more. The question after the war became how to build a machine that isn't purpose-built for a single problem: a universal machine, Turing's theoretical idea, in actual hardware.
Historical note. Turing was prosecuted for homosexuality in 1952 and given probation on condition of chemical castration. He died of cyanide poisoning in 1954; the inquest ruled it suicide. Britain issued a formal apology in 2009, and he now appears on the £50 note.
Everything is sand
The first general-purpose electronic computer, ENIAC (1945), was built from vacuum tubes: glass bulbs that controlled electrical current using a heated filament inside a vacuum. ENIAC had 17,468 of them. It had thirty metres of cabinets lining the walls of a room, weighed thirty tons, consumed 150 kilowatts, and broke down on average every two days because a tube would burn out.
To make computers smaller, faster, more reliable, and affordable, the vacuum tube had to go. Its replacement was invented in 1947 at Bell Labs in New Jersey by John Bardeen and Walter Brattain, physicists in William Shockley's group. Bell Labs named it the transistor, a contraction of transfer resistor.
A vacuum tube and a transistor do the same job: a small control signal decides whether a much larger current flows. The transistor does it in a sliver of crystal, with no glass, no glowing filament and nothing to burn out.
How it works
- Tube. A filament, kept hot, boils electrons off; the plate draws them across the vacuum; the grid between them, set by the control signal, lets them through or holds them back.
- Transistor. The control signal goes to the base, and current flows through the crystal between the other two terminals (the next figures open it).
- Why it won. A tube is hot, fragile and wears out: one of ENIAC's 17,468 tubes failed about every two days. A transistor has nothing to burn out, and it shrinks.
What a transistor actually is
A transistor is a switch with no moving parts, flipped by electricity instead of a finger. A small voltage on its base (the control terminal) lets a much larger current flow between the other two, the collector (one end of the path) and the emitter (the other end); with no voltage there, nothing flows.
Off and on can stand for 0 and 1. Two switches in a row let current through only when both are on, a rule called AND. Circuits that apply such rules, wired together, can add numbers, hold them and carry out a program like a Turing machine's (Chapter 2 builds these circuits, called logic gates, up to one that adds).
Left, 0 V on the base and the lamp stays dark; right, 0.7 V and it lights. This is the older kind of transistor; modern chips are built from the field-effect kind, opened in Fig 1.5.
How it works
- Three terminals. Collector and emitter carry the current; the base controls it.
- The threshold. In this kind of transistor about 0.7 V on the base opens the path; below it, almost nothing flows. In the field-effect transistors of modern chips the control terminal is called the gate (Fig 1.5).
- The dots. Electrons, drifting from emitter to collector for as long as the base signal is there.
Why semiconductors
A switch with no moving parts cannot open a gap in a wire, so the material itself must stop and start conducting when told. A semiconductor, a material whose conductivity sits between a conductor (like copper) and an insulator (like glass), can be made to. In pure silicon the atoms bond by sharing their outer electrons, so almost none move freely. What changes that is doping: salting the silicon with a trace of another element. An added atom with one more outer electron than silicon has no bond for the extra one, which wanders free (n-type silicon); one with one fewer leaves a gap in a bond, a "hole" (p-type).
A field-effect transistor puts p-type silicon between two n-type regions, the source and the drain. Where the kinds meet, free electrons drop into the nearby holes and leave a thin barrier with nothing free to move, so no current flows. Over the p-type silicon, on a thin insulating oxide, sits the third terminal, the gate, which does the base's job: a voltage on it draws free electrons out of the source and drain and in under the gate, where they form a bridge (Fig 1.5). Silicon and oxide both come from sand, which is mostly silicon dioxide: the silicon is refined from it, and the oxide is the same compound.
A positive voltage on the gate draws electrons in from the source and drain until they form a thin channel under the oxide, and current flows. The gate never touches them: its charge pulls through the insulator, as a rubbed balloon lifts hair, and that influence across a gap, an electric field, names the field-effect transistor.
How it works
- The silicon. The body is p-type silicon, doped with electron-accepting atoms; the source and drain are n-type, rich in free electrons. With the gate off, nothing joins them.
- The field. The gate's voltage acts across the oxide as an electric field. No charge crosses the insulator.
- The channel. The field gathers electrons, drawn in from the source and drain, under the oxide until that thin layer inverts, from p-type to n-type, and conducts.
- The name. MOSFET: metal-oxide-semiconductor field-effect transistor. A modern CPU holds billions of them, each a few tens of nanometres across.
In 1958, Jack Kilby at Texas Instruments made the first integrated circuit: a transistor and the other parts of a small circuit, formed in one piece of germanium (another semiconductor), though still joined by hand with fine wires. Within months, Robert Noyce at Fairchild Semiconductor conceived the same on silicon with the wiring laid on the chip itself, and his more manufacturable process became the basis of the entire industry. From then on, more parts on a chip meant smaller parts, not more wiring.
In 1965, Gordon Moore, Noyce's colleague at Fairchild and later a co-founder of Intel, observed that the number of transistors that fit on one integrated circuit was doubling about every year, and predicted it would continue; he later revised this to every two years. This pattern, Moore's Law, held for about fifty years, until around 2015, and drove the semiconductor industry's roadmap. It is why your phone, with perhaps fifteen billion transistors, exists at all.
On a log scale, where each gridline is ten times the one below (every second one is labelled), steady doubling draws a straight line. The chips follow the dashed two-year line for more than forty years, then fall below it: 2,300 transistors in 1971, 28 billion in 2024.
How it works
- The scale. Each gridline is ten times the one below, so growth at a steady rate of doubling is a straight line.
- The chips. 4004 (1971, 2,300) · 8086 (1978, 29 K) · 386 (1985, 275 K) · Pentium (1993, 3.1 M) · Pentium 4 (2000, 42 M) · Core i7 (2008, 731 M) · Epyc (2017, 19.2 B across four chips in one package) · M4 (2024, 28 B).
- The line. Doubling every two years from the 4004. Across the whole span the chips doubled about every two and a quarter years.
Bardeen and Brattain show the first working transistor on December 23, 1947; with Shockley, they share the 1956 Nobel Prize.
Silicon wins over germanium because it grows a better insulating oxide on its own surface.
Moore's article, "Cramming More Components onto Integrated Circuits", appears in the magazine Electronics in April 1965.
The 4004 put a whole processor, the part that carries out a program's instructions, on one chip: 2,300 transistors, paced by a clock ticking 740,000 times a second. Intel built it for a Japanese calculator maker.
Apple's M4 holds 28 billion transistors on a 3-nanometre process, each smaller than the width of a flu virus. The doubling has slowed since about 2015, as physical limits near.
ENIAC's thirty metres of cabinets held 17,468 vacuum tubes; the M4 fits under a fingertip and holds 28 billion transistors, about 1.6 million times as many switches. Each is drawn at its own scale: in life ENIAC's row of cabinets is some two thousand times longer than the chip.
How it works
- The same job. Both machines flip switches according to instructions; what changed is the size of the switch, from a glass tube to a speck of silicon.
- The red flashes. As the figure plays, a tube burns out. ENIAC lost one about every two days; a chip's transistors run for years.
- The numbers. 28,000,000,000 ÷ 17,468 ≈ 1.6 million. (From the 4004 of 1971 to the M4 the factor is twelve million: Fig 1.6.)
Von Neumann's insight: instructions are data
Early computers were hardwired. To set up a new calculation on ENIAC, its operators (almost all women, called "the ENIAC girls") unplugged and replugged cables for days. To change what the machine did, you changed the machine.
In 1945 the mathematician John von Neumann wrote the First Draft of a Report on the EDVAC, the machine ENIAC's builders planned as its successor. It described a different architecture, one that nearly every computer built since has followed.
The report's central idea: store the program itself in memory, alongside the data it operates on. Turing's universal machine had done this on paper; the report turned it into a design for real hardware, with one fast memory holding both. Instructions and data are both patterns of bits (ones and zeros), and the processor (the CPU) reads both the same way, so a program can read, change or create another program: a compiler, which turns code in a language such as C into the CPU's own instructions, is a program that writes programs.
A stored-program computer is never rewired: to change what it does, you load a different program into its memory. Each instruction is a number in bits: a code for an operation plus the number of the memory cell or the value it works on. The CPU's program counter names the next cell to run; that cell is read as an instruction, and any cell the instruction names, as data. In Fig 1.8, load A copies the 5 in cell 3 (A is the program's name for that cell) into a register (a slot in the CPU holding one value); add B adds the 3 in cell 4, and store C writes the 8 into cell 5.
One memory holds the program and its data; one bus, shared wires that the input and output devices (I/O) also use, carries both to the CPU one transfer at a time. There the control unit reads each instruction and the ALU (arithmetic and logic unit) computes.
How it works
- Fetch. The program counter names the next cell; its instruction travels over the bus to the control unit.
- Execute. The instruction asks for data, which travels over the same bus; the ALU does the arithmetic: 5 + 3.
- Store. The result goes back up the bus into memory, and the counter moves on.
- One bus. Code and data share it, so they take turns: the bottleneck the next sections return to.
The von Neumann bottleneck
Because instructions and data share one memory, they share one bus, and it carries one transfer at a time: even fetching the next instruction is one. While CPUs got faster over the decades (their clocks went from hundreds of thousands of ticks a second to billions), main memory became faster much more slowly. A modern CPU performs a basic operation in under a nanosecond (a billionth of a second); a round trip to main memory takes about sixty nanoseconds. Without help, the CPU would spend most of its time waiting.
The main remedy is a hierarchy of caches: small, fast memory built into the CPU that holds copies of recently used data, so most reads never make the trip to main memory. Much of CPU design since the 1980s is an effort to work around this bottleneck.
Fetch. Decode. Execute. Repeat.
Every CPU ever built does the same four things in a loop, today billions of times per second.
This loop is the instruction cycle. The program counter holds the address of the next instruction. In Fig 1.8 each cell held a whole instruction; a real memory numbers every byte (eight bits), and an instruction may take one byte or several. Fetch: the CPU reads the instruction's bytes out of memory. Decode: the control unit works out which operation those bits ask for and which registers or values it acts on. Execute: the unit that does the work (usually the ALU) carries it out, adding, comparing, or computing an address. Writeback: the result is stored back into a register or memory, and the program counter moves to the next instruction. Then the cycle starts over, on every core (a complete copy of this machinery). Fig 1.9 writes addresses in hexadecimal (0x), base sixteen, because each of its digits, 0–9 or A–F, stands for exactly four bits.
Follow the program counter: 0x00, then 0x05, then 0x08 (addresses written in hexadecimal, here the same as 0, 5 and 8), because the first instruction takes five bytes and the second three; at 0x08 the jump sets it back to 0x00, so the program loops.
How it works
- Fetch. Read the bytes at the address in the program counter.
- Decode. Work out the operation and its operands from the bit pattern (Fig 1.10 takes one apart).
- Execute. The ALU or another unit does the work: an addition, a comparison, an address.
- Writeback. Store the result and move the counter on by the instruction's length; a jump sets the counter instead.
What an instruction actually is
To the hardware, an instruction is a pattern of bits, and the control unit is a circuit that turns each pattern into a particular sequence of internal signals. The mapping from bit patterns to operations is called the instruction set architecture, or ISA. Intel and AMD CPUs use the x86 ISA; Apple Silicon Macs, your phone and the Raspberry Pi use ARM. The two are incompatible: code compiled for one will not run on the other unless translated.
Assembly language gives each pattern a human-readable name, and a program
called an assembler translates the names into bits, one for one:
mov eax, 5 becomes
10111000 00000101 00000000 00000000 00000000 on x86. We will spend
all of Chapter 3 on this.
; A simple addition program ; The steps behind an addition like C's "a + b", with a = 5 and b = 3 held in registers mov eax, 5 ; load the value 5 into register EAX mov ebx, 3 ; load the value 3 into register EBX add eax, ebx ; EAX = EAX + EBX, result is now 8 ; In memory, "add eax, ebx" is just two bytes: 01 D8 ; The CPU's decoder maps this pattern to "ALU add" with ; source EBX and destination EAX. Then the ALU performs it.
One instruction as the CPU sees it: the first byte says what to do and to which register, the next four hold the value, lowest byte first.
How it works
- Opcode. B8 is 10111 000. The top five bits mean “move a 32-bit value into a register”; the bottom three name the register, and 000 is EAX.
- Value. x86 is little-endian: it stores the lowest byte first, so 5 sits in memory as 05 00 00 00.
- Decode. The decoder is a circuit that recognises these bit patterns and routes them to the right units; nothing is looked up by name.
Pipelining and parallelism inside one core
Instead of finishing one instruction before starting the next, modern CPUs use pipelining: they cut the four steps into stages and overlap the stages of different instructions, like an assembly line. While instruction 3 is being executed, instruction 4 is being decoded, and instruction 5 is being fetched. Fig 1.11 adds a fifth stage, MEM, for reading or writing data in memory; a modern pipeline may have 14 to 20. Each stage takes one clock tick, or cycle.
Five instructions of five stages each, overlapped like an assembly line, finish in 9 cycles instead of 25. Each still takes five cycles; the gain is that once the pipeline is full, one instruction completes every cycle.
How it works
- The stages. IF fetch · ID decode · EX execute · MEM memory access · WB writeback.
- The diagonal. Each instruction starts one cycle after the one before, so the instructions in a column are always in different stages; in cycle 5 all five stages are busy at once.
- Beyond this. Modern CPUs also duplicate stages and finish several instructions in one cycle.
Modern CPUs also use out-of-order execution. If instruction 5 doesn't depend on instruction 4's result, the CPU may execute 5 first while 4 waits for memory. Speculative execution goes further. At a branch, a jump taken only if some condition holds (an if in a program), the pipeline must keep fetching before the condition is known, so the CPU guesses which way it will go and runs ahead on that path. A right guess keeps the work and saves the wait; a wrong one throws it away.
The guessed path runs before the branch is decided. If the guess is wrong its work is thrown away, but the data it loaded stays in the cache, a trace that the attacks described below, Spectre and Meltdown, read.
How it works
- Why guess. Waiting for the condition would leave the pipeline idle for many cycles.
- The guess. Made from the branch's history; modern CPUs are right about 95% of the time.
- The catch. Throwing work away rolls back the registers, not the cache. A load that should never have run leaves its data behind, and an attacker can time the cache to find it.
Spectre and Meltdown (2018). Spectre affects nearly every fast processor made since the mid-1990s, Meltdown mainly Intel's; both let a program read memory it may not see, such as another program's passwords. Both exploit speculative execution. The CPU runs ahead (in Meltdown, past a permission check that will fail; in Spectre, down a wrongly guessed branch) into work that reads a secret byte. That work then loads one of 256 memory locations, the one numbered by the byte's value (a byte has 256 possible values). The work is thrown away, but the loaded location stays cached, so the attacker times a read of each of the 256: the one that answers fast gives away the byte. Chapter 16 returns to them.
Why the kernel had to be invented
In the 1950s, running a program meant booking the entire computer for yourself: you handed in your stack of punched cards, the machine ran it alone, and you came back later for the printout.
An illustrative hour: your job runs for ten minutes of it, and between jobs, while the operator loaded the next deck, the most expensive machine in the building sat idle.
How it works
- A job. A deck of punched cards, roughly one line of program per card, carried to the machine room.
- The queue. The operator loaded decks one after another; each job had the whole computer while it ran.
- The cost. A bug meant new cards and another hour's wait, and the machine stood idle in the gaps.
As computers got faster, the CPU sat idle most of the time even while a job ran, waiting for slow input/output (I/O) devices such as card readers and tape drives. Universities started asking: can one program run while another waits for I/O? Can different users be logged in at once?
The answer was time-sharing: programs taking turns on one CPU, so quickly that each user seems to have the machine alone. Running the turns takes something between the programs and the hardware: the operating system kernel, the one program that stays loaded. A program that needs something slow (a disk, a keypress, a network reply) must ask the kernel, and asking makes the CPU jump into the kernel's code. While the program waits, the kernel saves where it stopped (its program counter and registers) and gives the CPU to another program with work ready; when the data arrives, it reloads that program's counter and registers and the program carries on. A program that never asks is interrupted anyway: a hardware timer hands the CPU to the kernel every few milliseconds. Programs sharing a machine must not trample each other, so the kernel owns the hardware and every other program must ask its permission to touch it (Fig 1.14).
The CPU runs each program at a privilege level, or ring: Ring 3 for programs, Ring 0 for the kernel. The red path, a program reaching straight for the disk, is blocked; the gold one, a request to the kernel called a system call, is let through.
How it works
- The rings. x86 has four privilege levels; operating systems use two: Ring 0 for the kernel, Ring 3 for everything else.
- Enforced in silicon. In Ring 3 the CPU itself refuses privileged instructions and the kernel's memory; no software stands in the way to be fooled.
- Driver, scheduler. Two parts of the kernel: a driver gives a device its orders; the scheduler decides which program gets the CPU next (the turns of the text).
The system call: crossing the boundary
When your program opens a file, it cannot touch the disk itself: in Ring 3 it may not give the disk orders. Instead it asks the kernel through a system call, the one instruction whose whole job is to cross the boundary: it switches the CPU to Ring 0 and jumps to an entry point, an address the kernel chose at boot, so a program can ask but never pick where it lands. The kernel checks permission and does the work, and your program gets back a small number (a file descriptor) that names the file in every later read or write. The Bridge, the short chapter that closes Part I after Chapter 3, shows how the CPU makes that crossing safe; Chapter 4 shows what the kernel does with the request.
That boundary, enforced by the CPU itself, is the foundation of operating system security: without it, every program could read every other program's memory, every file and every network packet.
UNIX and the kernels we still use
In 1969, at Bell Labs, Ken Thompson and Dennis Ritchie designed an operating system called UNIX around a four-word creed: everything is a file. A file on disk, the keyboard and, in later versions, a network connection or even a running program are all read and written through file descriptors, so once a program holds one, the same read and write calls work on any of them. The kernel calls each running program a process: on Linux, reading the file /proc/1234/status returns process 1234's name and memory use as text. UNIX became the model for most operating systems in use today (Linux, macOS, iOS, Android), and the ideas it spread (processes, file descriptors, the system call interface) define what an operating system is. Chapter 5 tells the story of C, the language it is written in.
Why this matters for security. In a privilege escalation attack, a malicious program running in Ring 3 gains Ring 0 power, for instance through a bug in the kernel code that handles its requests. If it succeeds, it controls the whole machine: it can read any file and watch any keypress. Every major operating system has had such bugs, which makes the kernel the most security-critical code on any computer. Part II's kernel chapter and Part IV's security chapter return to it.
The hierarchy of forgetting
A computer's memory is a hierarchy of storage, managed at different levels by the CPU and the kernel, that grows larger, slower and cheaper the further it sits from the CPU. Each level holds a copy of part of the level below it.
Section 03's gap, on one scale: arithmetic takes about 0.3 nanoseconds, a round trip to main memory (RAM) 60 nanoseconds, two hundred times longer, and a read from a fast SSD (solid-state drive) 50,000 nanoseconds, about 167,000 times longer than the arithmetic. Hiding the gap to RAM is the job of the caches: three levels of them, L1 to L3, sit between the CPU and RAM.
| Level | Location | Typical size | Access time | Managed by |
|---|---|---|---|---|
| Registers | Inside the CPU core | ~1 KB | 1 cycle (~0.3 ns) | Compiler, CPU |
| L1 Cache | On the CPU die | 32–64 KB | 4 cycles (~1.2 ns) | CPU hardware |
| L2 Cache | On the CPU die | 1–3 MB | 12 cycles (~3.6 ns) | CPU hardware |
| L3 Cache | On the CPU die, shared by cores | 8–64 MB | 40 cycles (~12 ns) | CPU hardware |
| RAM (DRAM) | On the motherboard | 8–128 GB | ~200 cycles (~60 ns) | OS kernel |
| SSD (NVMe) | On the PCIe bus | 256 GB – 4 TB | ~50,000 ns | OS + filesystem |
| HDD (spinning disk) | SATA bus | 1–20 TB | ~5,000,000 ns | OS + filesystem |
Each level down is bigger and far slower: at a scale where a register answers in one second, a hard disk takes six months. A cache keeps a copy of what was just read, so reading it again costs seconds, not minutes.
How it works
- The scale. One register access, about 0.3 ns, becomes one second; every other time keeps its ratio to it. Bar length is size on a log scale.
- A miss. The first read looks in L1, L2 and L3, finds nothing, and waits for RAM: about 60 ns.
- A copy. On the way back each cache keeps a copy of the data and its neighbours.
- A hit. The next read of the same data, or of data next to it, stops at L1: about 1.2 ns.
The hierarchy works because programs do not use memory evenly. They keep returning to the same places and the places next to them: a loop that adds up a list of numbers runs the same few instructions again and again, and reads numbers stored side by side. This is called locality of reference, and it is why a small fast cache can speed up a much larger slow memory. In numbers, the average time per access is:
Tavg = h · tcache + (1 − h) · tRAM
If the hit rate h, the share of accesses found in the cache, is 95% for L1 (tcache = 1 ns) and the other 5% miss into RAM (tRAM = 60 ns), the average access time is 0.95 × 1 + 0.05 × 60 = 3.95 ns. Without the cache, every access would cost 60 ns. The cache makes the average access roughly fifteen times faster, and the hit rate only has to be high, not perfect. The same calculation justifies every layer of the hierarchy.
Peter Denning's working set model (1968) made the idea rigorous for virtual memory (just below): a program needs in fast memory only the pages it has used recently. Processor caches rest on the same principle. It returns wherever a small fast store sits in front of a large slow one: recently used file data kept in RAM (Part II), recent web-address lookups remembered (Part III), a database's busiest data held in memory (Part IV).
Virtual memory: the lie the kernel tells
When your program asks for memory, the kernel does not give it a real physical address. It gives a virtual address, a fiction: every program sees its own private range of addresses, starting at zero and seemingly gigabytes long, as if it had the machine to itself. So two programs can both use address 1,000 and reach different places in the real memory.
Between the CPU and the RAM sits the Memory Management Unit (MMU), a piece of hardware that translates every virtual address to a physical one at the moment of access. Memory is handled in fixed-size blocks called pages, typically 4 KB (4,096 bytes), and for each program the kernel keeps page tables that say which physical page holds each of its virtual pages. Take address 1,000: it is byte 1,000 of virtual page 0. If one program's table maps its page 0 to physical page 37, the MMU reads byte 1,000 of page 37; the other program's table sends its page 0 elsewhere. The Bridge shows the hardware at work, and Chapter 4 the kernel's side.
Page tables give three things. Isolation: no program can read another's memory, because different programs' page tables point to different physical pages. Flexibility: the kernel can move pages around in RAM, send little-used ones out to disk, or read a program's file in from disk only as its pages are first used. Protection: the page tables also encode permissions for read, write and execute, and the MMU enforces them. When a program touches a page that is not in RAM, the MMU stops it with a page fault and calls the kernel, which reads the page in, updates the table and lets the program carry on, none the wiser.
Buffer overflow attacks pass the MMU's write checks, because they write only into the program's own writable memory. Each call to a function (a reusable piece of the program) keeps its buffers, fixed-size blocks for incoming data, in its own slot on the stack (the memory that function calls work in), beside its return address: the address the CPU jumps back to when the function ends. If a program writes more data than a buffer can hold, the extra bytes spill over that return address, so an attacker who controls the input can fill the buffer with instructions and aim the return address at them. Those instructions can run only if the stack's pages allow execution, so marking them no-execute (the defence called DEP) lets the MMU's execute check stop them. Chapter 3 shows the defences, among them the stack canary, ASLR and DEP, at the assembly level.
What you now understand
You have followed the chain from sand to software. Transistors, silicon switches controlled by voltage, combine into logic gates, then into integrated circuits, then into CPUs. The CPU runs an endless loop: fetch, decode, execute, writeback. It executes instructions encoded as binary patterns drawn from an instruction set architecture like x86 or ARM. Programs and data live together in the same memory, a design choice (von Neumann's) that defines what a modern computer is. The kernel mediates between programs and hardware, enforcing isolation and security through privilege rings, virtual memory and a hardware timer.
This is the substrate. Every chapter that follows builds on it. Chapter 2 goes
deeper into a single layer of this stack: the layer where mathematics and
electricity meet. We will look at why a computer must be binary, how George Boole
invented the logic that runs on those binary signals, and how arithmetic
emerges from a few simple gates. By the end
of Chapter 2, you will understand why 0.1 + 0.2
does not equal 0.3 in almost any programming language.