Skip to content

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.

[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:

  1. a task acquires the lock,
  2. an interrupt arrives on that same core,
  3. the handler tries to acquire the same lock,
  4. 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.

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.

struct Spinlock<T> { /* private fields */ }

Data shared between cores.

Implements: Send, Sync

fn cores() -> u8

How many cores this build expects to run the kernel.

fn current_core() -> hal::smp::CoreId

The calling core.

References: CoreId

fn is_pinnable(core: u8) -> bool

Whether 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.

fn is_timekeeper() -> bool

Whether this core is the sole writer of shared kernel time.

fn joined_mask() -> u8

The current scheduler membership bitmap.

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