diff --git a/src/context/context.rs b/src/context/context.rs
index 0be56c7508..9e663274d9 100644
--- a/src/context/context.rs
+++ b/src/context/context.rs
@@ -13,6 +13,7 @@ use crate::{
context::{
self, arch,
file::{FileDescriptor, LockedFileDescription},
+ run_contexts_mut,
},
cpu_set::{LogicalCpuId, LogicalCpuSet},
cpu_stats,
@@ -137,6 +138,10 @@ pub struct Context {
pub userspace: bool,
pub being_sigkilled: bool,
pub fmap_ret: Option,
+ /// Priority
+ pub prio: usize,
+ /// Enqueued
+ pub enqueued: bool,
// TODO: id can reappear after wraparound?
pub owner_proc_id: Option,
@@ -194,6 +199,8 @@ impl Context {
files: Arc::new(RwLock::new(FdTbl::new())),
userspace: false,
fmap_ret: None,
+ prio: 20,
+ enqueued: false,
being_sigkilled: false,
owner_proc_id,
diff --git a/src/context/mod.rs b/src/context/mod.rs
index 62a0a575b5..dd389bdf4b 100644
--- a/src/context/mod.rs
+++ b/src/context/mod.rs
@@ -2,7 +2,11 @@
//!
//! For resources on contexts, please consult [wikipedia](https://en.wikipedia.org/wiki/Context_switch) and [osdev](https://wiki.osdev.org/Context_Switching)
-use alloc::{collections::BTreeSet, sync::Arc};
+use alloc::{
+ collections::{BTreeSet, VecDeque},
+ string::String,
+ sync::Arc,
+};
use core::num::NonZeroUsize;
use crate::{
@@ -71,6 +75,22 @@ pub use self::arch::empty_cr3;
// the context file descriptors.
static CONTEXTS: RwLock> = RwLock::new(BTreeSet::new());
+// Actual context store for the scheduler
+static RUN_CONTEXTS: RwLock = RwLock::new(RunContextData::new());
+
+pub struct RunContextData {
+ set: [VecDeque; 40],
+}
+
+impl RunContextData {
+ pub const fn new() -> Self {
+ const EMPTY_VEC: VecDeque = VecDeque::new();
+ Self {
+ set: [EMPTY_VEC; 40],
+ }
+ }
+}
+
/// Get the global schemes list, const
pub fn contexts(token: LockToken<'_, L0>) -> RwLockReadGuard<'_, L1, BTreeSet> {
CONTEXTS.read(token)
@@ -81,6 +101,14 @@ pub fn contexts_mut(token: LockToken<'_, L0>) -> RwLockWriteGuard<'_, L1, BTreeS
CONTEXTS.write(token)
}
+pub fn run_contexts(token: LockToken<'_, L0>) -> RwLockReadGuard<'_, L1, RunContextData> {
+ RUN_CONTEXTS.read(token)
+}
+
+pub fn run_contexts_mut(token: LockToken<'_, L0>) -> RwLockWriteGuard<'_, L1, RunContextData> {
+ RUN_CONTEXTS.write(token)
+}
+
pub fn init(token: &mut CleanLockToken) {
let owner = None; // kmain not owned by any fd
let mut context = Context::new(owner).expect("failed to create kmain context");
@@ -95,6 +123,7 @@ pub fn init(token: &mut CleanLockToken) {
context.status = Status::Runnable;
context.running = true;
context.cpu_id = Some(crate::cpu_id());
+ context.enqueued = false;
let context_lock = Arc::new(ContextLock::new(context));
@@ -110,6 +139,52 @@ pub fn init(token: &mut CleanLockToken) {
}
}
+pub fn wakeup_context(context_lock: &Arc>) {
+ let mut global_token = unsafe { CleanLockToken::new() };
+ let mut local_token = unsafe { CleanLockToken::new() };
+
+ let mut run_queues = run_contexts_mut(global_token.token());
+ let mut context = context_lock.write(local_token.token());
+
+ context.wake = None;
+
+ /*
+ if context.status.is_soft_blocked() {
+ context.status = Status::Runnable;
+ context.status_reason = "";
+
+ if !context.enqueued {
+ let prio = context.prio;
+ run_queues.set[prio].push_back(ContextRef(Arc::clone(context_lock)));
+ context.enqueued = true;
+ }
+ }
+ */
+
+ context.unblock();
+
+ if context.status.is_runnable() && !context.running && !context.enqueued {
+ let prio = context.prio;
+ run_queues.set[prio].push_back(ContextRef(Arc::clone(context_lock)));
+ context.enqueued = true;
+ }
+}
+
+pub fn set_priority(
+ context_lock: &Arc>,
+ new_prio: usize,
+ local_token: &mut CleanLockToken,
+) -> Result<(), String> {
+ if new_prio >= 40 {
+ return Err("Priority out of bounds".into());
+ }
+
+ let mut guard = context_lock.write(local_token.token());
+ guard.prio = new_prio;
+
+ Ok(())
+}
+
pub fn current() -> Arc {
PercpuBlock::current()
.switch_internals
@@ -174,6 +249,10 @@ pub fn spawn(
contexts_mut(token.token()).insert(context_ref);
+ let run_ref = ContextRef(Arc::clone(&context_lock));
+ run_contexts_mut(token.token()).set[20].push_back(run_ref);
+ context_lock.write(token.token()).enqueued = true;
+
Ok(context_lock)
}
diff --git a/src/context/switch.rs b/src/context/switch.rs
index c3815bf200..6a614520c8 100644
--- a/src/context/switch.rs
+++ b/src/context/switch.rs
@@ -8,13 +8,16 @@ use core::{
sync::atomic::Ordering,
};
-use alloc::sync::Arc;
+use alloc::{sync::Arc, vec::Vec};
use syscall::PtraceFlags;
use crate::{
- context::{arch, contexts, ArcContextLockWriteGuard, Context, ContextLock},
+ context::{
+ self, arch, contexts, run_contexts_mut, ArcContextLockWriteGuard, Context, ContextLock,
+ Status,
+ },
cpu_set::LogicalCpuId,
- cpu_stats,
+ cpu_stats, log,
percpu::PercpuBlock,
sync::CleanLockToken,
};
@@ -26,6 +29,13 @@ enum UpdateResult {
Skip,
}
+// A simple geometric series where value[i] ~= value[i - 1] * 1.25
+const sched_prio_to_weight: [usize; 40] = [
+ 88761, 71755, 56483, 46273, 36291, 29154, 23254, 18705, 14949, 11916, 9548, 7620, 6100, 4904,
+ 3906, 3121, 2501, 1991, 1586, 1277, 1024, 820, 655, 526, 423, 335, 272, 215, 172, 137, 110, 87,
+ 70, 56, 45, 36, 29, 23, 18, 15,
+];
+
/// Determines if a given context is eligible to be scheduled on a given CPU (in
/// principle, the current CPU).
///
@@ -54,15 +64,6 @@ unsafe fn update_runnable(
return UpdateResult::Skip;
}
- // If context is soft-blocked and has a wake-up time, check if it should wake up.
- if context.status.is_soft_blocked()
- && let Some(wake) = context.wake
- && switch_time >= wake
- {
- context.wake = None;
- context.unblock_no_ipi();
- }
-
// If the context is runnable, indicate it can be switched to.
if context.status.is_runnable() {
UpdateResult::CanSwitch
@@ -124,11 +125,8 @@ pub enum SwitchResult {
AllContextsIdle,
}
-/// Selects and switches to the next context using a round-robin scheduler.
-///
-/// This function performs the context switch, checking each context in a loop for eligibility
-/// until it finds a context ready to run. If no other context is runnable, it returns to the
-/// idle context.
+/// This function performs the context switch, using select_next_context to
+/// actually select the next context to switch to.
///
/// # Warning
/// This is not memory-unsafe to call. But do NOT call this while holding locks!
@@ -157,73 +155,42 @@ pub fn switch(token: &mut CleanLockToken) -> SwitchResult {
percpu.maybe_handle_tlb_shootdown();
}
- let cpu_id = crate::cpu_id();
-
- let mut switch_context_opt = None;
+ // Alarm (previously in update_runnable)
+ // TODO: Optimise this somehow
+ let mut wakeups = Vec::new();
{
- let contexts = contexts(token.token());
-
- // Lock the previous context.
- let prev_context_lock = crate::context::current();
- // We are careful not to lock this context twice
- let prev_context_guard = unsafe { prev_context_lock.write_arc() };
-
- if !prev_context_guard.is_preemptable() {
- // Unset global lock
- arch::CONTEXT_SWITCH_LOCK.store(false, Ordering::SeqCst);
-
- // Pretend to have finished switching, so CPU is not idled
- return SwitchResult::Switched;
- }
-
- let idle_context = percpu.switch_internals.idle_context();
-
- // Stateful flag used to skip the idle process the first time it shows up.
- // After that, this flag is set to `false` so the idle process can be
- // picked up.
- let mut skip_idle = true;
-
- // Attempt to locate the next context to switch to.
- for next_context_lock in contexts
- // Include all contexts with IDs greater than the current...
- .range((
- Bound::Excluded(ContextRef(Arc::clone(&prev_context_lock))),
- Bound::Unbounded,
- ))
- // ... and all contexts with IDs less than the current...
- .chain(contexts.range((
- Bound::Unbounded,
- Bound::Excluded(ContextRef(Arc::clone(&prev_context_lock))),
- )))
- .filter_map(ContextRef::upgrade)
- // ... and the idle context...
- .chain(Some(Arc::clone(&idle_context)))
- // ... but not the current context (note the `Bound::Excluded`),
- // which is already locked.
- {
- if Arc::ptr_eq(&next_context_lock, &idle_context) && skip_idle {
- // Skip idle process the first time it shows up, but allow it
- // to be picked up again the next time.
- skip_idle = false;
+ let current_context = context::current();
+ let contexts_guard = contexts(token.token());
+ for context_ref in contexts_guard.iter().filter_map(|r| r.upgrade()) {
+ if Arc::ptr_eq(&context_ref, ¤t_context) {
continue;
}
-
- {
- // Lock next context
- // We are careful not to lock this context twice
- let mut next_context_guard = unsafe { next_context_lock.write_arc() };
-
- // Check if the context is runnable and can be switched to.
- if let UpdateResult::CanSwitch =
- unsafe { update_runnable(&mut next_context_guard, cpu_id, switch_time) }
- {
- // Store locks for previous and next context and break out from loop
- // for the switch
- switch_context_opt = Some((prev_context_guard, next_context_guard));
- break;
+ let mut local_token = unsafe { CleanLockToken::new() };
+ let guard = context_ref.read(local_token.token());
+ if guard.status.is_soft_blocked() {
+ if let Some(wake) = guard.wake {
+ if switch_time >= wake {
+ wakeups.push(Arc::clone(&context_ref));
+ continue;
+ }
}
}
+
+ if guard.status.is_runnable() && !guard.enqueued && !guard.running {
+ wakeups.push(Arc::clone(&context_ref));
+ }
}
+ }
+
+ for context_lock in wakeups {
+ context::wakeup_context(&context_lock);
+ }
+
+ let cpu_id = crate::cpu_id();
+
+ let switch_context_opt = match select_next_context(token, percpu, cpu_id, switch_time) {
+ Ok(opt) => opt,
+ Err(early_ret) => return early_ret,
};
// Update per-cpu times
@@ -297,8 +264,8 @@ pub fn switch(token: &mut CleanLockToken) -> SwitchResult {
prev_context.syscall_debug_info = percpu
.syscall_debug_info
.replace(next_context.syscall_debug_info);
- prev_context.syscall_debug_info.on_switch_from(token);
- next_context.syscall_debug_info.on_switch_to(token);
+ prev_context.syscall_debug_info.on_switch_from();
+ next_context.syscall_debug_info.on_switch_to();
}
percpu
@@ -328,6 +295,133 @@ pub fn switch(token: &mut CleanLockToken) -> SwitchResult {
}
}
+/// This is the scheduler function which currently utilises Deficit Weighted Round Robin Scheduler
+fn select_next_context(
+ token: &mut CleanLockToken,
+ percpu: &PercpuBlock,
+ cpu_id: LogicalCpuId,
+ switch_time: u128,
+) -> Result