sched: Work: deref into Arc<Task> rather than OwnedTask

Since a Arc<Work> can be obtained from `TASK_LIST`, this would allow the
potential mutation of 'owned'-state from other CPUs thereby causing a
race condition. Thefore, ensure that the deref of an `Arc<Work>` only
permits access to `t_shared`.
This commit is contained in:
Matthew Leach
2026-03-16 20:38:22 +00:00
parent a8aef0483f
commit 3d20e28c4f
4 changed files with 78 additions and 64 deletions

View File

@@ -60,7 +60,7 @@ pub async fn sys_capget(hdrp: TUA<CapUserHeader>, datap: TUA<CapUserData>) -> Re
.iter()
.find(|task| task.0.tgid.value() == header.pid as u32)
.and_then(|task| task.1.upgrade())
.map(|x| x.t_shared.clone())
.map(|x| (*x).clone())
.ok_or(KernelError::NoProcess)?
};
match header.version {
@@ -96,7 +96,7 @@ pub async fn sys_capset(hdrp: TUA<CapUserHeader>, datap: TUA<CapUserData>) -> Re
.iter()
.find(|task| task.0.tgid.value() == header.pid as u32)
.and_then(|task| task.1.upgrade())
.map(|x| x.t_shared.clone())
.map(|x| (*x).clone())
.ok_or(KernelError::NoProcess)?
};

View File

