Β§23.1VAX/VMS Virtual Memory

Part I OSTEP pp. 261–267 Β· ~10 min read

  • resident set size
  • segmented fifo
  • demand zeroing
  • copy-on-write

The memory arc ends with assembly: how the pieces β€” tables, TLBs, policies, swapping β€” combine into complete systems, with the features (performance, functionality, security) a real OS needs.

The Crux: How To Build A Complete VM System

What features are needed to realize a complete virtual memory system? How do they improve performance, increase security, or otherwise improve the system?

First case study: VAX/VMS (DEC, late 1970s; principal architect Dave Cutler, who later led Windows NT). A surprising number of its techniques survive today β€” some ideas, even 50-year-old ones, are still worth knowing.

Memory Management Hardware

The VAX-11: 32-bit virtual addresses, 512-byte pages (23-bit VPN, 9-bit offset), with the top two VPN bits selecting a segment β€” chapter 20’s paging-segmentation hybrid, in production silicon:

Seg (2)VPN (21)Offset (9)

Those tiny pages (a historical accident) made linear page tables ruinously large, and VMS fought the bloat twice over: P0 and P1 get separate page tables with base/bounds registers (bounds = table size, so the unused middle costs nothing), and user page tables live in kernel virtual memory β€” segment S β€” where they can themselves be swapped to disk under pressure. Translation grows baroque (find the user PT’s page via the system PT, then translate for real), but hardware TLBs usually skip the whole excursion.

Aside: The Curse Of Generality

VMS had to run on cheap VAXen (yes, that’s the plural) and monsters alike β€” the OS curse of supporting everything means excelling at nothing in particular. No less real today: the same Linux runs your phone, your laptop, and a datacenter node with thousands of processes.

A Real Address Space

Figure 23.1: the VAX/VMS address space (regions not to scale) β€” a REAL address space at last
Page 0: Invalid β“˜0User Code β“˜1User Heap β–Ύ β“˜β–Ό3(unused) β“˜5User Stack β–΄ β“˜β–²7Trap Tables β“˜2Β³ΒΉKernel Data9Kernel Code10Kernel Heap β–Ύ β“˜β–Ό11Unused β“˜132Β³Β²

click a region marked β“˜ for details

Two deliberate oddities repay study. Page 0 is invalid β€” so dereferencing NULL traps instantly:

int *p = NULL; // set p = 0
*p = 10;       // try to store 10 to virtual addr 0

TLB miss on VPN 0 β†’ page table β†’ invalid β†’ trap β†’ (on UNIX, a signal; uncaught, death). Debugging support, encoded in the address space. And the kernel is mapped into every process β€” the S registers simply don’t change on a context switch. The kernel can copy to and from user pointers naturally, page tables can live (and swap) in kernel virtual memory, and the kernel appears to applications as something like a protected library. Protection-level bits in each PTE keep user code out; a construction now used nearly everywhere.

Page Replacement

The VAX PTE: valid, 4 protection bits, a modify bit, 5 OS-reserved bits, PFN β€” and no reference bit. The policy must fly blind. Worse, the designers feared memory hogs monopolizing frames under global policies like LRU. Their answer, segmented FIFO : a per-process page cap β€” the resident set size β€” with FIFO eviction inside it, rescued by global second-chance lists:

Segmented FIFO with second-chance lists: FIFO's simplicity, most of LRU's brains
P's FIFO (RSS = 4)abcdglobal clean listxglobal dirty listw

1Process P sits at its resident set size

P may keep at most RSS = 4 pages in memory, managed as a plain FIFO β€” no reference bit required, which is convenient, because the VAX has none. P now touches a new page, e.

step 1 / 5

Aside: Emulating Reference Bits

Babaoglu and Joy later showed the missing bit was no obstacle: mark pages inaccessible (stashing true protections in the OS-reserved PTE bits); a touch traps, the OS notes β€œreferenced” and restores real protections. Come eviction, the still-inaccessible pages are the cold ones. The art is calibration β€” mark too eagerly and traps swamp you; too lazily and everything looks referenced.

Other Neat Tricks

Two lazy inventions round out VMS β€” both now universal: demand zeroing and copy-on-write :

Two lazy tricks: demand zeroing, then copy-on-write β€” defer the work, and often never do it
Process AS(no frame yet)PTE: inaccessiblework done so far: one page-table entry

1Demand zeroing: a heap page is "added" β€” with no work at all

The process asks for a heap page. Naively the OS would find a frame, ZERO it (a security must β€” otherwise you'd read another process's leftovers), and map it. Instead, VMS just writes an inaccessible PTE and stops. No frame. No zeroing.

step 1 / 5

Tip: Be Lazy

Deferring work can reduce latency (report the write done; flush later) and sometimes eliminates the work entirely (the file gets deleted; the page never gets touched; exec() discards the copy). Laziness works in life too β€” delay the OS project and classmates may debug the spec for you. But the project won’t be canceled. Don’t make professors sad.

Check yourself

1.The VAX's 512-byte pages threatened to drown memory in page tables. How did VMS fight back β€” twice?

2.Two deliberate oddities of the VMS address space: page 0 is invalid, and the kernel appears inside EVERY process's space. Why each?

3.The VAX PTE has no reference bit, and VMS feared memory hogs. Describe the replacement design that answered both.

4.Babaoglu and Joy showed the VAX could fake the missing reference bit. How?

5.VMS's two lazy tricks β€” demand zeroing and copy-on-write β€” share a design skeleton. What is it, and why must fresh heap pages be zeroed at all?

5 questions