Stage MC-0b: real kernel spinlocks; sf_mutex no longer a no-op (FABRIC-3.10.8 §3.2)
- New spinlock primitive (spinlock.h/.c): CAS on an owner word (core+1), interrupts masked while any lock is held (state kept in the per-core block, so release order doesn't matter). Recursion, release-not-held, held-at-switch and destroyed-while-held are counted and logged, never silent. Locks are no-ops until the boot core's block is installed (riscv64's monolithic early boot prints before sk_percpu_boot_init()). - Kernel locks in a documented order: hermes, switch, registry, doe, log, console, heap, vmm, pmm. sf_mutex (dict_lock/tuning_lock) uses the same lock word. - Hermes: public functions over *_locked internals; never held across the drain's vm_interpret_kind(); switch notifications after dropping the lock; event payload moved off static storage. Switch table and registry never call out while held (slot/entry copied under the lock). - No lock crosses a switch: checked at context switch (leave, resume) and the idle point. - Probe (removed): every lock balances on all three ISAs; 0 held at switch over 7.5M-51M checks; 0 recursions, 0 bad releases. - Standard sequence on amd64/aarch64/riscv64: hashes and G byte-identical to MC-0a, MINT/WIREBIND/USE ok, conservation true, 0 idle growth. - Block subsystem split out as MC-0b2 (Captain Bob's ruling). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8409e376cf
commit
5bc7b4dee7
@@ -49,6 +49,14 @@ typedef struct sk_percpu {
|
||||
struct sk_percpu *self; /* must stay first: amd64 reads it as %gs:0 */
|
||||
uint32_t core_id; /* 0 = the boot core (the system core, MC-R1) */
|
||||
struct VM *current_vm; /* who is running on this core (switch.c) */
|
||||
/* MC-0b (spinlock.c): locks this core holds right now, and the
|
||||
* interrupt state to restore when that count returns to 0. Kept here,
|
||||
* not in each lock, so interrupts come back correctly whatever order
|
||||
* the locks are released in. */
|
||||
uint32_t lock_depth;
|
||||
uint64_t saved_irq;
|
||||
uint64_t lock_acquired; /* every acquire on this core, ever */
|
||||
uint64_t lock_released; /* every release on this core, ever */
|
||||
} sk_percpu_t;
|
||||
|
||||
/* Installs the boot core's block in this core's per-CPU register. Call
|
||||
@@ -56,6 +64,14 @@ typedef struct sk_percpu {
|
||||
* arch_early_init(): on amd64 that reloads GS, which would reset the base). */
|
||||
void sk_percpu_boot_init(void);
|
||||
|
||||
/* 1 once the boot core's block is installed (MC-0b). Before that the
|
||||
* per-CPU register still holds whatever the firmware left there -- on
|
||||
* riscv64 the monolithic loader's kernel_main() prints from
|
||||
* arch_early_init() with tp still the firmware's -- so the spinlocks
|
||||
* (spinlock.c) do nothing until it is set: early boot is one core with
|
||||
* interrupts off. */
|
||||
extern volatile int sk_percpu_ready;
|
||||
|
||||
/* This core's block. Valid from sk_percpu_boot_init() on. */
|
||||
static inline sk_percpu_t *sk_percpu(void)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* spinlock.h - Kernel spinlocks (FABRIC-3.10.8 Stage MC-0b).
|
||||
*
|
||||
* One lock word: 0 = free, core_id + 1 = held by that core. Taken by
|
||||
* compare-and-swap; interrupts are masked on the owning core while it
|
||||
* holds any lock (the first lock taken saves the interrupt state in the
|
||||
* per-core block, the last one released restores it -- percpu.h).
|
||||
*
|
||||
* The rule that makes this correct: **no lock is held across a context
|
||||
* switch or into the idle point.** Every switch and every idle point
|
||||
* checks it (sk_lock_check_none_held()) and counts a violation.
|
||||
*
|
||||
* Failures are counted, never silent (sk_lock_report() logs new ones):
|
||||
* - recursion: this core takes a lock it already holds. On one core,
|
||||
* "the owner is this core" can't tell a nested call from a second VM
|
||||
* on the same core, so it is never treated as legitimate. The call is
|
||||
* let through (spinning would hang the core) and counted;
|
||||
* - bad release: releasing a lock this core doesn't hold;
|
||||
* - held at switch: a switch or idle point reached with a lock held.
|
||||
*
|
||||
* sf_mutex_t (the vendored VM's dict_lock/tuning_lock) uses the same lock
|
||||
* word through sk_lockword_acquire()/sk_lockword_release() (shim.c).
|
||||
*
|
||||
* THE KERNEL LOCKS, in acquisition order (a lock may be taken while
|
||||
* holding only locks above it; MC-3's deadlock freedom rests on this):
|
||||
*
|
||||
* g_lock_hermes kernel-Hermes arena, queues, channels, ledger
|
||||
* g_lock_switch switch-signal slot table and stamps
|
||||
* (capsule_vm_switch_signal.c)
|
||||
* g_lock_registry VM registry list and entries (capsule_birth.c)
|
||||
* g_lock_doe DoE region (doe_region.c)
|
||||
* g_lock_log log region (log_region.c)
|
||||
* g_lock_console console output and line state (console.c)
|
||||
* g_lock_heap kmalloc heap
|
||||
* g_lock_vmm page tables (allocates tables from the PMM)
|
||||
* g_lock_pmm physical page bitmap
|
||||
*
|
||||
* The switch table and the registry never call out while held: their walks
|
||||
* copy one entry under the lock and judge it outside. Hermes hands the
|
||||
* switch module its notifications after dropping its own lock (they read
|
||||
* Hermes queues back). Stadium's lock comes with MC-0d; it sits below
|
||||
* Hermes (Hermes admits and evicts cells).
|
||||
*/
|
||||
#ifndef STARKERNEL_SPINLOCK_H
|
||||
#define STARKERNEL_SPINLOCK_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct sk_spinlock {
|
||||
volatile uint32_t owner; /* 0 = free, else core_id + 1 */
|
||||
uint64_t acquired; /* updated while held, so exact */
|
||||
uint64_t released;
|
||||
} sk_spinlock_t;
|
||||
|
||||
#define SK_SPINLOCK_INIT { 0, 0, 0 }
|
||||
|
||||
void sk_lockword_acquire(volatile uint32_t *word);
|
||||
void sk_lockword_release(volatile uint32_t *word);
|
||||
|
||||
void sk_spin_lock(sk_spinlock_t *l);
|
||||
void sk_spin_unlock(sk_spinlock_t *l);
|
||||
|
||||
/* 1 if this core holds `l`. For paths that must not spin on a lock their
|
||||
* own core may hold (the fatal exception printers). */
|
||||
int sk_spin_held_here(const sk_spinlock_t *l);
|
||||
|
||||
/* Every switch and idle point: counts a violation if this core holds any
|
||||
* lock. `where` names the site in the log. */
|
||||
void sk_lock_check_none_held(const char *where);
|
||||
|
||||
/* Logs any failure counted since the last report. Call only with no lock
|
||||
* held (the idle point does). */
|
||||
void sk_lock_report(void);
|
||||
|
||||
uint64_t sk_lock_recursions(void);
|
||||
uint64_t sk_lock_bad_releases(void);
|
||||
uint64_t sk_lock_held_at_switch(void);
|
||||
|
||||
extern sk_spinlock_t g_lock_registry;
|
||||
extern sk_spinlock_t g_lock_switch;
|
||||
extern sk_spinlock_t g_lock_hermes;
|
||||
extern sk_spinlock_t g_lock_doe;
|
||||
extern sk_spinlock_t g_lock_log;
|
||||
extern sk_spinlock_t g_lock_console;
|
||||
extern sk_spinlock_t g_lock_heap;
|
||||
extern sk_spinlock_t g_lock_pmm;
|
||||
extern sk_spinlock_t g_lock_vmm;
|
||||
|
||||
#endif /* STARKERNEL_SPINLOCK_H */
|
||||
Reference in New Issue
Block a user