From 6ec9bcb1c4dfbe5fc5365d93e75c516bb03bf9fc Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 11 Sep 2025 16:15:27 +0200 Subject: [PATCH] executor: add priority scheduler. --- ci.sh | 6 ++++++ embassy-executor/CHANGELOG.md | 1 + embassy-executor/Cargo.toml | 14 ++++++++++--- embassy-executor/src/metadata.rs | 26 ++++++++++++++++++++++++ embassy-executor/src/raw/mod.rs | 5 ----- embassy-executor/src/raw/run_queue.rs | 29 ++++++++++++++++++++++----- 6 files changed, 68 insertions(+), 13 deletions(-) diff --git a/ci.sh b/ci.sh index a25097690..81e665edf 100755 --- a/ci.sh +++ b/ci.sh @@ -38,6 +38,12 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,executor-interrupt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt,executor-thread,embassy-time-driver \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt,executor-thread,embassy-time-driver,scheduler-priority \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt,executor-thread,embassy-time-driver,scheduler-priority,scheduler-deadline \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt,executor-thread,embassy-time-driver,scheduler-deadline \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt,executor-thread,scheduler-priority,scheduler-deadline \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt,executor-thread,scheduler-deadline \ --- build --release --manifest-path embassy-executor/Cargo.toml --target armv7a-none-eabi --features arch-cortex-ar,executor-thread \ --- build --release --manifest-path embassy-executor/Cargo.toml --target armv7r-none-eabi --features arch-cortex-ar,executor-thread \ --- build --release --manifest-path embassy-executor/Cargo.toml --target armv7r-none-eabihf --features arch-cortex-ar,executor-thread \ diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index 03d60208e..6f079a11a 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added new metadata API for tasks. - Main task automatically gets a name of `main` when the `metadata-name` feature is enabled. - Upgraded rtos-trace +- Added optional "highest priority" scheduling - Added optional "earliest deadline first" EDF scheduling ## 0.9.1 - 2025-08-31 diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 0ac666f80..d4ea0e6ac 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -24,7 +24,12 @@ build = [ {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-thread"]}, {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt"]}, {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread"]}, - {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "scheduler-deadline", "embassy-time-driver"]}, + {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver"]}, + {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver", "scheduler-priority"]}, + {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver", "scheduler-priority", "scheduler-deadline"]}, + {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver", "scheduler-deadline"]}, + {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "scheduler-priority", "scheduler-deadline"]}, + {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "scheduler-deadline"]}, {target = "armv7a-none-eabi", features = ["arch-cortex-ar", "executor-thread"]}, {target = "armv7r-none-eabi", features = ["arch-cortex-ar", "executor-thread"]}, {target = "armv7r-none-eabihf", features = ["arch-cortex-ar", "executor-thread"]}, @@ -36,7 +41,7 @@ build = [ [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/" -features = ["defmt", "scheduler-deadline"] +features = ["defmt", "scheduler-deadline", "scheduler-priority"] flavors = [ { name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] }, { name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] }, @@ -47,7 +52,7 @@ flavors = [ [package.metadata.docs.rs] default-target = "thumbv7em-none-eabi" targets = ["thumbv7em-none-eabi"] -features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt", "scheduler-deadline", "embassy-time-driver"] +features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt", "scheduler-deadline", "scheduler-priority", "embassy-time-driver"] [dependencies] defmt = { version = "1.0.1", optional = true } @@ -136,6 +141,9 @@ _any_trace = [] ## tasks based on the remaining time before their deadline. Adds some overhead. scheduler-deadline = [] +## Enable "Highest Priority First" Scheduler. Adds some overhead. +scheduler-priority = [] + ## Enable the embassy_time_driver dependency. ## This can unlock extra APIs, for example for the `sheduler-deadline` embassy-time-driver = ["dep:embassy-time-driver"] diff --git a/embassy-executor/src/metadata.rs b/embassy-executor/src/metadata.rs index 4220048a6..bc0df0f83 100644 --- a/embassy-executor/src/metadata.rs +++ b/embassy-executor/src/metadata.rs @@ -1,6 +1,8 @@ #[cfg(feature = "metadata-name")] use core::cell::Cell; use core::future::{poll_fn, Future}; +#[cfg(feature = "scheduler-priority")] +use core::sync::atomic::{AtomicU8, Ordering}; use core::task::Poll; #[cfg(feature = "metadata-name")] @@ -14,6 +16,8 @@ use crate::raw::Deadline; pub struct Metadata { #[cfg(feature = "metadata-name")] name: Mutex>>, + #[cfg(feature = "scheduler-priority")] + priority: AtomicU8, #[cfg(feature = "scheduler-deadline")] deadline: raw::Deadline, } @@ -23,6 +27,8 @@ impl Metadata { Self { #[cfg(feature = "metadata-name")] name: Mutex::new(Cell::new(None)), + #[cfg(feature = "scheduler-priority")] + priority: AtomicU8::new(0), // NOTE: The deadline is set to zero to allow the initializer to reside in `.bss`. This // will be lazily initalized in `initialize_impl` #[cfg(feature = "scheduler-deadline")] @@ -33,6 +39,14 @@ impl Metadata { pub(crate) fn reset(&self) { #[cfg(feature = "metadata-name")] critical_section::with(|cs| self.name.borrow(cs).set(None)); + + #[cfg(feature = "scheduler-priority")] + self.set_priority(0); + + // By default, deadlines are set to the maximum value, so that any task WITH + // a set deadline will ALWAYS be scheduled BEFORE a task WITHOUT a set deadline + #[cfg(feature = "scheduler-deadline")] + self.unset_deadline(); } /// Get the metadata for the current task. @@ -61,6 +75,18 @@ impl Metadata { critical_section::with(|cs| self.name.borrow(cs).set(Some(name))) } + /// Get this task's priority. + #[cfg(feature = "scheduler-priority")] + pub fn priority(&self) -> u8 { + self.priority.load(Ordering::Relaxed) + } + + /// Set this task's priority. + #[cfg(feature = "scheduler-priority")] + pub fn set_priority(&self, priority: u8) { + self.priority.store(priority, Ordering::Relaxed) + } + /// Get this task's deadline. #[cfg(feature = "scheduler-deadline")] pub fn deadline(&self) -> u64 { diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 51a363385..9f36c60bc 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -300,11 +300,6 @@ impl AvailableTask { self.task.raw.poll_fn.set(Some(TaskStorage::::poll)); self.task.future.write_in_place(future); - // By default, deadlines are set to the maximum value, so that any task WITH - // a set deadline will ALWAYS be scheduled BEFORE a task WITHOUT a set deadline - #[cfg(feature = "scheduler-deadline")] - self.task.raw.metadata.unset_deadline(); - let task = TaskRef::new(self.task); SpawnToken::new(task) diff --git a/embassy-executor/src/raw/run_queue.rs b/embassy-executor/src/raw/run_queue.rs index d98c26f73..b8b052310 100644 --- a/embassy-executor/src/raw/run_queue.rs +++ b/embassy-executor/src/raw/run_queue.rs @@ -2,7 +2,7 @@ use core::ptr::{addr_of_mut, NonNull}; use cordyceps::sorted_list::Links; use cordyceps::Linked; -#[cfg(feature = "scheduler-deadline")] +#[cfg(any(feature = "scheduler-priority", feature = "scheduler-deadline"))] use cordyceps::SortedList; #[cfg(target_has_atomic = "ptr")] @@ -83,7 +83,7 @@ impl RunQueue { /// Empty the queue, then call `on_task` for each task that was in the queue. /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue /// and will be processed by the *next* call to `dequeue_all`, *not* the current one. - #[cfg(not(feature = "scheduler-deadline"))] + #[cfg(not(any(feature = "scheduler-priority", feature = "scheduler-deadline")))] pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) { let taken = self.stack.take_all(); for taskref in taken { @@ -106,10 +106,29 @@ impl RunQueue { /// /// This process will repeat until the local `sorted` queue AND the global /// runqueue are both empty, at which point this function will return. - #[cfg(feature = "scheduler-deadline")] + #[cfg(any(feature = "scheduler-priority", feature = "scheduler-deadline"))] pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) { - let mut sorted = - SortedList::::new_with_cmp(|lhs, rhs| lhs.metadata.deadline().cmp(&rhs.metadata.deadline())); + let mut sorted = SortedList::::new_with_cmp(|lhs, rhs| { + // compare by priority first + #[cfg(feature = "scheduler-priority")] + { + let lp = lhs.metadata.priority(); + let rp = rhs.metadata.priority(); + if lp != rp { + return lp.cmp(&rp).reverse(); + } + } + // compare deadlines in case of tie. + #[cfg(feature = "scheduler-deadline")] + { + let ld = lhs.metadata.deadline(); + let rd = rhs.metadata.deadline(); + if ld != rd { + return ld.cmp(&rd); + } + } + core::cmp::Ordering::Equal + }); loop { // For each loop, grab any newly pended items