@@ -218,11 +218,11 @@ impl SchedState {
{
let current = self.run_q.current_mut();
current.task.update_accounting(Some(now_inst));
current.work.task.update_accounting(Some(now_inst));
// Reset accounting baseline after updating stats to avoid double-counting
// the same time interval on the next scheduler tick.
current.task.reset_last_account(now_inst);
current.work.reset_last_account(now_inst);
}
self.run_q.schedule(now_inst)
@@ -270,7 +270,7 @@ pub fn sys_sched_yield() -> Result<usize> {
}
pub fn current_work() -> Arc<Work> {
SCHED_STATE.borrow().run_q.current().task.clone()
SCHED_STATE.borrow().run_q.current().work.clone()
}
pub fn current_work_waker() -> Waker {

View File

@@ -101,8 +101,8 @@ impl RunQueue {
let mut deferred_drops: Vec<RunnableTask> = Vec::new();
if let Some(mut cur_task) = self.running_task.take() {
prev_task = Arc::as_ptr(&cur_task.task);
let state = cur_task.task.state.load(Ordering::Acquire);
prev_task = Arc::as_ptr(&cur_task.work);
let state = cur_task.work.state.load(Ordering::Acquire);
match state {
TaskState::Running | TaskState::Woken => {
if cur_task.tick(now) {
@@ -116,7 +116,7 @@ impl RunQueue {
TaskState::PendingSleep | TaskState::PendingStop => {
// Task wants to deactivate. Drop the RunnableTask now to
// restore sched_data.
let work = cur_task.task.clone();
let work = cur_task.work.clone();
cur_task.sched_data.last_cpu = ArchImpl::id();
self.total_weight = self.total_weight.saturating_sub(cur_task.weight() as u64);
drop(cur_task);
@@ -138,17 +138,17 @@ impl RunQueue {
{
next_task.about_to_execute(now);
if Arc::as_ptr(&next_task.task) != prev_task {
if Arc::as_ptr(&next_task.work) != prev_task {
// If we scheduled a different task than before, context switch.
NUM_CONTEXT_SWITCHES.fetch_add(1, Ordering::Relaxed);
next_task.switch_context();
next_task.task.reset_last_account(now);
next_task.work.reset_last_account(now);
CUR_TASK_PTR
.borrow_mut()
.set_current(Box::as_ptr(&next_task.task.task) as *mut _);
.set_current(Box::as_ptr(&next_task.work.task) as *mut _);
}
self.running_task = Some(next_task);
@@ -158,7 +158,7 @@ impl RunQueue {
CUR_TASK_PTR
.borrow_mut()
.set_current(Box::as_ptr(&self.idle.task.task) as *mut _);
.set_current(Box::as_ptr(&self.idle.work.task) as *mut _);
}
deferred_drops
@@ -192,7 +192,7 @@ impl RunQueue {
}
while let Some(ByDeadline(best)) = self.eligible.pop() {
if best.task.state.load(Ordering::Acquire).is_finished() {
if best.work.state.load(Ordering::Acquire).is_finished() {
self.total_weight = self.total_weight.saturating_sub(best.weight() as u64);
deferred_drops.push(best);
continue;
@@ -211,8 +211,9 @@ impl RunQueue {
None
}
fn enqueue(&mut self, task: RunnableTask) {
task.task.state.mark_runnable();
fn enqueue(&mut self, mut task: RunnableTask) {
task.refresh_priority();
task.work.state.mark_runnable();
if self.v_clock.is_task_eligible(&task) {
self.eligible.push(ByDeadline(task));

View File

@@ -7,7 +7,7 @@ use super::{DEFAULT_TIME_SLICE, SCHED_WEIGHT_BASE, VT_FIXED_SHIFT};
use crate::{
arch::{Arch, ArchImpl},
drivers::timer::{Instant, schedule_preempt},
process::owned::OwnedTask,
process::{Task, owned::OwnedTask},
sync::SpinLock,
};
@@ -16,12 +16,6 @@ use state::TaskStateMachine;
pub mod state;
pub struct Work {
pub task: Box<OwnedTask>,
pub state: TaskStateMachine,
pub sched_data: SpinLock<Option<SchedulerData>>,
}
pub const NR_CPUS: usize = 256;
pub const CPU_MASK_SIZE: usize = NR_CPUS / 64;
@@ -37,10 +31,11 @@ pub struct SchedulerData {
pub last_run: Option<Instant>,
pub last_cpu: usize,
pub cpu_mask: [u64; CPU_MASK_SIZE],
pub priority: i8,
}
impl SchedulerData {
fn new() -> Self {
fn new(task: &Box<OwnedTask>) -> Self {
Self {
v_runtime: 0,
v_eligible: 0,
@@ -50,58 +45,72 @@ impl SchedulerData {
last_run: None,
last_cpu: usize::MAX,
cpu_mask: [u64::MAX; CPU_MASK_SIZE],
priority: task.priority(),
}
}
}
pub struct Work {
pub task: Box<OwnedTask>,
pub state: TaskStateMachine,
pub sched_data: SpinLock<Option<SchedulerData>>,
}
impl Deref for Work {
type Target = Arc<Task>;
fn deref(&self) -> &Self::Target {
&self.task.t_shared
}
}
impl DerefMut for Work {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.task.t_shared
}
}
impl Work {
pub fn new(task: Box<OwnedTask>) -> Arc<Self> {
let sched_data = SchedulerData::new(&task);
Arc::new(Self {
task,
state: TaskStateMachine::new(),
sched_data: SpinLock::new(Some(sched_data)),
})
}
pub fn into_runnable(self: Arc<Self>) -> RunnableTask {
let mut sd = self
.sched_data
.lock_save_irq()
.take()
.expect("Should have sched data");
// Refresh priority.
sd.priority = self.task.priority();
RunnableTask {
work: self,
sched_data: sd,
}
}
}
pub struct RunnableTask {
pub(super) task: Arc<Work>,
pub(super) work: Arc<Work>,
pub(super) sched_data: SchedulerData,
}
impl Drop for RunnableTask {
// Replace the hot sched info back into the struct.
fn drop(&mut self) {
*self.task.sched_data.lock_save_irq() = Some(self.sched_data.clone());
*self.work.sched_data.lock_save_irq() = Some(self.sched_data.clone());
}
}
impl Deref for Work {
type Target = OwnedTask;
fn deref(&self) -> &Self::Target {
&self.task
}
}
impl DerefMut for Work {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.task
}
}
impl Work {
pub fn new(task: Box<OwnedTask>) -> Arc<Self> {
Arc::new(Self {
task,
state: TaskStateMachine::new(),
sched_data: SpinLock::new(Some(SchedulerData::new())),
})
}
pub fn into_runnable(self: Arc<Self>) -> RunnableTask {
let sd = self
.sched_data
.lock_save_irq()
.take()
.expect("Should have sched data");
RunnableTask {
task: self,
sched_data: sd,
}
}
}
impl Deref for RunnableTask {
type Target = SchedulerData;
@@ -161,7 +170,7 @@ impl RunnableTask {
/// weight = priority + SCHED_WEIGHT_BASE
/// The sum is clamped to a minimum of 1
pub fn weight(&self) -> u32 {
let w = self.task.priority() as i32 + SCHED_WEIGHT_BASE;
let w = self.sched_data.priority as i32 + SCHED_WEIGHT_BASE;
if w <= 0 { 1 } else { w as u32 }
}
@@ -199,7 +208,7 @@ impl RunnableTask {
/// Setup task accounting info such that it is about to be executed.
pub fn about_to_execute(&mut self, now: Instant) {
self.exec_start = Some(now);
self.task.state.activate();
self.work.state.activate();
// Deadline logic
if self.deadline.is_none_or(|d| d <= now + DEFAULT_TIME_SLICE) {
@@ -212,6 +221,10 @@ impl RunnableTask {
}
pub fn switch_context(&self) {
ArchImpl::context_switch(self.task.t_shared.clone());
ArchImpl::context_switch(self.work.task.t_shared.clone());
}
pub fn refresh_priority(&mut self) {
self.sched_data.priority = self.work.task.priority();
}
}