Early on, building systems was easy β users didnβt expect much. Itβs those darned users, with their demands of βease of useβ and βhigh performance,β who caused all these headaches. (Thank one, next time you see one.) This chapter earns the memory arcβs vocabulary: what an address space actually is, and why it must be an illusion.
13.1 Early Systems
From memoryβs perspective, early machines provided no abstraction at all:
click a region marked β for details
13.2 Multiprogramming and Time Sharing
Machines cost hundreds of thousands of dollars, so people shared them: first multiprogramming (switch processes on I/O; raise utilization), then β because programmers were tired of agonizing batch debug cycles β time sharing and interactivity.
One crude implementation: give the running process all of memory (as in Figure 13.1), and on every switch save its entire memory to disk and load the next processβs. Correct β and brutally slow, ever slower as memories grow. Registers are cheap to save; megabytes are not. The fix that stuck: leave processes resident in memory and switch between them:
click a region marked β for details
Residency changed the stakes. With multiple programs in memory at once, protection became critical: nobody should read β or worse, write β another processβs memory.
13.3 The Address Space
For the pesky users, the OS needs an easy-to-use abstraction of physical memory: the address space β the running programβs view of memory. It contains all the memory state of the program: the code code segment The address-space region holding the program's instructions β static in size, conventionally placed at the top (low addresses). defined in ch. 13 β open in glossary (instructions), the heap heap Memory region for explicitly requested dynamic allocations β malloc() and free() in C. defined in ch. 4 β open in glossary (malloc/new data), and the stack stack Memory region for function parameters, local variables, and return addresses; allocated by the OS at process creation. defined in ch. 4 β open in glossary (call chain, locals, parameters) β plus odds and ends like statically-initialized variables:
click a region marked β for details
Code sits at the top (static, never grows). Heap and stack take the two ends of the remaining space and grow toward each other β thatβs the convention that lets both expand freely (until threads ruin it, but thatβs Part IIβs problem).
Now the essential trick. The address space is the abstraction the OS provides β the program is not really at physical 0β16KB. Look back at Figure 13.2: process A believes it starts at address 0, but it lives at 320KB. When A loads from address 0, something must quietly redirect that load:
Aβs load of virtual address virtual address An address as seen by a running program β every address a user program generates or prints; translated to physical by OS + hardware. defined in ch. 13 β open in glossary 0 lands at physical address physical address An actual location in the machine's memory β known only to the OS and hardware, never to user programs. defined in ch. 13 β open in glossary 320KB β the key to memory virtualization, underlying every modern computer system.
The Crux: How To Virtualize Memory
How can the OS build this abstraction of a private, potentially large address space for multiple running processes (all sharing memory) on top of a single, physical memory?When the OS pulls this off, it is virtualizing memory: the program thinks itβs loaded at some address (say 0) into a potentially huge space (32 or 64 bitsβ worth); reality differs, quietly. The goals that βwith styleβ implies β and the tools you have as a user to peek at your own virtual space β are next.
Check yourself
1.Why did the save-ALL-of-memory-to-disk approach to time sharing die?
2.What exactly IS an address space?
3.Why are the heap and stack placed at opposite ends of the address space?
4.Process A 'starts at address 0' yet lives at physical 320KB. What must happen when A loads from address 0?
5.Residency (Figure 13.2) made time sharing fast. What problem did it create?