Technology — Virtio Device Model

Seven devices. Every one a real mechanism, not a checkbox.

FlashVMM's guest sees exactly seven virtio devices — no legacy hardware emulation, nothing a FlashVM doesn't need. Each one below is described by what it actually does under load, including the real bugs found and fixed while proving it out.

virtio-blk: batched, not blocking

Block I/O runs through an io_uring READ+WRITE batcher — a 256-entry ring, default on. FLUSH, DISCARD, and WRITE_ZEROES always run synchronously; batching only applies where it's safe and where it matters for throughput. EMBER_BLK_IO_URING=0 (or off/false) forces the plain synchronous path as a one-line escape hatch; if the host kernel can't create a ring at all (old kernel, rlimit), FlashVMM falls back to sync automatically rather than failing the VM.

flowchart LR
  Guest["Guest issues
READ/WRITE"] --> Ring["io_uring ring
(256 entries)"] Ring --> Batch["Batched submission
to the host kernel"] Batch --> Complete["Completions reaped,
accounted precisely"] Complete --> Guest2["Guest sees result"] Guest -.FLUSH/DISCARD/WRITE_ZEROES.-> Sync["Synchronous path,
always"]
Batching where it's safe; synchronous where correctness demands it.

The completion path was pressure-tested under real conditions until it held exactly, not assumed correct:

  • Completion accounting under vCPU throttling verified precisely (queued=11 reaped=9 traced to source) — closing a silent-corruption path before it shipped.
  • Cold-ring reliability taken from 24/24 requests failing to 0 failures across 1,500 requests — verified on real hardware, not just in review.
  • Read-failure reporting audited end to end so a failed read can never read back as success.

virtio-net: fixed topology, live rate caps

Attached at creation with --nic tap=NAME,mbps=N,pps=N,ingress-mbps=N — all three rate caps required, 0 rejected (no silent "unlimited" default). Topology is fixed once the VM boots: hotplugging a NIC in or out always refuses with 409 hotplug_unsupported, by design, not as a missing feature — a VM's MMIO/IRQ map is built once, at boot. What is live-patchable: the three rate caps on an already-attached NIC (mbps/pps/ingress-mbps), each its own independent token bucket. See Fleet Scale for the full enforcement detail.

virtio-console: two channels, two jobs

COM1 is the guest's raw kernel serial console — what you see in flash logs --stream console. COM2 is the control channel: a structured protocol the guest-agent uses to report Ready, receive Start, and stream metrics and log lines back to the host. Separating them means a noisy kernel console never interferes with the structured control protocol, and vice versa.

virtio-balloon: reclaim on request

Guest RAM size is a ceiling set at creation from the KVM memory mapping — PATCH /memory always refuses with 422 memory_immutable, because the base mapping can't change while the VM exists. The balloon is the cooperative lever inside that ceiling: PATCH /memory/balloon asks the guest to hand back idle pages. The guest can return fewer pages than requested, and a timeout is reported distinctly from "0 pages freed" — the API never conflates "the guest said no" with "the guest is unresponsive."

virtio-mem: the host sets the target, live

Balloon asks; virtio-mem tells. A VM created with --mem-region-mib N gets a second, independently-sized memory region reserved at creation, and from then on the host is authoritative over how much of it the guest actually uses — PATCH /memory/mem {"requested_mib": N} sets a live target, and the guest's own virtio-mem driver onlines or offlines physical blocks to match. GET /memory/mem reports the real, observable result — region_mib, block_kib, requested_mib, plugged_mib — so automation knows a resize actually took effect, not just that it was asked for.

This is host-driven sizing, not guest-cooperative reclaim: the target is set once and the guest converges to it in fine-grained blocks, growing usable RAM on demand or shrinking it back down without a reboot, a stop, or a resize cycle. The declared region is still a ceiling of its own — a target beyond it is refused outright with 422 mem_exceeds_region rather than silently capped, the same fail-loud discipline every resource control on this page follows. A VM created without --mem-region-mib pays nothing for the device it doesn't have: GET/PATCH both answer 422 mem_not_attached, cleanly, with no MMIO window and no boot-time cost.

Right-size a tenant into more RAM once its real footprint is known. Reclaim it deterministically when the host decides, not when the guest gets around to it. Give an elastic workload room for its peak and give that room back the rest of the time — all without touching the VM's original, immutable creation-time ceiling.

virtio-rng: a correctness requirement, not a nicety

This device closes a real, non-obvious hole: FlashVMM restores one golden snapshot into many VMs, and a snapshot captures guest RAM — including the kernel's CRNG state. Without a fresh entropy source, every clone would resume with byte-identical randomness: the same SSH host keys, the same TLS randoms, across the entire fleet. The guest-agent reseeds the kernel CRNG from virtio-rng on every boot specifically to close that gap, not as a generic security nice-to-have.

virtio-9p: one share, fixed at creation

An optional host↔guest shared folder — at most one per VM, attached with --share host_dir=DIR,tag=TAG,at=PATH. Like NIC topology, it's fixed at boot: the 9p device is built once, and PUT/DELETE on an existing VM's share always refuse. If you need a different share, set it correctly at creation time.