Deficit Weighted Round Robin Scheduler
This commit is contained in:
committed by
Jeremy Soller
parent
4531e15ba1
commit
976756991a
Generated
+7
-7
@@ -40,9 +40,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
@@ -108,7 +108,7 @@ version = "0.5.12"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitfield",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cc",
|
||||
"fdt",
|
||||
"hashbrown 0.14.5",
|
||||
@@ -201,18 +201,18 @@ checksum = "64072665120942deff5fd5425d6c1811b854f4939e7f1c01ce755f64432bbea7"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
|
||||
checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ fdt = { git = "https://github.com/repnop/fdt.git", rev = "2fb1409edd1877c714a0aa
|
||||
hashbrown = { version = "0.14.3", default-features = false, features = ["ahash", "inline-more"] }
|
||||
linked_list_allocator = "0.9.0"
|
||||
redox-path = "0.2.0"
|
||||
redox_syscall = { version = "=0.7.3", default-features = false }
|
||||
redox_syscall = { version = "0.7.4", default-features = false }
|
||||
rmm = { path = "rmm", default-features = false }
|
||||
slab = { version = "0.4", default-features = false }
|
||||
smallvec = { version = "1.15.1", default-features = false }
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::{
|
||||
context::{
|
||||
self, arch,
|
||||
file::{FileDescriptor, LockedFileDescription},
|
||||
run_contexts_mut,
|
||||
},
|
||||
cpu_set::{LogicalCpuId, LogicalCpuSet},
|
||||
cpu_stats,
|
||||
@@ -138,6 +139,10 @@ pub struct Context {
|
||||
pub userspace: bool,
|
||||
pub being_sigkilled: bool,
|
||||
pub fmap_ret: Option<Frame>,
|
||||
/// Priority
|
||||
pub prio: usize,
|
||||
/// Enqueued
|
||||
pub enqueued: bool,
|
||||
|
||||
// TODO: id can reappear after wraparound?
|
||||
pub owner_proc_id: Option<NonZeroUsize>,
|
||||
@@ -195,6 +200,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,
|
||||
|
||||
|
||||
+58
-4
@@ -2,7 +2,10 @@
|
||||
//!
|
||||
//! 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},
|
||||
sync::Arc,
|
||||
};
|
||||
use core::num::NonZeroUsize;
|
||||
|
||||
use crate::{
|
||||
@@ -70,7 +73,6 @@ pub use self::arch::empty_cr3;
|
||||
// Set of weak references to all contexts available for scheduling. The only strong references are
|
||||
// the context file descriptors.
|
||||
static CONTEXTS: Mutex<L2, BTreeSet<ContextRef>> = Mutex::new(BTreeSet::new());
|
||||
|
||||
/// Try to get the global free contexts
|
||||
pub fn free_contexts_try(
|
||||
token: LockToken<'_, L1>,
|
||||
@@ -83,7 +85,23 @@ pub fn free_contexts(token: LockToken<'_, L1>) -> MutexGuard<'_, L2, BTreeSet<Co
|
||||
CONTEXTS.lock(token)
|
||||
}
|
||||
|
||||
/// Get per cpu contexts, const
|
||||
// Actual context store for the scheduler
|
||||
static RUN_CONTEXTS: RwLock<L1, RunContextData> = RwLock::new(RunContextData::new());
|
||||
|
||||
pub struct RunContextData {
|
||||
set: [VecDeque<ContextRef>; 40],
|
||||
}
|
||||
|
||||
impl RunContextData {
|
||||
pub const fn new() -> Self {
|
||||
const EMPTY_VEC: VecDeque<ContextRef> = VecDeque::new();
|
||||
Self {
|
||||
set: [EMPTY_VEC; 40],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the global schemes list, const
|
||||
pub fn contexts(token: LockToken<'_, L0>) -> RwLockReadGuard<'_, L1, BTreeSet<ContextRef>> {
|
||||
let percpu = PercpuBlock::current();
|
||||
percpu.contexts.read(token)
|
||||
@@ -95,6 +113,14 @@ pub fn contexts_mut(token: LockToken<'_, L0>) -> RwLockWriteGuard<'_, L1, BTreeS
|
||||
percpu.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");
|
||||
@@ -109,6 +135,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));
|
||||
|
||||
@@ -123,6 +150,30 @@ pub fn init(token: &mut CleanLockToken) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wakeup_context(context_lock: &Arc<RwLock<L4, Context>>, mut token: LockToken<L0>) {
|
||||
let mut prio = None;
|
||||
{
|
||||
let mut context = context_lock.write(token.token());
|
||||
|
||||
context.wake = None;
|
||||
context.unblock();
|
||||
|
||||
if !(context.status.is_runnable() && !context.running && !context.enqueued) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.enqueued = true;
|
||||
|
||||
prio = Some(context.prio);
|
||||
}
|
||||
|
||||
let mut run_queues = run_contexts_mut(token);
|
||||
|
||||
if let Some(priority) = prio {
|
||||
run_queues.set[priority].push_back(ContextRef(Arc::clone(context_lock)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current() -> Arc<ContextLock> {
|
||||
PercpuBlock::current()
|
||||
.switch_internals
|
||||
@@ -185,7 +236,10 @@ pub fn spawn(
|
||||
let context_lock = Arc::new(ContextLock::new(context));
|
||||
let context_ref = ContextRef(Arc::clone(&context_lock));
|
||||
|
||||
free_contexts(token.token().downgrade()).insert(context_ref);
|
||||
let run_ref = ContextRef(Arc::clone(&context_lock));
|
||||
run_contexts_mut(token.token()).set[20].push_back(run_ref);
|
||||
contexts_mut(token.token()).insert(context_ref);
|
||||
context_lock.write(token.token()).enqueued = true;
|
||||
|
||||
Ok(context_lock)
|
||||
}
|
||||
|
||||
+178
-49
@@ -1,26 +1,26 @@
|
||||
//! This module provides a context-switching mechanism that utilizes a simple round-robin scheduler.
|
||||
//! The scheduler iterates over available contexts, selecting the next context to run, while
|
||||
//! handling process states and synchronization.
|
||||
use core::{
|
||||
cell::{Cell, RefCell},
|
||||
hint, mem,
|
||||
ops::Bound,
|
||||
sync::atomic::Ordering,
|
||||
};
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use syscall::PtraceFlags;
|
||||
|
||||
use crate::sync::ArcRwLockWriteGuard;
|
||||
use crate::sync::L4;
|
||||
use crate::{
|
||||
context::{
|
||||
arch, contexts, contexts_mut, free_contexts_try, ArcContextLockWriteGuard, Context,
|
||||
ContextLock,
|
||||
self, arch, contexts, contexts_mut, free_contexts_try, run_contexts_mut,
|
||||
ArcContextLockWriteGuard, Context, ContextLock,
|
||||
},
|
||||
cpu_set::LogicalCpuId,
|
||||
cpu_stats,
|
||||
percpu::PercpuBlock,
|
||||
sync::CleanLockToken,
|
||||
};
|
||||
use alloc::{sync::Arc, vec::Vec};
|
||||
use core::{
|
||||
cell::{Cell, RefCell},
|
||||
hint, mem,
|
||||
ops::Bound,
|
||||
sync::atomic::Ordering,
|
||||
};
|
||||
use syscall::PtraceFlags;
|
||||
|
||||
use super::ContextRef;
|
||||
|
||||
@@ -29,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).
|
||||
///
|
||||
@@ -127,11 +134,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!
|
||||
@@ -175,43 +179,44 @@ pub fn switch(token: &mut CleanLockToken) -> SwitchResult {
|
||||
return SwitchResult::Switched;
|
||||
}
|
||||
|
||||
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());
|
||||
let current_context = context::current();
|
||||
|
||||
// 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)
|
||||
// ... but not the current context (note the `Bound::Excluded`),
|
||||
// which is already locked.
|
||||
{
|
||||
{
|
||||
// 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(next_context_guard);
|
||||
break;
|
||||
let mut contexts_guard = contexts(token.token());
|
||||
let (context, mut token) = contexts_guard.token_split();
|
||||
for context_ref in context.iter().filter_map(|r| r.upgrade()) {
|
||||
if Arc::ptr_eq(&context_ref, ¤t_context) {
|
||||
continue;
|
||||
}
|
||||
let guard = context_ref.read(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, token.token());
|
||||
}
|
||||
|
||||
let cpu_id = crate::cpu_id();
|
||||
|
||||
let mut switch_context_opt =
|
||||
match select_next_context(token, percpu, cpu_id, switch_time, &mut prev_context_guard) {
|
||||
Ok(opt) => opt,
|
||||
Err(early_ret) => return early_ret,
|
||||
};
|
||||
|
||||
if switch_context_opt.is_none() {
|
||||
let mut this_contexts = contexts_mut(token.token());
|
||||
@@ -334,6 +339,130 @@ 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,
|
||||
prev_context_guard: &mut ArcRwLockWriteGuard<L4, Context>,
|
||||
) -> Result<Option<ArcContextLockWriteGuard>, SwitchResult> {
|
||||
let mut contexts_data = run_contexts_mut(token.token());
|
||||
let mut contexts_list = &mut contexts_data.set;
|
||||
let mut balance = percpu.balance.get();
|
||||
let mut i = percpu.last_queue.get() % 40;
|
||||
|
||||
// Lock the previous context.
|
||||
let prev_context_lock = crate::context::current();
|
||||
|
||||
let mut empty_queues = 0;
|
||||
let mut total_iters = 0;
|
||||
let mut next_context_guard_opt = None;
|
||||
|
||||
let total_contexts: usize = contexts_list.iter().map(|q| q.len()).sum();
|
||||
let mut skipped_contexts = 0;
|
||||
|
||||
'priority: loop {
|
||||
i = (i + 1) % 40;
|
||||
total_iters += 1;
|
||||
|
||||
// The least prioritised queue takes <5000 iters to build up
|
||||
// balance = sched_prio_to_weight[20], if we have already spent
|
||||
// that many iters and not found any context, it is better to just
|
||||
// skip for now
|
||||
if total_iters >= 5000 {
|
||||
break 'priority;
|
||||
}
|
||||
|
||||
if skipped_contexts > total_contexts && total_contexts > 0 {
|
||||
break 'priority;
|
||||
}
|
||||
|
||||
let contexts = contexts_list
|
||||
.get_mut(i)
|
||||
.expect("i should be between [0, 39]!");
|
||||
|
||||
if contexts.is_empty() {
|
||||
empty_queues += 1;
|
||||
if empty_queues >= 40 {
|
||||
// If all queues are empty, just break out
|
||||
break 'priority;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
empty_queues = 0;
|
||||
}
|
||||
|
||||
if balance[i] < sched_prio_to_weight[20] {
|
||||
// This queue does not have enough balance to run,
|
||||
// increment the balance!
|
||||
balance[i] += sched_prio_to_weight[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
let len = contexts.len();
|
||||
for _ in 0..len {
|
||||
let next_context_lock = match contexts.pop_front() {
|
||||
Some(lock) => match lock.upgrade() {
|
||||
Some(new_lock) => new_lock,
|
||||
None => {
|
||||
skipped_contexts += 1;
|
||||
continue; // Ghost Process, just continue
|
||||
}
|
||||
},
|
||||
None => break, // Empty Queue
|
||||
};
|
||||
|
||||
if Arc::ptr_eq(&next_context_lock, &prev_context_lock) {
|
||||
contexts.push_back(ContextRef(Arc::clone(&next_context_lock)));
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut next_context_guard = unsafe { next_context_lock.write_arc() };
|
||||
next_context_guard.enqueued = false;
|
||||
|
||||
if !next_context_guard.status.is_runnable() {
|
||||
skipped_contexts += 1;
|
||||
continue; // Lazy removal of blocked contexts
|
||||
}
|
||||
|
||||
// Is this context runnable on this CPU?
|
||||
if let UpdateResult::CanSwitch =
|
||||
unsafe { update_runnable(&mut next_context_guard, cpu_id, switch_time) }
|
||||
{
|
||||
next_context_guard_opt = Some(next_context_guard);
|
||||
balance[i] -= sched_prio_to_weight[20];
|
||||
break 'priority;
|
||||
} else {
|
||||
contexts.push_back(ContextRef(Arc::clone(&next_context_lock)));
|
||||
next_context_guard.enqueued = true;
|
||||
skipped_contexts += 1;
|
||||
|
||||
if skipped_contexts >= total_contexts {
|
||||
break 'priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
percpu.balance.set(balance);
|
||||
percpu.last_queue.set(i);
|
||||
|
||||
if let Some(next_context_guard) = next_context_guard_opt {
|
||||
// We found a new process!
|
||||
// Send the old process to the back of the line (if it is still runnable)
|
||||
if prev_context_guard.status.is_runnable() {
|
||||
let prio = prev_context_guard.prio;
|
||||
contexts_list[prio].push_back(ContextRef(Arc::clone(&prev_context_lock)));
|
||||
prev_context_guard.enqueued = true;
|
||||
}
|
||||
|
||||
return Ok(Some(next_context_guard));
|
||||
} else {
|
||||
// We found no other process to run.
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds per-CPU state necessary for context switching.
|
||||
///
|
||||
/// This struct contains information such as the idle context, current context, and PIT tick counts,
|
||||
|
||||
@@ -34,6 +34,8 @@ pub struct PercpuBlock {
|
||||
pub current_addrsp: RefCell<Option<Arc<AddrSpaceWrapper>>>,
|
||||
pub new_addrsp_tmp: Cell<Option<Arc<AddrSpaceWrapper>>>,
|
||||
pub wants_tlb_shootdown: AtomicBool,
|
||||
pub balance: Cell<[usize; 40]>,
|
||||
pub last_queue: Cell<usize>,
|
||||
|
||||
// TODO: Put mailbox queues here, e.g. for TLB shootdown? Just be sure to 128-byte align it
|
||||
// first to avoid cache invalidation.
|
||||
@@ -204,6 +206,8 @@ impl PercpuBlock {
|
||||
current_addrsp: RefCell::new(None),
|
||||
new_addrsp_tmp: Cell::new(None),
|
||||
wants_tlb_shootdown: AtomicBool::new(false),
|
||||
balance: Cell::new([0; 40]),
|
||||
last_queue: Cell::new(39),
|
||||
ptrace_flags: Cell::new(PtraceFlags::empty()),
|
||||
ptrace_session: RefCell::new(None),
|
||||
inside_syscall: Cell::new(false),
|
||||
|
||||
+4
-2
@@ -1265,6 +1265,7 @@ impl ContextHandle {
|
||||
guard.pid = info.pid as usize;
|
||||
guard.euid = info.euid;
|
||||
guard.egid = info.egid;
|
||||
guard.prio = (info.prio as usize).min(39);
|
||||
Ok(size_of::<ProcSchemeAttrs>())
|
||||
}
|
||||
ContextHandle::OpenViaDup => {
|
||||
@@ -1459,14 +1460,15 @@ impl ContextHandle {
|
||||
ContextHandle::Attr => {
|
||||
let mut debug_name = [0; 32];
|
||||
let c = &context.read(token.token());
|
||||
let (euid, egid, pid, name) = (c.euid, c.egid, c.pid as u32, c.name);
|
||||
let (euid, egid, pid, name, prio) =
|
||||
(c.euid, c.egid, c.pid as u32, c.name, c.prio as u32);
|
||||
let min = name.len().min(debug_name.len());
|
||||
debug_name[..min].copy_from_slice(&name.as_bytes()[..min]);
|
||||
buf.copy_common_bytes_from_slice(&ProcSchemeAttrs {
|
||||
pid,
|
||||
euid,
|
||||
egid,
|
||||
ens: 0,
|
||||
prio,
|
||||
debug_name,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user