JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

CPU Virtualization: Trap-and-Emulate and VT-x

The first guide showed you what a hypervisor is. This one answers the question that almost broke virtualization: when a guest OS, written to own the whole machine, runs a privileged instruction inside a box, how does the hypervisor stay in charge — without the guest ever noticing it isn't?

The core trick: let the guest run, but trap when it reaches for power

From the first guide you already hold the key idea: a hypervisor runs guest virtual machines, and the guest inside one is an entire operating system that was written believing it owns the bare metal — it expects to set up page tables, mask interrupts, and talk to the CPU's control registers directly. The problem is obvious once you say it out loud: there is only one real CPU, and it cannot let two kernels both own it. So how do you run a guest kernel, which constantly tries to do privileged things, without either letting it actually seize the machine or rewriting it line by line? The answer that made machine virtualization practical is a single, beautiful trick called trap-and-emulate.

Here is the shape of it. You run the guest's ordinary instructions — add, load, store, the millions of harmless arithmetic and memory ops that make up almost all of any program — directly on the real CPU at full native speed. There is no interpreter and no slowdown for those, because the guest is not special; it is just code. The hypervisor only needs to intervene when the guest tries to do something that would affect the real machine's state — a privileged instruction, like reloading the page-table register or halting the CPU. And the hardware already has a mechanism for catching exactly that, one you met two rungs down: you run the guest in unprivileged user mode, so the instant it executes a privileged instruction, the CPU refuses and raises a trap — a general-protection fault — which yanks control to the most-privileged code: the hypervisor.

Now the second half: emulate. The hypervisor catches the trap, looks at which instruction the guest was trying to run and with what operands, and does the right thing on the guest's behalf — but against a private, virtual copy of the machine state, never the real one. If the guest tried to load a new page-table base, the hypervisor records that into the guest's virtual CPU state (and updates its own shadow of the real page tables, which the next guide is entirely about). If the guest tried to disable interrupts, the hypervisor just notes "this guest thinks interrupts are off" in a flag and returns. Then it advances the guest's saved instruction pointer past the faulting instruction and resumes the guest, which never knew anything unusual happened. Trap on the dangerous ones, emulate them safely, run everything else native — that is the whole idea.

When is trap-and-emulate even possible? Popek and Goldberg

Trap-and-emulate only works if every dangerous instruction actually traps when run unprivileged. If even one privileged-ish instruction quietly does something different in user mode instead of faulting, the hypervisor never gets its chance to intervene, and the illusion silently breaks. In 1974, Gerald Popek and Robert Goldberg made this precise in a result that is still the foundation of the field: the Popek and Goldberg theorem. To state it you need two careful categories of instruction, and the difference between them is the entire game.

