Skip to content

feat(vmm): v0.4 Phase 5a — memfd_create() helper for live-fork backing#186

Merged
WaylandYang merged 3 commits into
mainfrom
feat/v0.4-phase5a-memfd-helper
May 28, 2026
Merged

feat(vmm): v0.4 Phase 5a — memfd_create() helper for live-fork backing#186
WaylandYang merged 3 commits into
mainfrom
feat/v0.4-phase5a-memfd-helper

Conversation

@WaylandYang
Copy link
Copy Markdown
Contributor

First piece of forkd-side wiring for the v0.4 live-fork path. The vendored Firecracker fork (merged context: #185) gives FC the `MAP_SHARED` mmap option; this PR gives the forkd controller the matching helper to produce a memfd whose fd it can hand to FC.

New module: `forkd_vmm::memfd`

Symbol What it does
`MemfdRegion` Owns the fd, exposes `/proc/self/fd/` as `backend_path` for FC's `mem_backend.backend_path`, releases backing pages on Drop
`create_and_populate(source, name)` Opens the snapshot's `memory.bin`, creates memfd with `MFD_CLOEXEC`, `ftruncate`s to source size, copies bytes in. Fails early on missing source — no partial memfd left dangling
`try_clone()` Duplicated File handle for callers that want to mmap the region directly (or for tests)
`size_bytes()` Logical size

Linux-only via `#[cfg(target_os = "linux")]`. Non-Linux stub returns an explicit error rather than silently falling back to file-backed semantics.

Tests (Linux-only, 3 of them)

  • `create_and_populate_succeeds_for_small_file` — fd_path is `/proc/self/fd/N`-shaped, size_bytes matches the 4 KiB source.
  • `populated_memfd_content_matches_source` — copies 8 KiB of a byte-sequenced pattern through, reads it back via try_clone'd fd; catches off-by-one and direction bugs.
  • `missing_source_file_errors` — fails fast, error message includes the missing path (operator-visible error chaining).

```
$ cargo test -p forkd-vmm --lib memfd
test memfd::tests::missing_source_file_errors ... ok
test memfd::tests::create_and_populate_succeeds_for_small_file ... ok
test memfd::tests::populated_memfd_content_matches_source ... ok
test result: ok. 3 passed; 0 failed
```

`cargo fmt --all -- --check` clean. `cargo check --all-targets` clean.

What's deliberately not in this PR

Not wired into `Vm::boot` yet. That's Phase 5b — adds a `BootConfig::with_memfd_backing(...)` option and threads `shared: true` into the JSON sent to Firecracker. Splitting it lets this PR be the zero-runtime-impact piece that lands first; Phase 5b builds on top.

Refs #101, `docs/VENDORED-FIRECRACKER.md`, `DESIGN-v0.4-USER-API.md` Phase 5 breakdown.

First piece of forkd-side wiring for the v0.4 live-fork path. The
patched Firecracker fork (deeplethe/firecracker, branch
forkd-v0.4-mem-backend-shared) gives FC the `MAP_SHARED` mmap option;
this PR gives the forkd controller the matching helper to produce a
memfd whose fd it can hand to FC.

New module: `forkd_vmm::memfd`

- `MemfdRegion` — owns the fd, exposes
  `/proc/self/fd/<N>` as the `mem_backend.backend_path` to send to FC,
  releases the backing pages on Drop.
- `create_and_populate(source, name) -> Result<MemfdRegion>` — opens
  the snapshot's memory.bin, creates a memfd with MFD_CLOEXEC,
  ftruncates it to the source size, copies bytes in. Fails early on
  missing source (no partial memfd left dangling).
- Linux-only via `#[cfg(target_os = "linux")]`. Non-Linux stub returns
  an explicit error rather than silently falling back.

Three tests (Linux-only):

- `create_and_populate_succeeds_for_small_file` — fd_path is
  `/proc/self/fd/N`-shaped, size_bytes matches.
- `populated_memfd_content_matches_source` — copies 8 KiB of a
  byte-sequenced pattern through and reads it back via try_clone'd
  fd; catches off-by-one and direction bugs.
- `missing_source_file_errors` — fails fast, error message includes
  the missing path.

Not wired into Vm::boot yet. That's Phase 5b — adds a
`BootConfig::with_memfd_backing(...)` option and threads `shared: true`
into the JSON sent to Firecracker. Splitting it lets this PR be the
zero-runtime-impact piece that lands first.

Refs #101, docs/VENDORED-FIRECRACKER.md.
@WaylandYang WaylandYang merged commit 7d7db3c into main May 28, 2026
2 checks passed
@WaylandYang WaylandYang deleted the feat/v0.4-phase5a-memfd-helper branch May 28, 2026 18:00
WaylandYang added a commit that referenced this pull request May 29, 2026
…y_with (#187)

* feat(vmm): v0.4 Phase 5b — MemoryBackend::MemfdShared via restore_many_with

Wires the memfd helper from #186 into the existing parallel-restore
path. When ForkOpts::memory_backend == MemoryBackend::MemfdShared,
each restored child gets its own memfd (created from the snapshot's
memory.bin via forkd_vmm::memfd::create_and_populate) and the JSON
PUT to /snapshot/load uses /proc/self/fd/<N> with mem_backend.shared:
true. The patched Firecracker at deeplethe/firecracker:forkd-v0.4-mem-
backend-shared honours the new shared flag and mmaps with MAP_SHARED;
forkd-controller can then arm UFFDIO_WRITEPROTECT on the same backing
in Phase 6 to capture dirty pages asynchronously.

Changes:
- MemoryBackend gains a MemfdShared variant. Docstring spells out
  the patched-FC dependency loudly enough that an operator running
  vanilla FC + setting MemfdShared knows they're on the wrong path.
- Vm gains a public memfd: Option<MemfdRegion> field. Held for the
  VM's lifetime; Drop closes the fd and the kernel reclaims pages.
- restore_many_with's pre-flight check now permits both File and
  MemfdShared; Userfault still bails (still v0.3 scaffolding).
- After spawn, a Phase 1.5 loop creates per-child memfds when
  MemfdShared. Failure surfaces with the child index + the source
  path in the error chain — same Drop-on-error guarantee as the
  helper module.
- The single shared JSON body is replaced with a per-child Vec<String>
  so each child's PUT can reference its own /proc/self/fd/<N> path
  without sharing state.

Not in this PR:
- Fresh-boot (non-snapshot) memfd backing in Vm::boot. v0.4's live-
  fork only needs the restore path; that's the only one wired.
- `forkd doctor` check that the runtime FC binary supports the
  shared flag (Phase 8).

Refs #101, deeplethe/firecracker#1.

* fix(vmm): add memfd: None to Vm::boot constructor (missed in 5b)

* style: drop redundant .into_iter() (clippy::useless_conversion)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant