Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion awkernel_async_lib/src/scheduler/clustered_edf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ impl ClusteredEDFScheduler {
return false;
};

if task > target_task {
if task.preempts(&target_task) {
push_preemption_pending(victim_cpu, task);
let preempt_irq = awkernel_lib::interrupt::get_preempt_irq();
set_need_preemption(target_task.id, victim_cpu);
Expand Down
2 changes: 1 addition & 1 deletion awkernel_async_lib/src/scheduler/gedf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ impl GEDFScheduler {
.unwrap();

let (target_task, target_cpu) = preemption_target;
if task > target_task {
if task.preempts(&target_task) {
push_preemption_pending(target_cpu, task);
let preempt_irq = awkernel_lib::interrupt::get_preempt_irq();
set_need_preemption(target_task.id, target_cpu);
Expand Down
2 changes: 1 addition & 1 deletion awkernel_async_lib/src/scheduler/prioritized_fifo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl PrioritizedFIFOScheduler {
.unwrap();

let (target_task, target_cpu) = preemption_target;
if task > target_task {
if task.preempts(&target_task) {
push_preemption_pending(target_cpu, task);
let preempt_irq = awkernel_lib::interrupt::get_preempt_irq();
set_need_preemption(target_task.id, target_cpu);
Expand Down
2 changes: 1 addition & 1 deletion awkernel_async_lib/src/scheduler/prioritized_rr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl PrioritizedRRScheduler {
.unwrap();

let (target_task, target_cpu) = preemption_target;
if task > target_task {
if task.preempts(&target_task) {
push_preemption_pending(target_cpu, task);
let preempt_irq = awkernel_lib::interrupt::get_preempt_irq();
set_need_preemption(target_task.id, target_cpu);
Expand Down
25 changes: 25 additions & 0 deletions awkernel_async_lib/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,31 @@ impl Task {
pub fn scheduler_name(&self) -> SchedulerType {
self.scheduler.scheduler_name()
}

/// True if `self` must preempt `other`, i.e. `self` has a strictly higher
/// priority (scheduler priority, then task priority; see `PriorityInfo`).
///
/// Equal priorities never preempt. Within a priority level the schedulers
/// are FIFO (PrioritizedFIFO, ClusteredEDF/GEDF on equal deadlines) or
/// rotate by their own time quantum (PrioritizedRR), so a task woken while
/// an equal-priority task is running waits in the run queue instead of
/// displacing it.
///
/// The one exception is the PrioritizedRR time quantum:
/// `PrioritizedRRScheduler::invoke_preemption_tick` pushes the next
/// runnable task to the preemption-pending heap without a priority check,
/// and `preempt::do_preemption` lets such an equal-priority pending task
/// preempt a running PrioritizedRR task so that the quantum rotates tasks
/// of the same priority.
///
/// This is deliberately not `self > other`: [`Ord`] for `Task` breaks
/// priority ties by task ID to give the preemption-pending heaps a total
/// order, and that tie-break must not decide whether a running task is
/// preempted.
#[inline(always)]
pub(crate) fn preempts(&self, other: &Self) -> bool {
self.priority > other.priority
}
}

impl PartialEq for Task {
Expand Down
26 changes: 24 additions & 2 deletions awkernel_async_lib/src/task/preempt.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::{
scheduler::{move_preemption_pending, peek_preemption_pending, remove_preemption_pending},
scheduler::{
move_preemption_pending, peek_preemption_pending, remove_preemption_pending, SchedulerType,
},
task::{get_current_task, get_task, set_current_task, Task},
};
use alloc::{collections::VecDeque, sync::Arc};
Expand Down Expand Up @@ -153,7 +155,27 @@ unsafe fn do_preemption() {
{
let current_task = get_task(current_task_id).unwrap();

if current_task > next {
// The running task may have changed since the IPI was requested. Only
// a strictly higher-priority pending task preempts it; on equal
// priority the pending task is re-queued instead (FIFO within a
// priority level).
//
// The one exception is the PrioritizedRR time quantum. Every wake
// path requires a strictly higher priority (`Task::preempts`), so a
// pending task of equal priority can only come from
// `PrioritizedRRScheduler::invoke_preemption_tick`, which pushes the
// next runnable task without a priority check when the quantum of
// the running task has expired. Let it through when the running task
// is PrioritizedRR so that the quantum rotates equal-priority tasks.
// `PriorityInfo` includes the scheduler priority, so equal priority
// implies that `next` is a PrioritizedRR task too.
let quantum_rotation = !current_task.preempts(&next)
&& matches!(
current_task.scheduler_name(),
SchedulerType::PrioritizedRR(_)
);

if !next.preempts(&current_task) && !quantum_rotation {
remove_preemption_pending(cpu_id, next.id);
next.scheduler.wake_task(next);
return;
Expand Down
4 changes: 2 additions & 2 deletions mdbook/src/internal/scheduler.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ The PrioritizedFIFO scheduler is implemented in [prioritized_fifo.rs](https://gi

The scheduler uses a `PriorityQueue<PrioritizedFIFOTask>` as its run queue. When a task is enqueued through `wake_task()`, the priority is extracted from `SchedulerType::PrioritizedFIFO(priority)` and used to insert the task into the priority queue. The get_next() method retrieves the task at the head of the highest-priority non-empty queue.

The scheduler implements preemption via `invoke_preemption()`, which evaluates all currently running tasks and determines if the newly awakened task should preempt any of them. If preemption is triggered, the scheduler sends an IPI to the target CPU and updates the preemption pending queue.
The scheduler implements preemption via `invoke_preemption()`, which evaluates all currently running tasks and determines if the newly awakened task should preempt any of them. If preemption is triggered, the scheduler sends an IPI to the target CPU and updates the preemption pending queue. Preemption requires a strictly higher priority (`Task::preempts()`): a task woken while an equal-priority task is running is enqueued behind it, so the FIFO order within a priority level also holds against running tasks. The same rule applies to the other schedulers and to the preemption handler itself, which re-queues a pending task if the task running on the target CPU no longer has a strictly lower priority, except for the PrioritizedRR time-quantum rotation described below.

### PrioritizedRR Scheduler

The PrioritizedRR (Prioritized Round Robin) scheduler is implemented in [prioritized_rr.rs](https://github.com/tier4/awkernel/blob/main/awkernel_async_lib/src/scheduler/prioritized_rr.rs). This scheduler combines fixed-priority scheduling with time quantum enforcement to provide fair CPU time distribution.

The scheduler maintains a `PriorityQueue<PrioritizedRRTask>` similar to PrioritizedFIFO, but adds time quantum management with a default interval of 4ms (4,000 microseconds). The scheduler provides two preemption mechanisms: `invoke_preemption_wake()` for priority-based preemption when tasks are awakened, and `invoke_preemption_tick()` for time quantum-based preemption.

The `invoke_preemption_tick()` method is called periodically on primary CPU to check if the currently running task has exceeded its time quantum. It compares the elapsed execution time against the configured interval and triggers preemption by sending an IPI if the quantum is exceeded.
The `invoke_preemption_tick()` method is called periodically on primary CPU to check if the currently running task has exceeded its time quantum. It compares the elapsed execution time against the configured interval and triggers preemption by sending an IPI if the quantum is exceeded. The task pushed by the tick usually has the same priority as the running one; `do_preemption()` lets such an equal-priority pending task preempt a running PrioritizedRR task, which is the only case where equal priority preempts.

### Panicked Scheduler

Expand Down
Loading