arm64: add split_hugepages_at_snapshot to avoid runtime block splits#80
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds opt-in
MachineOptions::split_hugepages_at_snapshot(default off, requiressplit_hugepages, ARM64-only — no effect on amd64). When set,prepare_copy_on_write()splits the shared 2 MB blocks into 4 KB leaf pages at snapshot time, so forks only ever copy-on-write existing 4 KB leaves and never split a block at runtime.Why
On 16 KiB-page ARM64 hosts (Asahi), splitting a shared 2 MB block into 4 KB pages at runtime — during free-running guest execution, driven by a CoW fault — can race the hardware page-table walker and make the guest read stale data, even though the resulting page tables are correct. The failure is masked by single-stepping (a KVM world-switch per instruction) and avoided entirely by never splitting at runtime; it points at a stage-2/walker timing issue below the software layer. This flag is the deterministic mitigation for the cold/warm-fork serving path while the underlying host/KVM interaction is pursued separately.
Note on the implementation
The freshly-split L3 table is allocated in a memory bank, which
foreach_page()does not recurse into (out of main-memory bounds). So its 4 KB leaves are marked cloneable read-only inline in the pre-split branch — otherwise they stay writable and a fork's write bleeds into the shared master. The regression test catches exactly that bleed.Tests
tests/unit/arm64_minimal.cpp— "split_hugepages_at_snapshot pre-splits shared blocks": asserts the block becomes a 4 KB-leaf table and that a fork's write to a pre-split page stays private (master untouched).Cost
~3× fork time and extra page-table memory when enabled; zero impact when off.
🤖 Generated with Claude Code