Skip to content

Commit 71e2e40

Browse files
ZhiXiao-LinRoy Lin
andauthored
docs: native snapshot-fork feasibility (GO — extend our libkrun fork) (#27)
Code survey of A3S-Lab/libkrun (vendored checkout): vCPU/VM state save+restore already exists as dead code; pause/resume half-wired; TEE guest_memfd is an in-tree precedent for non-anonymous RAM; device serialization missing — but our use case snapshots an IDLE deferred-main template (quiesced queues), shrinking device state to queue registration + the virtio-fs inode map (the one hard item). Native path chosen over a Firecracker/CH second backend (no virtio-fs in FC; no adapter layer; single VMM). 4-phase plan, Phase A = RAM+CPU snapshot/restore with MAP_PRIVATE CoW, go/no-go in ~2 weeks. Co-authored-by: Roy Lin <roylin@a3s.box>
1 parent 1138157 commit 71e2e40

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Feasibility: Native snapshot-fork in our libkrun fork
2+
3+
Status: **survey complete — GO, native path chosen over a second VMM.**
4+
Supersedes the backend-selection question left open in
5+
`cow-snapshot-fork-design.md` (§2): instead of adding a Firecracker/Cloud
6+
Hypervisor second backend, we extend **our own libkrun fork**
7+
([A3S-Lab/libkrun](https://github.com/A3S-Lab/libkrun), vendored as a git
8+
checkout at `src/deps/libkrun-sys/vendor/libkrun`, built by our `build.rs`).
9+
10+
## Why native (vs a second VMM)
11+
12+
- Single VMM: rootfs (virtio-fs dir), vsock exec server, networking, lifecycle
13+
all stay as-is — no adapter layer, no dual ops surface. (Firecracker has no
14+
virtio-fs at all; CH would still mean a permanent second backend.)
15+
- We already patch vendored deps (the CRI vendored-h2 patch precedent).
16+
- The fork's VMM is Firecracker-derived and **already contains** much of the
17+
hard state code (below).
18+
19+
## What the survey found (vendor/libkrun/src, x86_64 Linux)
20+
21+
| Area | Status |
22+
|---|---|
23+
| vCPU state save+restore (regs/sregs/xsave/MSRs/LAPIC/CPUID, ordered) | **EXISTS**`vmm/src/linux/vstate.rs:1302-1416` (`#[allow(unused)]` dead code) |
24+
| VM state save+restore (PIT, clock, PIC/IOAPIC irqchips) | **EXISTS**`vstate.rs:834-892` |
25+
| vCPU Pause/Resume events | **HALF-WIRED**`VcpuEvent::Pause/Resume` + `VcpuHandle::pause()` exist; `Vmm::resume_vcpus()` is public, `pause_vcpus()` missing |
26+
| Guest RAM | anonymous mmap (`builder.rs:3057`); **but the TEE path already uses `guest_memfd`** — in-tree precedent for non-anonymous backing |
27+
| Virtio device state serialization (fs/net/vsock/console/balloon/rng) | **MISSING entirely** — no Persist trait, nothing |
28+
| Snapshot orchestration / FFI | **MISSING** |
29+
30+
## The key scoping insight: snapshot an IDLE deferred-main template
31+
32+
The generic "snapshot any running VM" problem (Firecracker's) is dominated by
33+
device state. **Our use case doesn't need it.** The pool snapshots a template VM
34+
that booted with `deferred_main` (P2) and is **quiesced**: no container main, the
35+
exec server blocked on accept, virtio queues empty, no in-flight I/O. At that
36+
point:
37+
38+
- queue rings live in guest RAM (captured by the RAM snapshot); host-side device
39+
state shrinks to per-queue ring addresses + ready/activation status + device
40+
config — small, explicit structs;
41+
- vsock: no live connections at idle → muxer state is empty;
42+
- console: stream positions only;
43+
- **virtio-fs is the one real device problem**: the in-process passthrough
44+
server holds a host-side inode/nodeid map the guest references. It must be
45+
serialized (or rebuilt deterministically) — this is the riskiest single item.
46+
47+
## Phased plan (Linux x86_64 only first)
48+
49+
1. **Phase A — RAM + CPU snapshot/restore of a quiesced idle VM (~1.5–2w)**
50+
- memfd/file-backed guest RAM (mirror the TEE `guest_memfd` plumbing for
51+
non-TEE).
52+
- `Vmm::pause_vcpus()` (wire the existing Pause event), then
53+
`krun_snapshot(path)`: vCPU+VM state (existing code) + RAM dump + per-queue
54+
MMIO/virtio registration state.
55+
- `krun_restore(path)`: rebuild the VM, **`MAP_PRIVATE` the RAM file**
56+
(kernel page-level CoW — this is the fork), restore vCPU/VM state, re-plug
57+
devices with the saved queue state, resume.
58+
- Exit criterion: an idle deferred-main alpine template restores and answers
59+
an exec heartbeat. Measure restore latency (target ~10–150ms).
60+
2. **Phase B — virtio-fs inode-map persistence (~1–2w, the hard part)**
61+
- Serialize the passthrough server's nodeid→inode map (paths + generation),
62+
re-open on restore. Exit criterion: restored VM can read/write its rootfs
63+
and run `spawn-main` to completion with correct logs/exit code.
64+
3. **Phase C — box/pool integration (~1w)**
65+
- Template manager: boot idle template → snapshot → N×`krun_restore` children;
66+
re-stamp identity via the existing exec server (P2 spawn-main already gives
67+
the command path). Pool flag `--fork` falling back to full boots.
68+
4. **Phase D — hardening**: concurrent restores, snapshot invalidation
69+
(image digest + config + libkrun version), KSM interaction, aarch64 later.
70+
71+
Estimated total: **4–6 weeks** (matches the survey's verdict), with Phase A
72+
delivering a measurable go/no-go checkpoint in ~2 weeks.
73+
74+
## Risks
75+
76+
| risk | mitigation |
77+
|---|---|
78+
| virtio-fs inode map (guest holds nodeids) | Phase B dedicated; fallback: restart the fs device + remount in guest via a guest-init hook (slower, uglier) |
79+
| `vm_memory` crate hides mmap flags | TEE guest_memfd path shows where to hook; worst case small vm-memory patch in-fork |
80+
| device quiesce atomicity | snapshot only IDLE deferred-main templates (enforced by the box side) |
81+
| fork maintenance burden | changes confined to fork; upstream rebases already our responsibility |

0 commit comments

Comments
 (0)