Introduction
Welcome back! In our previous lesson, we examined the virtio transport mechanism, centered on virtqueues and shared memory. We concluded that while virtio dramatically improves on full hardware emulation, it still has a performance bottleneck: every data packet must traverse from the host kernel up to the QEMU userspace process and then be copied back down to the guest. This round trip involves costly context switches and memory copies.
This lesson addresses that final bottleneck. Our goal is to explain how the vhost-net and vhost-scsi protocols accelerate virtio by offloading the data plane to the host kernel. You'll learn how this widely-used optimization bypasses QEMU for high-speed data transfer, moving I/O processing into a dedicated kernel thread for near-native performance. This is the standard high-performance configuration for KVM, making it a critical topic for performance analysis and debugging in virtualized environments.
1. The Problem: The QEMU Userspace Bottleneck
In the "pure" virtio model we've discussed, QEMU acts as the backend for the virtio device. While it doesn't emulate the low-level registers of a physical NIC, it is still responsible for mediating all data transfers. Let's break down the overhead this creates.
Introduction to VirtIO, Part 2: Vhost
To fully appreciate the problem vhost solves, it's essential to understand the sources of overhead in the standard QEMU-based virtio model. This article from the Oracle Linux Blog, 'Introduction to VirtIO, Part 2: Vhost', provides an excellent, detailed analysis of this overhead.
Please read the initial section, 'Context: VirtIO Data Plane Overhead,' including the subsections 'Limitations of Pure VirtIO' and the detailed 'Transmit Path' and 'Receive Path' walkthroughs. Focus on where context switches and VM exits/entries occur.
As the article you just read explains, the data path for a single network packet involves a costly dance between different execution contexts:
- Transmit (Guest -> Host): The guest driver's "kick" causes a VM exit. KVM then needs to schedule the QEMU userspace thread, resulting in a context switch from kernel to userspace. QEMU processes the packet and uses a syscall to send it to the host's network stack, which is a ring transition back to the kernel.
- Receive (Host -> Guest): The host kernel receives a packet and must wake up the QEMU process (another context switch). QEMU reads the packet, places it in the virtqueue, and then makes an
ioctlsyscall to KVM to inject an interrupt, which involves another VM exit/entry.
The key takeaway is that for every batch of I/O, the QEMU process must be scheduled, perform work, and make syscalls. This user-kernel-user transition is the primary source of overhead.

