How KVM, QEMU & Libvirt Work Together
/ 3 min read
Table of Contents
The Low-Level Dance of KVM and QEMU
At its core, virtualization on Linux is a partnership. The KVM (Kernel-based Virtual Machine) module provides direct access to the CPU’s virtualization features, but it only handles CPU and memory virtualization. It doesn’t know what a disk or a network card is. That’s where QEMU comes in.
QEMU emulates the rest of the machine: storage controllers, network interfaces, USB ports, and more. When running with KVM, this is called qemu-kvm
. QEMU prepares the virtual machine environment and then uses the KVM module for the heavy lifting of executing guest code.
The interaction happens through the /dev/kvm
device file. A user-space program like QEMU uses ioctl
system calls to tell KVM what to do. The basic process looks like this in pseudo-code:
// 1. Open the KVM devicekvm_fd = open("/dev/kvm");
// 2. Create a new virtual machinevm_fd = ioctl(kvm_fd, KVM_CREATE_VM);
// 3. Create one or more virtual CPUs (vCPUs)vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU);
// 4. The main execution loopfor (;;) { ioctl(vcpu_fd, KVM_RUN);
// 5. After KVM_RUN, check why it exited switch (exit_reason) { case KVM_EXIT_IO: // Guest tried to access hardware // QEMU emulates the I/O operation here break; case KVM_EXIT_HLT: // Guest halted // QEMU can stop the VM break; // ... other exit reasons }}
This is a simplified view, but it shows the fundamental relationship: QEMU manages the overall VM, but the KVM_RUN
command hands control over to the hardware-accelerated kernel module for maximum performance.
Libvirt: The C Abstraction Layer
As you can imagine, managing all these ioctl
calls and emulated devices directly is incredibly complex. This is why libvirt was created. Libvirt is a toolkit, written primarily in C, that provides a stable, portable, and high-level API to manage virtualization.
It provides a daemon (libvirtd
) that runs on the host and exposes a consistent API, regardless of whether you’re using KVM, Xen, or another hypervisor. Here is a simple C program that uses libvirt
to connect to the local QEMU/KVM hypervisor:
#include <stdio.h>#include <stdlib.h>#include <libvirt/libvirt.h>
int main(int argc, char *argv[]) { virConnectPtr conn;
// Open a read-only connection to the system's QEMU/KVM hypervisor conn = virConnectOpenReadOnly("qemu:///system"); if (conn == NULL) { fprintf(stderr, "Failed to open connection to qemu:///system\n"); return 1; }
printf("Successfully connected to the hypervisor.\n");
// Close the connection virConnectClose(conn); return 0;}
This C code is the foundation. It’s what tools like virsh
and cloud platforms like OpenStack use under the hood.
High-Level Access Through Python
While libvirt’s core is C, it provides bindings for many other languages, including Python. The libvirt-python
library allows you to access the same C API functions from a more convenient, high-level language. The following Python script is functionally equivalent to the C code above:
import libvirtimport sys
conn = Nonetry: # libvirt.openReadOnly() is the Python binding for virConnectOpenReadOnly() conn = libvirt.openReadOnly('qemu:///system')except libvirt.libvirtError as e: print(repr(e), file=sys.stderr) sys.exit(1)
print("Successfully connected to the hypervisor.")conn.close()
Conclusion
KVM, QEMU, and libvirt create a layered architecture for virtualization. KVM provides raw, hardware-accelerated performance at the kernel level. QEMU provides the full machine emulation. And libvirt’s C API offers a stable and powerful interface to manage it all, with bindings that extend its reach to higher-level languages like Python, enabling the automation that powers the modern cloud.