Replace enqueue with dedicated idle contexts

This commit is contained in:
Wildan M
2026-04-26 06:37:38 +07:00
parent de7926e05e
commit 0534ab6377
3 changed files with 67 additions and 59 deletions
-3
View File
@@ -140,8 +140,6 @@ pub struct Context {
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>,
@@ -200,7 +198,6 @@ impl Context {
userspace: false,
fmap_ret: None,
prio: 20,
enqueued: false,
being_sigkilled: false,
owner_proc_id,
+9 -23
View File
@@ -77,6 +77,9 @@ static CONTEXTS: RwLock<L2, BTreeSet<ContextRef>> = RwLock::new(BTreeSet::new())
// Actual context store for the scheduler
static RUN_CONTEXTS: Mutex<L1, RunContextData> = Mutex::new(RunContextData::new());
// Context that has been pushed out from RUN_CONTEXTS after being idle
static IDLE_CONTEXTS: Mutex<L2, VecDeque<ContextRef>> = Mutex::new(VecDeque::new());
pub struct RunContextData {
set: [VecDeque<ContextRef>; 40],
}
@@ -100,6 +103,10 @@ pub fn contexts_mut(token: LockToken<'_, L1>) -> RwLockWriteGuard<'_, L2, BTreeS
CONTEXTS.write(token)
}
pub fn idle_contexts(token: LockToken<'_, L1>) -> MutexGuard<'_, L2, VecDeque<ContextRef>> {
IDLE_CONTEXTS.lock(token)
}
pub fn run_contexts(token: LockToken<'_, L0>) -> MutexGuard<'_, L1, RunContextData> {
RUN_CONTEXTS.lock(token)
}
@@ -118,7 +125,6 @@ pub fn init(token: &mut CleanLockToken) {
context.status = Status::Runnable;
context.running = true;
context.cpu_id = Some(crate::cpu_id());
context.enqueued = false;
let priority = context.prio;
@@ -136,25 +142,6 @@ pub fn init(token: &mut CleanLockToken) {
}
}
pub fn wakeup_context(context_lock: &Arc<RwLock<L4, Context>>, mut token: LockToken<L0>) {
let priority = {
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;
context.prio
};
run_contexts(token).set[priority].push_back(ContextRef(Arc::clone(context_lock)));
}
pub fn current() -> Arc<ContextLock> {
PercpuBlock::current()
.switch_internals
@@ -219,9 +206,8 @@ pub fn spawn(
let context_ref = ContextRef(Arc::clone(&context_lock));
let run_ref = ContextRef(Arc::clone(&context_lock));
run_contexts(token.token()).set[20].push_back(run_ref);
contexts_mut(token.token().downgrade()).insert(context_ref);
context_lock.write(token.token()).enqueued = true;
idle_contexts(token.downgrade()).push_back(run_ref);
contexts_mut(token.downgrade()).insert(context_ref);
Ok(context_lock)
}
+58 -33
View File
@@ -3,7 +3,10 @@
//! handling process states and synchronization.
use crate::{
context::{self, arch, contexts, run_contexts, ArcContextLockWriteGuard, Context, ContextLock},
context::{
self, arch, contexts, idle_contexts, run_contexts, ArcContextLockWriteGuard, Context,
ContextLock,
},
cpu_set::LogicalCpuId,
cpu_stats::{self, CpuState},
percpu::PercpuBlock,
@@ -22,6 +25,7 @@ use super::ContextRef;
enum UpdateResult {
CanSwitch,
Skip,
Blocked,
}
// A simple geometric series where value[i] ~= value[i - 1] * 1.25
@@ -72,7 +76,7 @@ unsafe fn update_runnable(
if context.status.is_runnable() {
UpdateResult::CanSwitch
} else {
UpdateResult::Skip
UpdateResult::Blocked
}
}
@@ -177,34 +181,52 @@ pub fn switch(token: &mut CleanLockToken) -> SwitchResult {
let mut wakeups = Vec::new();
{
let current_context = context::current();
let idle_context = percpu.switch_internals.idle_context();
let mut contexts_guard = contexts(token.downgrade());
let (context, mut token) = contexts_guard.token_split();
for context_ref in context.iter().filter_map(|r| r.upgrade()) {
let idle_contexts = idle_contexts(token.downgrade());
let (mut idle_contexts, mut token) = idle_contexts.into_split();
let len = idle_contexts.len();
for i in 0..len {
let Some(context_ref) = idle_contexts.pop_front() else {
break;
};
let Some(context_ref) = context_ref.upgrade() else {
continue;
};
if Arc::ptr_eq(&context_ref, &current_context) {
idle_contexts.push_back(ContextRef(context_ref));
continue;
}
if Arc::ptr_eq(&context_ref, &idle_context) {
let Some(guard) = context_ref.try_read(token.token()) else {
idle_contexts.push_back(ContextRef(context_ref));
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));
let prio = guard.prio;
drop(guard);
wakeups.push((prio, ContextRef(context_ref)));
continue;
}
}
}
if guard.status.is_runnable() && !guard.enqueued && !guard.running {
wakeups.push(Arc::clone(&context_ref));
if guard.status.is_runnable() && !guard.running {
let prio = guard.prio;
drop(guard);
wakeups.push((prio, ContextRef(context_ref)));
continue;
}
drop(guard);
idle_contexts.push_back(ContextRef(context_ref));
}
}
for context_lock in wakeups {
context::wakeup_context(&context_lock, token.token());
if wakeups.len() > 0 {
let mut run_contexts = run_contexts(token.token());
for (prio, context_lock) in wakeups {
run_contexts.set[prio].push_back(context_lock);
}
}
let cpu_id = crate::cpu_id();
@@ -334,8 +356,10 @@ fn select_next_context(
was_idle: bool,
prev_context_guard: &mut ArcRwLockWriteGuard<L4, Context>,
) -> Result<Option<ArcContextLockWriteGuard>, SwitchResult> {
let mut contexts_data = run_contexts(token.token());
let contexts_data = run_contexts(token.token());
let (mut contexts_data, mut token) = contexts_data.into_split();
let contexts_list = &mut contexts_data.set;
let idle_context = percpu.switch_internals.idle_context();
let mut balance = percpu.balance.get();
let mut i = percpu.last_queue.get() % 40;
@@ -401,28 +425,27 @@ fn select_next_context(
};
if Arc::ptr_eq(&next_context_lock, &prev_context_lock) {
contexts.push_back(ContextRef(Arc::clone(&next_context_lock)));
contexts.push_back(ContextRef(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
if Arc::ptr_eq(&next_context_lock, &idle_context) {
contexts.push_back(ContextRef(next_context_lock));
continue;
}
let mut next_context_guard = unsafe { next_context_lock.write_arc() };
// Is this context runnable on this CPU?
if let UpdateResult::CanSwitch =
unsafe { update_runnable(&mut next_context_guard, cpu_id, switch_time) }
{
let sw = unsafe { update_runnable(&mut next_context_guard, cpu_id, switch_time) };
if let UpdateResult::CanSwitch = sw {
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;
if matches!(sw, UpdateResult::Blocked) {
idle_contexts(token.token()).push_back(ContextRef(next_context_lock));
} else {
contexts.push_back(ContextRef(next_context_lock));
};
skipped_contexts += 1;
if skipped_contexts >= total_contexts {
@@ -434,18 +457,20 @@ fn select_next_context(
percpu.balance.set(balance);
percpu.last_queue.set(i);
if let Some(next_context_guard) = next_context_guard_opt {
// We found a new process!
if !Arc::ptr_eq(&prev_context_lock, &idle_context) {
// 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;
} else {
idle_contexts(token.token()).push_back(ContextRef(Arc::clone(&prev_context_lock)));
}
}
if let Some(next_context_guard) = next_context_guard_opt {
// We found a new process!
return Ok(Some(next_context_guard));
} else {
let idle_context = percpu.switch_internals.idle_context();
if !was_idle && !Arc::ptr_eq(&prev_context_lock, &idle_context) {
// We switch into the idle context
Ok(Some(unsafe { idle_context.write_arc() }))