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
+26
@@ -3553,3 +3553,29 @@ No FORTH-visible behavior changed, as intended -- this stage was purely "make th
|
|||||||
complete," nothing yet reads or writes any of it beyond the ISR's own entry/exit. Stage 1 (per-VM
|
complete," nothing yet reads or writes any of it beyond the ISR's own entry/exit. Stage 1 (per-VM
|
||||||
native stacks) is next.
|
native stacks) is next.
|
||||||
|
|
||||||
|
**Stage 1 CLOSED, same day.** Every VM now gets its own dedicated 2 MiB native (C) stack at
|
||||||
|
birth, allocated but not yet executed on. Design correction made before writing any code: the
|
||||||
|
plan's own text said to clone `sk_vm_arena_alloc()`'s guard-page pattern, but investigation
|
||||||
|
showed that pattern is actually Mama-only -- `host_services.c`'s `kernel_alloc()` gives every
|
||||||
|
baby VM a plain `kmalloc()` block for its 5 MB dictionary arena, not a real guarded PMM
|
||||||
|
allocation; only Hera gets the true singleton. Stacks are different: a stack overflow is
|
||||||
|
precisely the failure mode guard pages exist for, and a corrupted stack can corrupt whatever
|
||||||
|
saved context Stage 2 later trusts, so the new `sk_vm_native_stack_alloc()`/`_free()`
|
||||||
|
(`arena.c`) gives **every** VM without exception a real, independent `pmm_alloc_contiguous()`
|
||||||
|
allocation with guard pages -- no singleton, no kmalloc fallback. Size (2 MiB) is not a guess:
|
||||||
|
it matches this project's own established kernel-stack convention (`g_kernel_stack`/
|
||||||
|
`g_rpi5_native_stack` in the arch entry files), and that single 2 MiB stack today already
|
||||||
|
carries the entire shared kernel C stack's worth of nested VM-EXEC/`execute_colon_word`
|
||||||
|
recursion across every VM combined -- generous headroom for one VM alone.
|
||||||
|
|
||||||
|
Wired at both VM-creation sites: `sk_vm_bootstrap_parity()` (Hera) and `capsule_birth_baby()`
|
||||||
|
(every other VM, WIREBIND-birthed identities included -- purely additive there, no attach/detach
|
||||||
|
behavior change). Three new fields on `VM` (`native_stack_paddr`/`native_stack_guard_vaddr`/
|
||||||
|
`native_stack_top`), freed in `vm_cleanup()` alongside the existing `call_stack` free.
|
||||||
|
Allocation failure is deliberately non-fatal to birth (degrades to "not yet switchable," not
|
||||||
|
"stillborn," since nothing executes on these stacks yet regardless).
|
||||||
|
|
||||||
|
All 3 architectures re-verified clean boot to `ok>`, no native-stack allocation failures logged
|
||||||
|
for any Tripod-fleet VM on any arch. Stage 2 (the actual save/restore switch primitive,
|
||||||
|
cooperative only, no timer) is next.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# Capsule Block Manifest — Auto-generated
|
||||||
<!-- Generated by mkcapsule --manifest 2026-09-13T13:21:10Z -->
|
<!-- Generated by mkcapsule --manifest 2026-09-13T18:28:32Z -->
|
||||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||||
<!-- Hand-written justifications and immutability notes live -->
|
<!-- Hand-written justifications and immutability notes live -->
|
||||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||||
|
|||||||
Binary file not shown.
@@ -62,6 +62,35 @@ size_t sk_vm_arena_size(void);
|
|||||||
int sk_vm_arena_is_initialized(void);
|
int sk_vm_arena_is_initialized(void);
|
||||||
void sk_vm_arena_assert_guards(const char *tag);
|
void sk_vm_arena_assert_guards(const char *tag);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sk_vm_native_stack_t - one VM's own dedicated native (C) stack.
|
||||||
|
*
|
||||||
|
* FABRIC-3.md §XXVIII (Stage 1, preemptive-context-switch per-VM native
|
||||||
|
* stacks, 2026-09-13). Unlike sk_vm_arena_alloc() -- whose PMM+guard-page
|
||||||
|
* path is exercised only for Hera; every baby VM's "arena" is actually a
|
||||||
|
* plain kmalloc block, per host_services.c's kernel_alloc() -- every native
|
||||||
|
* stack, for every VM without exception, gets its own real, independent
|
||||||
|
* pmm_alloc_contiguous() allocation with guard pages. A stack overflow is
|
||||||
|
* exactly the failure mode guard pages exist for, and unlike the dictionary
|
||||||
|
* arena, a corrupted stack can also corrupt whatever saved context Stage 2
|
||||||
|
* later trusts -- worth the extra PMM pages every VM, not just Hera.
|
||||||
|
*
|
||||||
|
* Caller owns this struct (stored directly on the VM, mirroring how
|
||||||
|
* vm->memory already holds its own arena pointer directly rather than going
|
||||||
|
* through any module-level registry) and passes it back unchanged to
|
||||||
|
* sk_vm_native_stack_free().
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint64_t paddr; /* physical base (for pmm_free_contiguous) */
|
||||||
|
uint64_t guard_vaddr; /* virtual base of the whole guarded region */
|
||||||
|
uint64_t stack_top; /* initial SP value -- stacks grow down on all
|
||||||
|
* 3 arches, so this is guard_vaddr + one guard
|
||||||
|
* page + the full stack size */
|
||||||
|
} sk_vm_native_stack_t;
|
||||||
|
|
||||||
|
int sk_vm_native_stack_alloc(sk_vm_native_stack_t *out);
|
||||||
|
void sk_vm_native_stack_free(const sk_vm_native_stack_t *stack);
|
||||||
|
|
||||||
#endif /* __STARKERNEL__ */
|
#endif /* __STARKERNEL__ */
|
||||||
|
|
||||||
#endif /* STARKERNEL_VM_ARENA_H */
|
#endif /* STARKERNEL_VM_ARENA_H */
|
||||||
|
|||||||
@@ -645,6 +645,16 @@ typedef struct VM
|
|||||||
int call_stack_max; /**< High-water mark depth (DoE metric) */
|
int call_stack_max; /**< High-water mark depth (DoE metric) */
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
|
/** @name Native execution stack (FABRIC-3.md §XXVIII, Stage 1, 2026-09-13)
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
uint64_t native_stack_paddr; /**< Physical base (for teardown); 0 = not allocated */
|
||||||
|
uint64_t native_stack_guard_vaddr; /**< Virtual base of the whole guarded region */
|
||||||
|
uint64_t native_stack_top; /**< Initial SP value once something switches onto
|
||||||
|
* this stack -- Stage 1 only allocates it; nothing
|
||||||
|
* yet runs here (that's Stage 2). */
|
||||||
|
/** @} */
|
||||||
|
|
||||||
/** @name Stadium Identity (item 4.2, FABRIC-0.md §25.5)
|
/** @name Stadium Identity (item 4.2, FABRIC-0.md §25.5)
|
||||||
* @{
|
* @{
|
||||||
*/
|
*/
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@
|
|||||||
#include "starkernel/kmalloc.h"
|
#include "starkernel/kmalloc.h"
|
||||||
#include "starkernel/console.h"
|
#include "starkernel/console.h"
|
||||||
#include "starkernel/vm/stadium.h" /* item 4.1a -- stadium_grant_quota() */
|
#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/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 "starkernel/timer.h" /* timer_calibration_record()->vm_mode -- FABRIC-2.md §I.5 CONTRIB trust tier */
|
||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
@@ -627,6 +628,26 @@ CapsuleRunResult capsule_birth_baby(
|
|||||||
* own reservoir, not vm_uuid_hera()'s (item 4.1's hardcoded default). */
|
* own reservoir, not vm_uuid_hera()'s (item 4.1's hardcoded default). */
|
||||||
((VM *)new_vm)->stadium_vm_id = vm_id;
|
((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
|
/* 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
|
* 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
|
* 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;
|
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) {
|
void sk_vm_arena_assert_guards(const char *tag) {
|
||||||
if (!vm_arena_initialized) {
|
if (!vm_arena_initialized) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
#include "starkernel/capsule_birth.h"
|
#include "starkernel/capsule_birth.h"
|
||||||
#include "starkernel/capsule_run.h"
|
#include "starkernel/capsule_run.h"
|
||||||
#include "starkernel/capsule_vm_physics.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 "vm_internal.h"
|
||||||
#include "platform_time.h"
|
#include "platform_time.h"
|
||||||
#include "test_runner/include/test_runner.h"
|
#include "test_runner/include/test_runner.h"
|
||||||
@@ -266,6 +267,21 @@ int sk_vm_bootstrap_parity(ParityPacket *out) {
|
|||||||
return -1;
|
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.
|
/* Capsule subsystem: hooks, registry (Hera), run log.
|
||||||
* Called exactly once here for Mama — child VMs go through
|
* Called exactly once here for Mama — child VMs go through
|
||||||
* capsule_birth_baby() which never calls vm_init_with_host(). */
|
* capsule_birth_baby() which never calls vm_init_with_host(). */
|
||||||
|
|||||||
@@ -364,6 +364,23 @@ void vm_cleanup(VM* vm)
|
|||||||
#ifdef __STARKERNEL__
|
#ifdef __STARKERNEL__
|
||||||
sf_free(vm->call_stack);
|
sf_free(vm->call_stack);
|
||||||
vm->call_stack = NULL;
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user