Designing Embedded Hypervisor Components – Part 1
This article explains how PRTOS implements a separated kernel architecture with paravirtualization, details the hardware‑dependency layer, outlines the constraints of embedded virtualization, and walks through CPU frequency calibration and interrupt‑vector initialization for x86‑based embedded hypervisors.
PRTOS architecture
PRTOS runs in processor privileged mode and virtualizes the CPU, memory, interrupts, and selected peripherals. Its architecture consists of four layers: hardware‑dependency layer, virtualization service layer, internal service layer, and a hypercall interface library.
Limits of embedded virtualization
Limited hardware resources (processing power, memory, storage) make the overhead of managing virtual machines costly.
Many embedded MCUs (e.g., 32‑bit ARMv7) lack hardware virtualization extensions.
Real‑time requirements must be satisfied by the guest OS, the application, and the hypervisor.
Pipeline and cache characteristics of single‑ or multi‑core CPUs directly affect system latency.
Because the hypervisor is a shared software layer for all partitions, it must have the highest security level in a safety‑critical system.
Partition hardware resources
A partition (virtual machine or domain) runs user code as if on native hardware. Six hardware resources are virtualized for a partition:
Special CPU registers (e.g., CR3, GDTR, IDTR).
Interrupt controller.
Clock/timer.
MMU paging hardware.
I/O ports.
Cache management.
Three resources are not virtualized:
The partition’s own memory address space (directly accessible).
Non‑privileged instructions (e.g., ADD) execute directly on the physical CPU.
Hardware cache, which remains transparent to the hypervisor.
On multi‑core platforms the hypervisor must address cache‑coherency and information‑leakage issues. Partitions are typically flushed of caches on scheduling, and shared‑memory contention can cause unpredictable response times, requiring more complex WCET estimation at the partition level.
Processor driver
The processor driver initializes the processor clock, interrupt controller, and memory controller. On Intel x86 the CPU frequency is measured using the 64‑bit Time Stamp Counter (TSC) together with the 8253/8254 programmable interval timer (PIT) channel 2.
With a default LATCH value of 65 535 the PIT output frequency is 1 193 180 / 65 535 ≈ 18.2 Hz. Setting LATCH = 1 193 180 / 100 yields an output of 100 Hz (10 ms period). The pulse precision equals 1 / CPU_frequency (e.g., 2 ns for a 500 MHz CPU).
#define CLOCK_TICK_RATE 1193180 // clock frequency Hz
#define PIT_CH2 0x42
#define PIT_MODE 0x43
#define CALIBRATE_MULT 100
#define CALIBRATE_CYCLES (CLOCK_TICK_RATE / CALIBRATE_MULT)
__VBOOT prtos_u32_t calculate_cpu_freq(void) {
prtos_u64_t c_start, c_stop, delta;
out_byte((in_byte(0x61) & ~0x02) | 0x01, 0x61);
out_byte(0xb0, PIT_MODE); // binary, mode 0, LSB/MSB, channel 2
out_byte(CALIBRATE_CYCLES & 0xff, PIT_CH2); // low 8 bits
out_byte(CALIBRATE_CYCLES >> 8, PIT_CH2); // high 8 bits
c_start = read_tsc_load_low();
delta = read_tsc_load_low();
delta = read_tsc_load_low() - delta;
while ((in_byte(0x61) & 0x20) == 0);
c_stop = read_tsc_load_low();
return (c_stop - (c_start + delta)) * CALIBRATE_MULT;
}Source path: core/kernel/x86/processor.c in the PRTOS repository (https://github.com/prtos-project/prtos-hypervisor).
Interrupt vector initialization
Interrupts originate either externally or from the CPU itself (traps). Exceptions are synchronous events such as division‑by‑zero. All interrupts are handled by locating the appropriate service routine via an interrupt vector stored in memory.
In x86, vector entries are called “gates”. Four gate types exist: task, interrupt, trap, and call gates. PRTOS only initializes interrupt and trap gates. Entering an interrupt gate automatically clears the IF flag to prevent nested interrupts, whereas a trap gate leaves IF unchanged.
PRTOS uses the Global Descriptor Table (GDT) for segment descriptors. The Interrupt Descriptor Table (IDT) is defined and initialized as follows:
// source path: core/kernel/x86/head.S
#include <linkage.h>
#include <arch/irqs.h>
#include <arch/asm_offsets.h>
#include <arch/segments.h>
#include <arch/prtos_def.h>
...
ENTRY(idt_desc) // interrupt descriptor table
.word IDT_ENTRIES*8-1
.long _VIRT2PHYS(hyp_idt_table)
...
ENTRY(hyp_idt_table) // IDT definition
.zero IDT_ENTRIES*8The function setup_x86_idt() (source: core/kernel/x86/irqs.c) completes the initialization of external interrupt vectors (assumed 16) and reserves 19 trap/exception entries.
Linux Code Review Hub
A professional Linux technology community and learning platform covering the kernel, memory management, process management, file system and I/O, performance tuning, device drivers, virtualization, and cloud computing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
