123 lines
4.0 KiB
Rust
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)
|
|
}
|
|
}
|