A privileged instruction is one that traps if executed in user mode and runs only in kernel mode — the CPU's built-in fence. A sensitive instruction is broader: any instruction that reads or changes the machine's privileged state (which CPU mode you're in, the interrupt flag, the page-table register) or whose behaviour depends on that state. The classic worked example: imagine an instruction that reads the current privilege level into a register. It is sensitive — its result depends on real privileged state — but if the hardware lets it run harmlessly in user mode (just returning the real ring number) instead of trapping, it is not privileged. Popek and Goldberg's theorem says trap-and-emulate is possible exactly when every sensitive instruction is also privileged — when the set of dangerous instructions is a subset of the set that traps. If that holds, you can catch them all.

  sensitive  = touches or depends on privileged state (dangerous)
  privileged = traps when run in user mode (catchable)

  Popek-Goldberg, virtualizable when:
      { sensitive } is a subset of { privileged }
      i.e. every dangerous instruction is one we can trap

  The x86 hole (pre-2005):
      some sensitive instructions did NOT trap in user mode
      -> classic trap-and-emulate could not catch them
The condition in one picture: if any sensitive instruction is not privileged, it slips through.

Why the x86 broke the rule — and the awful fixes

Here is the historical sting: the 32-bit x86, the most important CPU in the world, failed the Popek-Goldberg condition. It had a notorious set of about seventeen sensitive-but-not-privileged instructions — the famous example is popf, which pops a value into the flags register. In kernel mode popf can change the interrupt-enable flag; in user mode the same instruction just silently ignores that bit instead of trapping. So a guest kernel running popf to disable interrupts would change nothing and get no fault — the hypervisor never finds out, the guest's mental model of the machine drifts out of sync with reality, and it eventually misbehaves. The CPU that everyone wanted to virtualize was, by the strict theorem, not classically virtualizable at all.

Engineers refused to give up, and the workarounds they built are worth knowing because they show how high the stakes were. VMware's breakthrough around 1999 was binary translation: before running a guest kernel's code, scan it and rewrite the offending sensitive instructions on the fly into safe sequences that do call into the hypervisor — essentially patching the guest at the instruction level so the un-trappable instructions trap after all. It worked, it was a genuine feat of engineering, but it was complex and carried real overhead. A second route, which the next guide on virtio circles back to, was paravirtualization: don't fight the hardware — change the guest. Modify the guest kernel's source so that where it would have run a dangerous instruction it instead makes an explicit, polite call (a hypercall) straight to the hypervisor. Faster and cleaner, but it demands a guest you are allowed to patch, so it can't run an unmodified off-the-shelf OS.

VT-x: when the hardware grew a real place for the hypervisor

The clean fix could only come from the chip itself, and in 2005-2006 it did: Intel's VT-x and AMD's AMD-V brought hardware-assisted virtualization. The central idea is elegant. Instead of cramming the hypervisor and the guest into the same old four privilege rings and hoping traps catch everything, the CPU adds a whole new dimension of execution: two modes called root and non-root. The hypervisor runs in VMX root mode — the new most-privileged place, sometimes pictured as "ring -1," below the guest kernel. The entire guest, kernel and all, runs in VMX non-root mode, where it can use ring 0 and feel like a normal kernel — but its dangerous operations are now configured to bounce out to the hypervisor automatically.

Two new transitions stitch the modes together. A VM-entry runs the hardware down into non-root mode to execute the guest; a VM-exit is the modern, hardware-native successor to the old trap — when the guest does something configured to be intercepted, the CPU automatically saves the guest's full state, switches back to root mode, and lands the hypervisor at a known handler, with a reason code telling it exactly why the exit happened (a privileged instruction, an external interrupt, an attempt to touch a control register). The hypervisor reads the reason, emulates the operation against the guest's virtual state, and issues a VM-entry to resume. It is the same trap-and-emulate dance from section one — but now the trap is a first-class CPU feature that catches every sensitive instruction by design, so the seventeen-instruction x86 hole simply closes. No binary translation, no patched guest: an unmodified Windows or Linux now runs as a guest directly.

All this configuration lives in one in-memory structure the hypervisor controls: the VMCS (Virtual Machine Control Structure; AMD calls its version the VMCB). Think of it as the guest's per-VM control panel. It holds the guest's saved register state, the host state to restore on exit, and crucially a set of control bitmaps that let the hypervisor dial in exactly which events cause a VM-exit — which exceptions, which control-register writes, whether a given I/O port or a specific instruction should bounce out. The hypervisor's whole policy toward a guest is, in large part, just the bits it sets in that structure.

What it costs, and where this leads

Hardware assist made virtualization correct and simple, but be honest about the price: a VM-exit is not free. It is a full hardware context switch — save all guest state, restore host state, run the handler, reverse it on the way back — costing on the order of a thousand cycles, far more than a plain function call. The performance art of a modern hypervisor is therefore avoiding exits: configure the VMCS so the common, harmless operations never exit at all, and only the genuinely rare, genuinely privileged ones do. A guest doing tight computation can run for long stretches with zero exits at near-native speed; a guest hammering virtual devices exits constantly and pays dearly. This is exactly the type-1 versus type-2 performance story from the first guide, now grounded in the concrete event you can count.

Step back and hold the arc. CPU virtualization is solved by letting the guest run native and trapping only when it reaches for privilege; the Popek-Goldberg condition tells you when that is even possible; the x86 violated it and needed binary translation or paravirtualization until VT-x added the root/non-root split and made the VM-exit catch everything cleanly. But notice the gap we deliberately left open: every time the guest touches its own page tables, we said the hypervisor must keep a shadow of the real ones in sync. The CPU is virtualized — memory is not yet, and naively trapping every page-table edit would be a storm of expensive exits. Closing that gap is exactly where the next guide goes: shadow page tables and the hardware answer, EPT.