Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 67

KVM and the Linux Scheduler: Managing vCPU Execution

Recommended resources

Introduction

Welcome back. In our last lesson, we established the fundamental architecture of KVM, focusing on how a user-space process like QEMU uses the /dev/kvm API to create and manage a virtual machine. A key takeaway was that QEMU typically creates one thread for each of the guest's virtual CPUs (vCPUs), and this thread enters a loop calling ioctl(KVM_RUN) to execute guest code.

This naturally leads to a critical question: If vCPUs are just threads in a user-space process, how does the host operating system schedule them? This lesson dives deep into that very topic.

Our learning outcome is to explain how KVM integrates with the host Linux scheduler to manage vCPU execution. We will see that while the basic model is simple—vCPUs are just threads—the integration involves sophisticated mechanisms to ensure correctness and performance, especially in complex scenarios.

By the end of this lesson, you will understand:

  • How the host Linux scheduler perceives and schedules vCPU threads.
  • The role of "preempt notifiers" in coordinating between KVM and the scheduler.
  • The performance challenges that arise from vCPU scheduling, particularly in overcommitted systems.
  • Advanced techniques the kernel uses to optimize vCPU scheduling.

1. The Core Principle: vCPUs as Linux Threads

The most important concept to grasp is that the host's scheduler is not directly aware of "guest operating systems" or "vCPUs." From the perspective of the host Linux kernel's scheduler, such as the Completely Fair Scheduler (CFS), a vCPU is simply a regular thread belonging to the QEMU process.

This has profound implications:

  • Each vCPU thread competes for CPU time against all other threads and processes on the host system.
  • The scheduler applies its standard policies (fairness, priorities, cgroups) to these threads just as it would to a thread from a web server or a compiler.
  • The host OS can, and will, preempt a vCPU thread at any time, even when it's in the middle of executing guest code.

Let's watch a video that explicitly covers this relationship.

Virtualization and Cloud Computing Lecture 4: Hardware-assisted CPU virtualization in KVM/QEMU

This video from Mythili Vutukuru, which we touched on in the last lesson, provides a clear explanation of how the host OS perceives the QEMU process and its vCPU threads. It reinforces the idea that the host scheduler is agnostic to the guest's existence.

Please watch from 19:46 to 22:37. Pay close attention to how the speaker describes the host's view of QEMU as a 'regular multi-threaded process' and clarifies that the host scheduler 'is not aware in any way of the execution of the guest'.

This core principle is also stated very clearly in KVM documentation and presentations.

KVM Process Model

This presentation slide from a UCSD course on operating systems provides a concise summary of the KVM execution model.

Please review slide 10. The text explicitly states, 'A virtual CPU is implemented using a Linux thread. The Linux scheduler is responsible for scheduling a virtual cpu, as it is a normal thread.' This is the central tenet of KVM scheduling.

The diagram below illustrates this model. The host scheduler moves the QEMU threads (representing vCPUs) between different physical CPUs, just like any other task.

vCPU Scheduling in KVM
This diagram shows QEMU pthreads, representing vCPU0 and vCPU1, being managed by the host. The host scheduler can run these threads on any available physical CPU (CPU0, CPU1). The ability to move a vCPU thread from one physical CPU to another is referred to as vCPU migration and is handled entirely by the host scheduler.

2. Deeper Integration: Preemption Notifiers

Treating a vCPU thread like any other thread works well, but it raises a critical issue: What happens when the host scheduler decides to preempt a vCPU thread while it is executing in guest mode (i.e., inside the KVM_RUN ioctl)?

The KVM module might have loaded guest-specific state into the CPU's registers (like FPU or vector registers) that isn't part of the standard Linux task state. A sudden context switch to another process could corrupt this state or lead to information leaks.

To solve this, KVM tightly integrates with the scheduler using a mechanism called preempt notifiers.

  1. KVM registers a notifier function with the kernel's scheduler subsystem.
  2. When the scheduler decides to preempt a task, it checks if a preemption notifier is registered for that task.
  3. If the task is a vCPU thread, the scheduler invokes KVM's notifier function before performing the context switch.
  4. This gives KVM a chance to safely save any special guest state associated with the physical CPU.
  5. Similarly, when the vCPU thread is scheduled to run again, another notification allows KVM to restore that state before re-entering the guest.

This ensures that preemption is handled gracefully without compromising the guest's integrity.

KVM Process Model - Preempt Notifiers

The same presentation we looked at earlier has excellent slides explaining this mechanism.

Please study slides 15, 16, and 17. Slide 16 explains the problem and the solution. Slide 17 provides a diagram showing the sequence: an interrupt or trap occurs, the scheduler decides to switch to another process, and KVM is notified to 'Restore host state' before the context switch proceeds.

This notifier mechanism is a perfect example of the deep, symbiotic relationship between a kernel subsystem (KVM) and a core component like the scheduler. It allows the best of both worlds: KVM can leverage the general-purpose Linux scheduler while still handling the special requirements of virtualization.


3. A Common Interview Scenario: The Overcommitment Problem

Understanding the "vCPU-as-thread" model is essential for diagnosing performance issues, a common topic in advanced technical interviews. Consider a system where you have more vCPUs than physical CPUs, a practice known as overcommitment.

