Interviewer: “How do VM exits work in your hypervisor, and how did you implement hypercalls?”
If the guest executes a normal instruction, it continues running directly on the CPU. When it executes an instruction configured to cause a VM exit, control returns from the guest to KVM and then back to my userspace hypervisor.
The execution flow is:
$$\text{Guest Native Execution} \longrightarrow \text{VM Exit Trap} \longrightarrow \text{KVM Kernel Module} \longrightarrow \text{KVM\_RUN Returns} \longrightarrow \text{Userspace Handler}$$
KVM stores the exit information in the shared struct kvm_run structure.
In my project, I primarily handle two exit types:
The first is KVM_EXIT_HLT. The guest executes the hlt instruction when it has finished its execution, so I use this exit to terminate the VM execution loop.
The second is KVM_EXIT_IO. I use x86 in and out instructions as a simple hypercall mechanism between the guest and my hypervisor.
The guest is freestanding, so it cannot directly call host functions such as printf. Instead, I provide small assembly helpers that execute an in or out instruction on a specific I/O port.
For example, when the guest executes an out to my character-output port (0xE0), KVM generates a KVM_EXIT_IO. My hypervisor checks:
vcpu->kvm_run->io.direction == KVM_EXIT_IO_OUTvcpu->kvm_run->io.port == 0xE0data_offset: Points to the byte being transferred within kvm_runMy hypervisor reads the character and prints it via putchar().
I designated different I/O ports for different hypercall services:
0xE0: Single character console output0xE1: 32-bit integer formatted output0xE2: String buffer printing via guest address translation0xE3: Guest querying host configuration information0xF0 / 0xF1: Inter-VM Producer/Consumer synchronization notificationsA VM Exit is a hardware-enforced CPU context transition from VMX non-root (guest) mode to VMX root (host/hypervisor) mode.
According to Popek-Goldberg virtualization requirements, all sensitive instructions (such as modifying CR0/CR3, accessing hardware I/O ports, or altering interrupt flags) must trap to the hypervisor so that the guest cannot compromise the host or other virtual machines.
In freestanding guest code, I wrote inline assembly macros:
static inline void outb(uint16_t port, uint8_t val) {
asm volatile("outb %0, %1" : : "a"(val), "Nd"(port) : "memory");
}
When this runs, x86 CPU hardware intercepts the privileged outb instruction, triggers a VM exit (EXIT_REASON_IO_INSTRUCTION), and transfers control to KVM. KVM formats kvm_run->exit_reason = KVM_EXIT_IO, sets direction, port, size, and data_offset, and returns from ioctl(KVM_RUN).
KVM_EXIT_IO_IN and KVM_EXIT_IO_OUT?KVM_EXIT_IO_OUT: Data flows from the guest to the hypervisor. The hypervisor reads the payload at (char *)kvm_run + kvm_run->io.data_offset.KVM_EXIT_IO_IN: Data flows from the hypervisor into the guest. The hypervisor writes the requested value into (char *)kvm_run + kvm_run->io.data_offset before invoking KVM_RUN again, satisfying the guest's in instruction.