Files
k-os/crates/kernel/src/smp.rs

123 lines
4.0 KiB
Rust

//! Cores: each thread has a home core and never migrates; waking or removing a thread on
//! another core sends that core an IPI when it must re-check its queue.
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 = usize::from(self.scheduler.num_cores().max(1));
let mut load = [0usize; u8::MAX as usize + 1];
for (t, _) in self.threads.iter().filter(|(t, _)| *t != tid) {
if let Some(n) = load.get_mut(usize::from(self.home(t).raw())) {
*n += 1;
}
}
let best = (0..cores)
.min_by_key(|c| load.get(*c).copied().unwrap_or(usize::MAX))
.and_then(|c| u8::try_from(c).ok())
.map_or(CoreId::from_raw(0), CoreId::from_raw);
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);
}
}
/// The highest priority waiting in `core`'s run queue.
pub fn highest_waiting(&self, core: CoreId) -> Option<Priority> {
self.scheduler.highest_waiting(core)
}
pub fn thread_priority(&self, tid: ThreadId) -> Option<Priority> {
self.threads.get(tid).ok().map(|t| t.priority())
}
/// What `core` does after an IPI: keep its thread or switch to a higher-priority one;
/// with no thread the caller picks.
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)
}
}