kernel::smp
Mutual exclusion between cores.
A [Spinlock] guards data shared by more than one core. It is not the
kernel’s task-level [crate::mutex]: that blocks the caller and involves
the scheduler, which is precisely what cannot be relied on here — the
scheduler is one of the things this protects.
Masking comes first, always
Section titled “Masking comes first, always”[Spinlock::with] masks interrupts on the calling core before it tries
to acquire, and unmasks after releasing. That order is the whole
correctness argument, and reversing it deadlocks a single core against
itself:
- a task acquires the lock,
- an interrupt arrives on that same core,
- the handler tries to acquire the same lock,
- it spins, waiting for a task that cannot run until the handler returns.
Masking first makes step 2 impossible. It also means a lock is never held across a context switch on the holding core, which is why holding one must stay short.
The other core spinning is fine and expected — it is a real wait for a real holder, and it ends.
What this is not
Section titled “What this is not”Not fair and not reentrant. Reentrancy is a bug rather than a feature, and
[Spinlock::with] detects it rather than deadlocking silently.
It is released if the closure unwinds. On the target that hardly matters —
a kernel that panics halts — but on a host the tests panic routinely, and a
lock leaked by one failing assertion turns every later test into a hang.
Which is exactly what happened before the release moved into a Drop
guard: one broken assert took the whole suite down with it, and a hang says
far less than a failure does.
Structs
Section titled “Structs”Spinlock
Section titled “Spinlock”struct Spinlock<T> { /* private fields */ }Data shared between cores.
Implements: Send, Sync
Functions
Section titled “Functions”fn cores() -> u8How many cores this build expects to run the kernel.
current_core
Section titled “current_core”fn current_core() -> hal::smp::CoreIdThe calling core.
References: CoreId
is_pinnable
Section titled “is_pinnable”fn is_pinnable(core: u8) -> boolWhether a task may be pinned to core.
This exists so spawn_on can refuse a core that would never run the
task. A pinned task that is silently never scheduled is the worst outcome
available: it looks like a spawn that worked.
Three ways to say no, and they are different failures worth separating from
the allocation failures that follow: the core is beyond MAX_CORES, the
part does not have it, or it has it but nothing there runs the scheduler.
is_timekeeper
Section titled “is_timekeeper”fn is_timekeeper() -> boolWhether this core is the sole writer of shared kernel time.
joined_mask
Section titled “joined_mask”fn joined_mask() -> u8The current scheduler membership bitmap.
mark_joined
Section titled “mark_joined”fn mark_joined(core: hal::smp::CoreId)Record that core has a valid scheduler context and can accept work.
Call this after installing that core’s idle/current state and before unmasking its scheduling interrupts.
References: CoreId