2. The Solution: Offloading the Data Plane with Vhost
The vhost protocol was designed to eliminate this userspace bottleneck. It moves the entire virtio data plane—the actual processing of virtqueues—from the QEMU process into a dedicated kernel module.
vhost-netis the kernel module forvirtio-netdevices.vhost-scsiis the kernel module forvirtio-scsidevices.
With vhost, QEMU's role is reduced to handling the control plane: initial device setup, feature negotiation, and managing live migration. The high-frequency data operations are handled entirely within the host kernel.
Introduction to VirtIO, Part 2: Vhost
Now, let's explore how the vhost architecture achieves this. We'll return to the Oracle blog post to see the solution.
Please read the sections 'What is Vhost?' and 'Vhost Architecture', focusing on the 'Kernel-based Vhost Architecture' subsection. Pay close attention to the new Tx and Rx paths and the roles of ioeventfd and irqfd.
How Vhost Bypasses QEMU
As you saw in the reading, the vhost architecture fundamentally changes the notification pathway. Instead of QEMU acting as a middleman, KVM facilitates a more direct link between the guest and the vhost kernel thread. Two special mechanisms, implemented using eventfd, are key to this:
-
ioeventfd(Guest-to-Host Kicks):- QEMU tells KVM to associate the virtio device's "doorbell" MMIO address with a specific
eventfdfile descriptor. Thiseventfdis monitored by thevhostkernel thread. - When the guest driver "kicks" the device by writing to the doorbell, it triggers a VM exit as usual.
- However, KVM now sees this address is tied to an
ioeventfd. Instead of waking up QEMU, KVM directly signals theeventfd. - This signal immediately wakes up the sleeping
vhost-netkernel thread to process the available ring. - Result: The context switch to QEMU is completely eliminated from the transmit path.
- QEMU tells KVM to associate the virtio device's "doorbell" MMIO address with a specific
-
irqfd(Host-to-Guest Interrupts):- QEMU creates an
eventfdand registers it with KVM as a virtual interrupt source for the guest (an "irqfd"). It passes thisfdto thevhostmodule. - When the
vhost-netthread has processed buffers and placed them in the used ring, it signals the need for an interrupt by simply writing to theirqfd. - KVM receives this signal and injects the corresponding virtual interrupt into the guest.
- Result: The
vhost-netthread can trigger a guest interrupt without ever needing to communicate with QEMU, eliminating another context switch.
- QEMU creates an
With these optimizations, the data path stays almost entirely within the kernel:
Guest Kernel <-> KVM <-> Vhost Kernel Module <-> Host Kernel Network/Storage Stack
The only remaining overheads are the necessary VM exits for notifications and the data copies between guest RAM and host kernel buffers.
3. Observing Vhost in Action
Theory is great, but seeing is believing. Given your background, you'll appreciate how you can inspect a running system to verify that vhost is active and see the components interacting.
Hands on vhost-net: Do. Or do not. There is no try
This Red Hat blog post provides a hands-on guide to inspecting a vhost-net setup. It shows the exact commands you would use in a real-world scenario to confirm that offloading is working correctly.
Read the sections 'Inspecting the host', 'Generating traffic', and 'Analyzing the performance impact'. You don't need to run the commands yourself, but focus on the command output and the interpretation provided: How ps shows a vhost-[pid] kernel thread. How /proc/[qemu-pid]/fd shows a file descriptor for /dev/vhost-net. How strace on the QEMU process shows a dramatic reduction in ioctl and read/write syscalls when vhost is enabled.
Here’s a summary of the key verification steps from the article:
-
Check for the kernel module:
lsmod | grep vhostThis confirms
vhost_net,vhost, andvhost_iotlbare loaded. -
Identify the vhost kernel thread:
ps -ef | grep vhostYou will see a kernel thread named
[vhost-<qemu_pid>]for each VM using vhost. This is the thread doing the data plane work. -
Inspect QEMU's connection to vhost:
# Find the QEMU process PID pidof qemu-system-x86_64 # List its open file descriptors ls -l /proc/<qemu_pid>/fdIn the output, you will find a file descriptor pointing to
/dev/vhost-net. This character device is the control plane interface that QEMU uses to configure thevhost-netkernel module (e.g., telling it where the virtqueues are in guest memory).
The performance analysis using iperf3 and strace powerfully demonstrates the impact. With vhost disabled, strace shows the QEMU process spending most of its time in read() and ioctl() syscalls. With vhost enabled, those calls virtually disappear, as QEMU is no longer in the data path.
Interview Scenario
An interviewer presents this situation:
"A customer is complaining about poor network throughput and high CPU usage on a host running several network-intensive VMs. They confirm they are using virtio-net drivers in the guests. From the host, you run top and notice that the qemu-system-x86_64 processes corresponding to these VMs have very high CPU utilization. What is your primary suspect for this performance issue, and what three commands would you run on the host to confirm your hypothesis?"
Click to see a model response
-
Primary Suspect: The primary suspect is that vhost-net is disabled or misconfigured. The described symptoms—high
qemuprocess CPU usage despite using virtio—are classic signs that the QEMU process is stuck handling the entire network data plane instead of offloading it to the kernel. -
Confirmation Commands:
-
First, I'd check if a vhost kernel thread exists for the VM:
ps -ef | grep '[v]host'I'm looking for a
[vhost-<pid>]thread corresponding to the problematicqemuprocess PID. If it's missing, vhost is not active for that VM. The[v]is a trick to preventgrepfrom matching its own process. -
Second, I'd verify QEMU's configuration by inspecting its file descriptors:
ls -l /proc/<qemu_pid>/fd | grep vhost-netIf this command returns nothing, it confirms QEMU hasn't opened
/dev/vhost-net, meaning it's not using the offload mechanism. -
Third, to be thorough, I'd ensure the kernel module is loaded at all:
lsmod | grep vhost_netWhile it's usually present on modern systems, its absence would be a definitive root cause.
The absence of the vhost thread and the file descriptor in the QEMU process would confirm that the data plane is not being offloaded, explaining why the QEMU process itself is consuming so much CPU. The solution would be to correct the VM's configuration to enable vhost-net.
-
Conclusion
In this lesson, we bridged the gap between efficient virtio communication and true high-performance I/O by introducing the vhost protocol. By offloading the data plane, vhost removes the QEMU userspace process from the critical path of data transfer.
Key Takeaways:
- The Bottleneck: The standard QEMU virtio backend is limited by context switches and syscalls as data moves between the host kernel and the QEMU userspace process.
- The Solution:
vhost-netandvhost-scsiare kernel modules that take over the virtio data plane, creating a dedicated kernel thread for each VM's I/O. - The Mechanism: QEMU handles the control plane (setup), while the
vhostthread handles the data plane (virtqueue processing). - Key Enablers:
ioeventfdandirqfdallow KVM to mediate notifications directly between the guest and thevhostkernel thread, effectively bypassing QEMU. - Verification: You can confirm vhost is active by looking for the
vhostkernel thread withpsand checking for an open file descriptor to/dev/vhost-netin the QEMU process's/procentry.
Preview of the Next Lesson:
We have now examined the architecture of virtio I/O from multiple performance angles. Next, we will shift our focus from performance to control and introspection. In the upcoming lesson, you will learn to use QEMU's monitor (HMP/QMP) to inspect machine state and memory mappings, giving you a powerful, interactive tool to debug and manage a running virtual machine.