Topic 04: Dual-VM Producer/Consumer Architecture & Synchronization


🎙️ Deep-Dive Spoken Script

Interviewer: “How did the dual-VM producer–consumer system work, and how did you synchronize their shared buffer?”

In the second part of the project, I extended the hypervisor to run two VMs together: one VM acts as the Producer and the other as the Consumer.

Each VM has its own vCPU and its own 2 MB memory region, so they are completely isolated. Because one VM cannot directly access the other's memory, I used the hypervisor as the middle layer for communication.

The basic idea was to maintain a circular buffer of 20 elements in each VM:

struct circular_buffer {
    uint32_t buffer[20];
    uint32_t produce_ptr;
    uint32_t consume_ptr;
};

At startup, each VM tells the hypervisor where its local buffer and its produce and consume pointers are located. It does this through dedicated I/O port exits.

The hypervisor receives these addresses, converts them to host virtual addresses using vm->mem + GVA, and stores pointers to the corresponding structures in both VMs.

Once both VMs are registered, the actual producer-consumer communication starts:

When the Producer generates new items, it writes them into its local circular buffer and updates its produce pointer. It then uses an I/O instruction to notify the hypervisor that new items are available.

The hypervisor handles that I/O exit, identifies which new slots were produced, and copies those values from the Producer's buffer into the Consumer's buffer. It then updates the corresponding produce pointer so that the Consumer knows how much data is available.

Similarly, when the Consumer processes items, it updates its consume pointer and notifies the hypervisor through another I/O exit. The hypervisor then synchronizes the consume pointer back to the Producer.

So the important point is that the VMs never directly access each other's memory. All communication goes through the hypervisor, which receives notifications through I/O exits and synchronizes the buffer contents and pointers between the two isolated VMs.

This gave me a simple way to implement producer-consumer communication while keeping the two VMs isolated.


🔬 In-Depth Q&A Database

Q12: How does the hypervisor enforce isolation between the Producer and Consumer VMs?

Each VM has a completely separate memory slot registered with KVM:

Hardware EPT page tables for VM1 only map into vm1->mem; hardware EPT page tables for VM2 only map into vm2->mem. It is physically impossible for VM1's vCPU to issue an instruction that reads or overwrites VM2's RAM.

Q13: How does the hypervisor synchronize the circular buffer between VM1 and VM2?

  1. Producer Side: VM1 writes items into buf[produce_ptr % 20] and executes outb(PORT_NOTIFY_PROD, count).
  2. Hypervisor Trap: Hypervisor receives KVM_EXIT_IO, inspects how many items were produced, and loops over the ring buffer slots:
   vm2_cb->buffer[slot] = vm1_cb->buffer[slot];
   vm2_cb->produce_ptr = vm1_cb->produce_ptr;
  1. Consumer Side: When VM2 runs, its local produce_ptr now reflects the new items. After consuming, it signals the hypervisor, which copies vm2_cb->consume_ptr back to vm1_cb->consume_ptr.