feat: SMP stage 1 (4 harts, per-hart guarded stacks, home-core affinity, IPIs, TLB shootdown, one temporary kernel lock), 4-core storm
This commit is contained in:
119
crates/kernel/src/smp.rs
Normal file
119
crates/kernel/src/smp.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
//! Cores. Every thread has a home core, chosen when it is created, and
|
||||
//! only ever runs there: it waits in that core's run queue and is never
|
||||
//! migrated. Making a thread ready from another core sends that core an
|
||||
//! IPI when it has to look at its queue again (it is idle, or the thread
|
||||
//! outranks what it is running), and so does taking away the thread a
|
||||
//! core is running.
|
||||
|
||||
use domain::{
|
||||
scheduler::{Scheduler, SchedulerDecision},
|
||||
thread::ThreadState,
|
||||
};
|
||||
use platform::Platform;
|
||||
use types::{
|
||||
bounded::Priority,
|
||||
id::{CoreId, ThreadId},
|
||||
};
|
||||
|
||||
use crate::Kernel;
|
||||
|
||||
impl<P: Platform> Kernel<P> {
|
||||
/// The core `tid` runs on.
|
||||
pub fn home(&self, tid: ThreadId) -> CoreId {
|
||||
self.homes
|
||||
.get(tid.raw() as usize)
|
||||
.copied()
|
||||
.unwrap_or(CoreId::from_raw(0))
|
||||
}
|
||||
|
||||
/// Homes a new thread on `core`.
|
||||
pub(crate) fn set_home(&mut self, tid: ThreadId, core: CoreId) {
|
||||
if let Some(h) = self.homes.get_mut(tid.raw() as usize) {
|
||||
*h = core;
|
||||
}
|
||||
}
|
||||
|
||||
/// Homes a new thread on the core with the fewest live threads.
|
||||
pub(crate) fn place(&mut self, tid: ThreadId) {
|
||||
let cores = self.scheduler.num_cores().max(1);
|
||||
let load = |core: CoreId| {
|
||||
self.threads
|
||||
.iter()
|
||||
.filter(|(t, _)| *t != tid && self.home(*t) == core)
|
||||
.count()
|
||||
};
|
||||
let best = (0..cores)
|
||||
.map(CoreId::from_raw)
|
||||
.min_by_key(|c| load(*c))
|
||||
.unwrap_or(CoreId::from_raw(0));
|
||||
self.set_home(tid, best);
|
||||
}
|
||||
|
||||
/// Queues a ready thread on its home core, and kicks that core if it
|
||||
/// is another one that should look at its queue now.
|
||||
pub(crate) fn make_ready(&mut self, tid: ThreadId, priority: Priority) {
|
||||
let home = self.home(tid);
|
||||
self.scheduler.add(tid, priority, home);
|
||||
if home == self.platform.core_id() {
|
||||
return;
|
||||
}
|
||||
let outranks = match self.scheduler.current(home) {
|
||||
None => true,
|
||||
Some(running) => self
|
||||
.threads
|
||||
.get(running)
|
||||
.is_ok_and(|t| priority > t.priority()),
|
||||
};
|
||||
if outranks {
|
||||
self.platform.send_ipi(home);
|
||||
}
|
||||
}
|
||||
|
||||
/// Before `tid` is destroyed: if another core is running it, that core
|
||||
/// must stop and pick something else.
|
||||
pub(crate) fn evict(&mut self, tid: ThreadId) {
|
||||
if let Ok(ThreadState::Running(core)) = self.threads.get(tid).map(|t| *t.state())
|
||||
&& core != self.platform.core_id()
|
||||
{
|
||||
self.platform.send_ipi(core);
|
||||
}
|
||||
}
|
||||
|
||||
/// What `core` should do after an IPI: keep running its thread, or
|
||||
/// switch to a higher-priority one now waiting. When it has no thread
|
||||
/// (it was idle, or its thread was destroyed) the caller picks the
|
||||
/// next one itself.
|
||||
pub fn reschedule(&mut self, core: CoreId) -> SchedulerDecision {
|
||||
let Some(current) = self.scheduler.current(core) else {
|
||||
return SchedulerDecision::Continue;
|
||||
};
|
||||
let Ok(thread) = self.threads.get(current) else {
|
||||
return SchedulerDecision::Continue;
|
||||
};
|
||||
let priority = thread.priority();
|
||||
if self
|
||||
.scheduler
|
||||
.highest_waiting(core)
|
||||
.is_none_or(|p| p <= priority)
|
||||
{
|
||||
return SchedulerDecision::Continue;
|
||||
}
|
||||
let Some(next) = self.scheduler.next(core) else {
|
||||
return SchedulerDecision::Continue;
|
||||
};
|
||||
if self
|
||||
.threads
|
||||
.get_mut(next)
|
||||
.and_then(|t| t.set_running(core))
|
||||
.is_err()
|
||||
{
|
||||
return SchedulerDecision::Continue;
|
||||
}
|
||||
if let Ok(t) = self.threads.get_mut(current)
|
||||
&& t.preempt().is_ok()
|
||||
{
|
||||
self.scheduler.add(current, priority, core);
|
||||
}
|
||||
SchedulerDecision::Preempt(next)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user