Scenario: You have a host with 4 physical cores. You run a single VM with 8 vCPUs, which in turn runs a heavily multi-threaded database. You observe that the host CPUs are at 100% utilization, but the database throughput is surprisingly low.

What's happening?

  1. Lock Contention: The database workload inside the guest involves frequent locking. A guest thread on vCPU-A acquires a spinlock.
  2. Host Preemption: Before vCPU-A can release the lock, the host scheduler preempts its underlying QEMU thread. The host scheduler is just being "fair" and decides it's time for another thread to run.
  3. Wasted Work: The host scheduler then schedules the thread for vCPU-B. Inside the guest, vCPU-B now needs the same spinlock. It begins to spin, waiting for vCPU-A to release it.
  4. The Bottleneck: vCPU-B is burning 100% of a physical core, but it's doing no useful work. It cannot make progress because the lock holder, vCPU-A, is not currently scheduled to run by the host.

This leads to a "convoy" effect where vCPUs waste physical CPU cycles spinning for locks held by other vCPUs that are descheduled.

CFS Scheduling of Virtual Machines and vCPUs
This diagram abstractly represents scheduling priorities. In our scenario, the 'boosted' task could be the vCPU holding the lock, which we want to run, while the 'yielded' task could be the spinning vCPU, which we want to deschedule temporarily.

To combat this, KVM uses paravirtualization. The guest kernel is made aware that it's running in a virtual environment. When a guest vCPU detects it's spinning on a lock, instead of spinning endlessly, it can execute a KVM_HYPERCALL. This is a special instruction that triggers a VM Exit and tells the host: "I am spinning waiting for another vCPU. It would be better to deschedule me and run the vCPU that holds the lock."

KVM can then use the yield_to() mechanism in the host scheduler to give preference to the lock-holding vCPU thread. However, this is not a perfect solution and is an area of active development.


4. Advanced Optimizations in vCPU Scheduling

The simple yield_to() mechanism has limitations. For instance, the scheduling preference might be too brief, leading to a "ping-pong" effect where the lock holder runs for a moment, gets preempted again, and the spinning resumes.

This is where you can demonstrate deep, up-to-date knowledge relevant to open-source contributions. A recent patch series on the Linux Kernel Mailing List (LKML) addresses these exact inefficiencies.

sched/kvm: Semantics-aware vCPU scheduling

This LWN.net article describes a patch series aimed at improving vCPU scheduling in overcommitted scenarios. It provides a fantastic real-world example of the problems we've discussed and the sophisticated solutions being developed. This is precisely the kind of material that sets a candidate apart in an interview.

Please read the 'Problem Statement' and 'Solution Overview' sections. Focus on understanding: The scheduler-side limitation: Why the existing yield_to() buddy mechanism is insufficient. The KVM-side limitation: How the scheduler can lack awareness of guest-internal events like Inter-Processor Interrupts (IPIs). The proposed solution: Augmenting the scheduler with a 'vCPU Debooster' that applies a vruntime penalty to provide sustained preference, and making KVM's yield mechanism aware of IPIs.

As you read, appreciate the complexity here. The proposed solution isn't just a simple flag; it involves:

  • vruntime penalties: Manipulating a task's virtual runtime in the CFS scheduler to make it appear less "deserving" of CPU time, thereby giving other tasks (the lock holder) a better chance to run for a sustained period.
  • IPI-awareness: Actively tracking Inter-Processor Interrupts within the guest to make more intelligent guesses about which vCPU should be boosted.
  • Cgroup and EEVDF awareness: Ensuring these new heuristics work correctly with modern scheduler features like control groups and the new EEVDF scheduler.

Understanding these ongoing developments shows you're not just familiar with textbook concepts but are tracking the state-of-the-art in kernel engineering.


Conclusion

In this lesson, we've explored the nuanced relationship between KVM and the Linux scheduler. We started with the simple and elegant core principle that vCPUs are just threads, but quickly delved into the complexities required to make this model robust and performant.

Key Takeaways:

  • vCPUs are Threads: The host Linux scheduler treats vCPU threads from the QEMU process like any other task, applying standard scheduling policies.
  • Preempt Notifiers: KVM uses scheduler notifiers to safely manage guest-specific state during host-level preemptions, enabling correct context switching.
  • Overcommitment Challenges: When vCPUs outnumber physical CPUs, contention for guest-level resources (like spinlocks) can lead to inefficient host CPU usage, as descheduled lock-holders block other running vCPUs.
  • Advanced Scheduler-KVM Cooperation: To mitigate these issues, the kernel employs mechanisms like paravirtualized yield_to() and is actively developing more advanced heuristics, such as vruntime penalties and IPI-aware yielding, to make scheduling decisions more intelligent.

Preview of the Next Lesson:

We have now covered how KVM handles CPU virtualization and scheduling. The next critical piece of the puzzle is memory. In our upcoming lesson, "Describe how KVM utilizes hardware support (EPT/NPT) for efficient guest memory virtualization," we will investigate how KVM manages guest physical and virtual memory, leveraging hardware features like Extended Page Tables (EPT) to provide both isolation and performance.