skip to content
Smadi0x86 Blog
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 device
kvm_fd = open("/dev/kvm");
// 2. Create a new virtual machine
vm_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 loop
for (;;) {
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 libvirt
import sys
conn = None
try:
# 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.