Stage 1: per-VM native stacks, allocated but not yet executed on (FABRIC-3.md §XXVIII)
Second stage of the preemptive context-switching plan. Every VM (Hera, every capsule_birth_baby()-born VM including WIREBIND identities) now gets its own dedicated 2 MiB native C stack at birth -- but nothing runs on it yet, that's Stage 2. Pure allocation-machinery proof. Design correction made before writing code: the plan called for cloning sk_vm_arena_alloc()'s guard-page pattern, but that pattern turns out to be Mama-only -- host_services.c's kernel_alloc() gives every baby VM a plain kmalloc() block for its dictionary arena, not a real guarded PMM allocation. Stacks get the real treatment instead (new sk_vm_native_stack_alloc()/_free() in arena.c): independent pmm_alloc_contiguous() + guard pages for every VM without exception, no singleton, no kmalloc fallback -- a stack overflow is exactly the failure mode guard pages exist for, and a corrupted stack could corrupt whatever saved context Stage 2 trusts. 2 MiB size matches this project's own established kernel-stack convention (g_kernel_stack/g_rpi5_native_stack), not a guess -- that one shared 2 MiB stack today already carries all VMs' combined nested VM-EXEC recursion. Three new VM struct fields, freed in vm_cleanup() alongside the existing call_stack free. Allocation failure is non-fatal to birth. All 3 architectures re-verified clean boot to ok>, no native-stack allocation failures for any Tripod-fleet VM. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016UNhH1mhi52i6Qihh7ZV5S
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
15672ce17c
commit
57ac3fc304
@@ -34,6 +34,7 @@
|
||||
#include "starkernel/kmalloc.h"
|
||||
#include "starkernel/console.h"
|
||||
#include "starkernel/vm/stadium.h" /* item 4.1a -- stadium_grant_quota() */
|
||||
#include "starkernel/vm/arena.h" /* FABRIC-3.md §XXVIII Stage 1 -- sk_vm_native_stack_alloc() */
|
||||
#include "starkernel/session.h" /* session_register()/session_set_pinned() -- FABRIC-2.md §H.12 step 5 */
|
||||
#include "starkernel/timer.h" /* timer_calibration_record()->vm_mode -- FABRIC-2.md §I.5 CONTRIB trust tier */
|
||||
#include "vm.h"
|
||||
@@ -627,6 +628,26 @@ CapsuleRunResult capsule_birth_baby(
|
||||
* own reservoir, not vm_uuid_hera()'s (item 4.1's hardcoded default). */
|
||||
((VM *)new_vm)->stadium_vm_id = vm_id;
|
||||
|
||||
/* FABRIC-3.md §XXVIII, Stage 1 (2026-09-13): every VM gets its own
|
||||
* native stack from the moment it exists, including WIREBIND-birthed
|
||||
* identity VMs -- purely additive here, nothing executes on it yet
|
||||
* (that's Stage 2). Allocation failure is not fatal to birth itself:
|
||||
* a VM with no native stack yet is exactly as functional today as
|
||||
* every VM has always been (nothing currently runs on a dedicated
|
||||
* per-VM stack), so this degrades to "not yet switchable," not
|
||||
* "stillborn." native_stack_top stays 0, the pre-Stage-2 default. */
|
||||
{
|
||||
sk_vm_native_stack_t ns;
|
||||
if (sk_vm_native_stack_alloc(&ns) == 0) {
|
||||
((VM *)new_vm)->native_stack_paddr = ns.paddr;
|
||||
((VM *)new_vm)->native_stack_guard_vaddr = ns.guard_vaddr;
|
||||
((VM *)new_vm)->native_stack_top = ns.stack_top;
|
||||
} else {
|
||||
log_message(LOG_WARN, "capsule_birth_baby: native stack allocation failed for %s",
|
||||
capsule_name);
|
||||
}
|
||||
}
|
||||
|
||||
/* item 4.6 fix (FABRIC-1.md, 2026-08-18): granted here, before IDENTITY
|
||||
* exec, not after a confirmed live birth as item 4.1a originally placed
|
||||
* it. item 4.1a's placement assumed no VM's own IDENTITY code would ever
|
||||
|
||||
@@ -265,6 +265,92 @@ int sk_vm_arena_is_initialized(void) {
|
||||
return vm_arena_initialized;
|
||||
}
|
||||
|
||||
/* ===========================================================================
|
||||
* Per-VM native stack (FABRIC-3.md §XXVIII, Stage 1, 2026-09-13)
|
||||
* ===========================================================================
|
||||
*
|
||||
* 2 MiB, matching this project's own established kernel-stack convention
|
||||
* (g_kernel_stack / g_rpi5_native_stack in each arch's entry assembly) --
|
||||
* not a guessed number. That single 2 MiB stack today accommodates the
|
||||
* ENTIRE shared kernel C stack's worth of nested VM-EXEC/execute_colon_word
|
||||
* recursion across every VM combined, so 2 MiB for one VM's own stack alone
|
||||
* is generous headroom, not a guess at the edge of what's needed.
|
||||
*/
|
||||
#define SK_VM_STACK_SIZE (2ULL * 1024ULL * 1024ULL)
|
||||
#define SK_VM_STACK_PAGES ((SK_VM_STACK_SIZE + (2ULL * SK_VM_GUARD_SIZE) + PMM_PAGE_SIZE - 1) / PMM_PAGE_SIZE)
|
||||
|
||||
#if !(defined(ARCH_AARCH64) || defined(__riscv))
|
||||
/* amd64 needs its own virtual-address walk, separate from arena's
|
||||
* next_arena_vaddr -- different logical region, would otherwise collide. */
|
||||
#define SK_VM_STACK_ARENA_VADDR 0xFFFF980000000000ULL
|
||||
static uint64_t next_stack_vaddr = SK_VM_STACK_ARENA_VADDR;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* sk_vm_native_stack_alloc - allocate one VM's own dedicated native stack.
|
||||
*
|
||||
* Always a fresh, independent PMM allocation with real guard pages -- no
|
||||
* singleton, no kmalloc fallback, unlike sk_vm_arena_alloc()'s Mama-only
|
||||
* special case. Returns 0 on success (fields of *out populated), -1 on
|
||||
* failure.
|
||||
*/
|
||||
int sk_vm_native_stack_alloc(sk_vm_native_stack_t *out) {
|
||||
uint64_t paddr, guard_vaddr;
|
||||
|
||||
if (!out) return -1;
|
||||
|
||||
paddr = pmm_alloc_contiguous(SK_VM_STACK_PAGES);
|
||||
if (paddr == 0) {
|
||||
console_println("sk_vm_native_stack_alloc: PMM allocation failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if defined(ARCH_AARCH64) || defined(__riscv)
|
||||
guard_vaddr = paddr;
|
||||
(void)(VMM_FLAG_WRITABLE); (void)(VMM_FLAG_NX);
|
||||
#else
|
||||
guard_vaddr = next_stack_vaddr;
|
||||
next_stack_vaddr += SK_VM_STACK_PAGES * PMM_PAGE_SIZE;
|
||||
uint64_t flags = VMM_FLAG_WRITABLE | VMM_FLAG_NX;
|
||||
|
||||
if (vmm_map_range(guard_vaddr, paddr,
|
||||
SK_VM_STACK_PAGES * PMM_PAGE_SIZE, flags) != 0) {
|
||||
console_println("sk_vm_native_stack_alloc: VMM mapping failed");
|
||||
pmm_free_contiguous(paddr, SK_VM_STACK_PAGES);
|
||||
next_stack_vaddr -= SK_VM_STACK_PAGES * PMM_PAGE_SIZE;
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
guard_fill(guard_vaddr);
|
||||
guard_fill(guard_vaddr + SK_VM_GUARD_SIZE + SK_VM_STACK_SIZE);
|
||||
|
||||
out->paddr = paddr;
|
||||
out->guard_vaddr = guard_vaddr;
|
||||
out->stack_top = guard_vaddr + SK_VM_GUARD_SIZE + SK_VM_STACK_SIZE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sk_vm_native_stack_free - release a VM's native stack.
|
||||
*
|
||||
* Mirrors sk_vm_arena_free()'s unmap/free sequence, but per-call (this is
|
||||
* not a singleton) -- safe to call once per successful
|
||||
* sk_vm_native_stack_alloc(), and the caller (vm_cleanup()) is responsible
|
||||
* for not calling it twice on the same struct.
|
||||
*/
|
||||
void sk_vm_native_stack_free(const sk_vm_native_stack_t *stack) {
|
||||
if (!stack || stack->paddr == 0) return;
|
||||
|
||||
#if !(defined(ARCH_AARCH64) || defined(__riscv))
|
||||
for (uint64_t offset = 0; offset < SK_VM_STACK_PAGES * PMM_PAGE_SIZE; offset += PMM_PAGE_SIZE) {
|
||||
vmm_unmap_page(stack->guard_vaddr + offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
pmm_free_contiguous(stack->paddr, SK_VM_STACK_PAGES);
|
||||
}
|
||||
|
||||
void sk_vm_arena_assert_guards(const char *tag) {
|
||||
if (!vm_arena_initialized) {
|
||||
return;
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
#include "starkernel/capsule_birth.h"
|
||||
#include "starkernel/capsule_run.h"
|
||||
#include "starkernel/capsule_vm_physics.h"
|
||||
#include "starkernel/vm/arena.h" /* FABRIC-3.md §XXVIII Stage 1 -- sk_vm_native_stack_alloc() */
|
||||
#include "vm_internal.h"
|
||||
#include "platform_time.h"
|
||||
#include "test_runner/include/test_runner.h"
|
||||
@@ -266,6 +267,21 @@ int sk_vm_bootstrap_parity(ParityPacket *out) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* FABRIC-3.md §XXVIII, Stage 1 (2026-09-13): Hera gets her own native
|
||||
* stack too, same as every baby/WIREBIND VM (capsule_birth_baby()).
|
||||
* Not fatal on failure, same reasoning as there -- nothing executes on
|
||||
* this yet (Stage 2). */
|
||||
{
|
||||
sk_vm_native_stack_t ns;
|
||||
if (sk_vm_native_stack_alloc(&ns) == 0) {
|
||||
vm->native_stack_paddr = ns.paddr;
|
||||
vm->native_stack_guard_vaddr = ns.guard_vaddr;
|
||||
vm->native_stack_top = ns.stack_top;
|
||||
} else {
|
||||
console_println("VM: Hera native stack allocation failed");
|
||||
}
|
||||
}
|
||||
|
||||
/* Capsule subsystem: hooks, registry (Hera), run log.
|
||||
* Called exactly once here for Mama — child VMs go through
|
||||
* capsule_birth_baby() which never calls vm_init_with_host(). */
|
||||
|
||||
@@ -364,6 +364,23 @@ void vm_cleanup(VM* vm)
|
||||
#ifdef __STARKERNEL__
|
||||
sf_free(vm->call_stack);
|
||||
vm->call_stack = NULL;
|
||||
|
||||
/* FABRIC-3.md §XXVIII, Stage 1 (2026-09-13): free the native stack
|
||||
* allocated at birth (capsule_birth_baby() / sk_vm_bootstrap_parity()).
|
||||
* Nothing executes on it yet (that's Stage 2), so there is no
|
||||
* switched-out-state guard to check here yet -- Stage 2 adds that
|
||||
* guard at the KILL call sites, before this function is ever reached
|
||||
* for a VM that would need it. */
|
||||
if (vm->native_stack_paddr != 0) {
|
||||
sk_vm_native_stack_t ns;
|
||||
ns.paddr = vm->native_stack_paddr;
|
||||
ns.guard_vaddr = vm->native_stack_guard_vaddr;
|
||||
ns.stack_top = vm->native_stack_top;
|
||||
sk_vm_native_stack_free(&ns);
|
||||
vm->native_stack_paddr = 0;
|
||||
vm->native_stack_guard_vaddr = 0;
|
||||
vm->native_stack_top = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user