VM.running_on claimed by compare-and-swap on switch-in and released once the context is saved (release_after, sk_vm_switch_finish); a lost claim returns 1 and the caller re-decides; double claims counted. Per-core entry_vm and decision_due. g_lock_physics over capsule_vm_physics.c (rank between switch and registry). KILL refuses a VM running anywhere. Probe: 15737 claims, 0 lost, 0 double; a core-1 claim of Hera refused. 3-ISA acceptance with a 28-VM churn clean, 0 order violations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
159 lines
6.6 KiB
C
159 lines
6.6 KiB
C
/*
|
||
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;
|
||
* - order violation: see the rank check below.
|
||
*
|
||
* 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_physics fleet physics: node list, heat, touch window, slope
|
||
* (capsule_vm_physics.c, MC-3a); retirement walks
|
||
* the registry under it
|
||
* g_lock_registry VM registry list and entries (capsule_birth.c)
|
||
* g_lock_stadium Stadium's global part: quota-slot allocation
|
||
* (grants) and the shared block table (MC-0d)
|
||
* (slot lock) one Stadium quota slot: its lists, reservoir,
|
||
* consumed, word row and cells (stadium.c's
|
||
* StadiumVMQuota.lock; at most one held at a time)
|
||
* g_lock_doe DoE region (doe_region.c)
|
||
* g_lock_log log region (log_region.c)
|
||
* g_lock_block block subsystem: device chain, caches, BAM,
|
||
* relocation table, and its device I/O
|
||
* (block_subsystem.c, MC-0b2)
|
||
* 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 sits below Hermes (Hermes admits, evicts and
|
||
* decays cells) and below a VM's dict_lock (vm_core.c's heat bump dispatches
|
||
* the word under it), and above block (MIGRATE calls blk_flush()); nothing
|
||
* in the block path calls into Stadium.
|
||
*
|
||
* The order is checked at run time (FABRIC-3.10.8 K-11): every lock has a
|
||
* rank (sk_lock_rank_t, same order as above, with a VM's dict_lock and
|
||
* tuning_lock between the registry and Stadium), each core counts the
|
||
* locks it holds per rank, and taking a lock whose rank is not above every
|
||
* rank this core already holds counts an order violation. Two locks of one
|
||
* rank (two Stadium slots, two VMs' dict_locks) count as one too. Counted
|
||
* and logged like the other failures; never refused.
|
||
*/
|
||
#ifndef STARKERNEL_SPINLOCK_H
|
||
#define STARKERNEL_SPINLOCK_H
|
||
|
||
#include <stdint.h>
|
||
|
||
typedef enum sk_lock_rank {
|
||
SK_LOCK_RANK_NONE = 0, /* unranked: neither checked nor counted */
|
||
SK_LOCK_RANK_HERMES,
|
||
SK_LOCK_RANK_SWITCH,
|
||
SK_LOCK_RANK_PHYSICS, /* fleet physics (MC-3a): walks the registry */
|
||
SK_LOCK_RANK_REGISTRY,
|
||
SK_LOCK_RANK_VM_DICT, /* a VM's dict_lock (sf_mutex_t) */
|
||
SK_LOCK_RANK_VM_TUNING, /* a VM's tuning_lock (sf_mutex_t) */
|
||
SK_LOCK_RANK_STADIUM,
|
||
SK_LOCK_RANK_STADIUM_SLOT,
|
||
SK_LOCK_RANK_DOE,
|
||
SK_LOCK_RANK_LOG,
|
||
SK_LOCK_RANK_BLOCK,
|
||
SK_LOCK_RANK_CONSOLE,
|
||
SK_LOCK_RANK_HEAP,
|
||
SK_LOCK_RANK_VMM,
|
||
SK_LOCK_RANK_PMM,
|
||
SK_LOCK_RANK_COUNT
|
||
} sk_lock_rank_t;
|
||
|
||
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;
|
||
uint32_t rank; /* sk_lock_rank_t */
|
||
} sk_spinlock_t;
|
||
|
||
#define SK_SPINLOCK_INIT(rank) { 0, 0, 0, (rank) }
|
||
|
||
void sk_lockword_acquire(volatile uint32_t *word, uint32_t rank);
|
||
void sk_lockword_release(volatile uint32_t *word, uint32_t rank);
|
||
|
||
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);
|
||
uint64_t sk_lock_order_violations(void);
|
||
|
||
extern sk_spinlock_t g_lock_registry;
|
||
extern sk_spinlock_t g_lock_stadium;
|
||
extern sk_spinlock_t g_lock_switch;
|
||
extern sk_spinlock_t g_lock_physics;
|
||
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_block;
|
||
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 */
|