Β§23.2–23.3The Linux Virtual Memory System … Summary

Part I OSTEP pp. 268–277 Β· ~9 min read

  • kernel logical address
  • kernel virtual address
  • huge pages
  • page cache
  • 2q replacement
  • nx bit
  • return-oriented programming
  • aslr
  • kpti
  • meltdown and spectre

Second case study: the system on your phone, your laptop, and the datacenter β€” whose VM has been driven forward by real engineers solving real production problems.

23.2 The Linux Virtual Memory System

The Linux Address Space

Familiar bones: user portion below, kernel above, kernel identical in every process (and untouchable from user mode). In classic 32-bit Linux the split sits at 0xC0000000:

Figure 23.2: the (classic 32-bit) Linux address space β€” user below 0xC0000000, kernel above, in every process
Page 0: Invalid β“˜0x00000000User Code1User Heap β–Ύβ–Ό4(mappings, libraries, …) β“˜6User Stack β–΄ β“˜β–²9Kernel (Logical) β“˜0xC0000000Kernel (Virtual) β“˜140xFFFFFFFF

click a region marked β“˜ for details

The interesting wrinkle is the kernel’s two address types. Kernel logical addresses (get them with kmalloc) are direct-mapped onto the start of physical memory β€” trivially translatable, never swapped, and physically contiguous, which is exactly what device DMA transfers demand. Kernel virtual addresses (vmalloc) promise only virtual contiguity β€” easier to allocate for large buffers, useless for DMA, and historically the 32-bit kernel’s escape hatch beyond its ~1GB direct map.

Page Table Structure

x86 dictates terms: hardware-managed, multi-level, one table per process β€” the OS writes mappings, points CR3, and gets out of the way. The 64-bit era brought a four-level walk over the bottom 48 bits:

unused (16)P1 (9)P2 (9)P3 (9)P4 (9)Offset (12)

Nine bits per level because 512 eight-byte PTEs fill one 4KB page β€” chapter 20’s fit-in-a-page rule, stacked four high. As memories grow, five- and six-level trees are coming: imagine six translations to find one byte.

Large Page Support

x86 also offers 2MB and 1GB pages, which Linux exposes as huge pages . The driving force is TLB behavior, not table size: big-memory workloads can burn ~10% of their cycles on TLB misses with 4KB pages, and one huge-page entry covers five hundred 4KB entries’ worth of memory (with a shorter miss walk as a bonus). The rollout was deliberately slow: first explicit requests only (mmap/shmget flags, for databases that cared), then β€” once the upsides were proven broadly β€” transparent huge pages, applied automatically. The tax: internal fragmentation, and ugly interactions with swapping.

Tip: Consider Incrementalism

β€œThink big! Change the world!” β€” sometimes. But the huge-page story is engineering incrementalism at its best: specialized support first, learning, then generic support only when justified. Slow, thoughtful, sensible progress β€” in systems, and possibly in life.

The Page Cache

Linux caches aggressively and uniformly: the page cache holds memory-mapped files, file data and metadata, and the anonymous memory (heap, stack) backed by swap β€” all in one hash table, clean/dirty tracked, with pdflush background threads writing dirty pages to their backing store on a timer or a dirty-count threshold. When memory runs low, Linux evicts using a modified 2Q scheme:

Linux page replacement: a modified 2Q β€” protection must be EARNED by a second touch
inactive listp1p2hactive listabcd

1First touch β†’ the inactive list

A page accessed for the first time joins the INACTIVE list. The ACTIVE list holds the established hot set (kept to about two-thirds of the page cache). Nothing gets protected status for merely showing up.

step 1 / 4

Aside: The Ubiquity Of Memory-Mapping

mmap() a file and its contents appear as plain memory β€” faults page the data in on demand. EVERY Linux process is built this way; the loader mmap()s the binary and shared libraries before main() runs. pmap shows the truth of a running shell:
0000000000400000    372K r-xβ€” tcsh
00000000019d5000   1780K rw---   [ anon ]
00007f4e7cf06000   1792K r-xβ€” libc-2.23.so
00007f4e7d508000    148K r-xβ€” libtinfo.so.5.9
00007f4e7d731000    152K r-xβ€” ld-2.23.so
00007f4e7d932000     16K rw---   [ stack ]
Code from the binary, three libraries, the dynamic linker itself β€” plus anonymous heap and stack. Memory-mapped files are how a modern address space is assembled.

Security And Buffer Overflows

The sharpest difference between modern VM systems and their ancestors is the security arms race β€” and it opens with the buffer overflow attack, which starts with one line of trusting code:

int some_function(char *input) {
  char dest_buffer[100];
  strcpy(dest_buffer, input); // oops, unbounded copy!
}

Overflow the buffer with crafted input and you may redirect execution itself. The escalation, round by round:

The memory-security arms race: every defense breeds a cleverer attack
attackdefensethe price
round 1buffer overflow: inject code via an unbounded copyNX bit β€” pages that can never executea page-table bit
round 2ROP: chain "gadgets" already in the binary + libcASLR β€” randomize code/stack/heap placementaddress-space tidiness
round 3Meltdown & Spectre: read protected memory via speculationKPTI β€” unmap the kernel from user page tablesa page-table switch per kernel crossing β€” real performance
Dotted-underlined cells have explanations β€” click one.

You can watch ASLR work from your own shell β€” print a stack address twice:

int main(int argc, char *argv[]) {
    int stack = 0;
    printf("%p\n", &stack);
    return 0;
}
prompt> ./random
0x7ffd3e55d2b4
prompt> ./random
0x7ffe1033b8f4

The NX bit , ROP , ASLR/KASLR, Meltdown & Spectre , and KPTI form a ladder with no visible top: turning speculation off entirely would run thousands of times slower, so the tension is permanent. An interesting time to be alive, if systems security is your thing.

23.3 Summary

Two complete systems, top to bottom. VMS taught the canon β€” hybrid paging/segmentation, kernel-in-every-space, segmented FIFO, demand zeroing, COW β€” and Linux runs on it still: lazy COW fork(), demand zeroing via /dev/zero mappings, a background swap daemon (swapd), plus its own inventions (2Q, transparent huge pages, the unified page cache) and its own scars (KPTI). Read Levy and Lipman’s short, excellent VAX/VMS paper to see the source material behind these chapters. And with that, memory virtualization is complete β€” β€œwhat is any ocean, but a multitude of drops?”

Check yourself

1.Linux has TWO kinds of kernel addresses. Distinguish kmalloc's from vmalloc's β€” and say which one DMA needs.

2.Decompose a 64-bit x86 virtual address as Linux uses it today.

3.What actually drives Linux's huge-page support β€” and how was it rolled out?

4.Linux replaces pages with a modified 2Q scheme rather than plain LRU. What common pattern defeats LRU, and how does 2Q survive it?

5.Order the memory-security arms race, attack and answer.

5 questions