| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::task::{RoleOverrides, Task}; |
| use fidl::HandleBased; |
| use fidl_fuchsia_scheduler::{ |
| RoleManagerMarker, RoleManagerSetRoleRequest, RoleManagerSynchronousProxy, RoleName, RoleTarget, |
| }; |
| use fuchsia_component::client::connect_to_protocol_sync; |
| use starnix_logging::{impossible_error, log_debug, log_warn, track_stub}; |
| use starnix_uapi::errors::Errno; |
| use starnix_uapi::{ |
| SCHED_BATCH, SCHED_DEADLINE, SCHED_FIFO, SCHED_IDLE, SCHED_NORMAL, SCHED_RESET_ON_FORK, |
| SCHED_RR, errno, error, sched_param, |
| }; |
| |
| pub struct SchedulerManager { |
| role_manager: Option<RoleManagerSynchronousProxy>, |
| role_overrides: RoleOverrides, |
| } |
| |
| impl SchedulerManager { |
| /// Create a new SchedulerManager which will apply any provided `role_overrides` before |
| /// computing a role name based on a Task's scheduler state. |
| pub fn new(role_overrides: RoleOverrides) -> SchedulerManager { |
| let role_manager = fuchsia_runtime::with_thread_self(|thread| { |
| let role_manager = connect_to_protocol_sync::<RoleManagerMarker>().unwrap(); |
| if let Err(e) = Self::set_thread_role_inner( |
| &role_manager, |
| thread, |
| SchedulerState::default().role_name(), |
| ) { |
| log_debug!("Setting thread role failed ({e:?}), will not set thread priority."); |
| None |
| } else { |
| log_debug!("Thread role set successfully, scheduler manager initialized."); |
| Some(role_manager) |
| } |
| }); |
| |
| SchedulerManager { role_manager, role_overrides } |
| } |
| |
| /// Create a new empty SchedulerManager for testing. |
| pub fn empty_for_tests() -> Self { |
| Self { role_manager: None, role_overrides: RoleOverrides::new().build().unwrap() } |
| } |
| |
| /// Return the currently active role name for this task. Requires read access to `task`'s state, |
| /// should only be called by code which is not already modifying the provided `task`. |
| pub fn role_name(&self, task: &Task) -> Result<&str, Errno> { |
| let scheduler_state = task.read().scheduler_state; |
| self.role_name_inner(task, scheduler_state) |
| } |
| |
| fn role_name_inner(&self, task: &Task, scheduler_state: SchedulerState) -> Result<&str, Errno> { |
| let process_name = task |
| .thread_group() |
| .read() |
| .get_task(task.thread_group().leader) |
| .ok_or_else(|| errno!(EINVAL))? |
| .command(); |
| let thread_name = task.command(); |
| if let Some(name) = self.role_overrides.get_role_name(&process_name, &thread_name) { |
| return Ok(name); |
| } |
| Ok(scheduler_state.role_name()) |
| } |
| |
| /// Give the provided `task`'s Zircon thread a role. |
| /// |
| /// Requires passing the current `SchedulerState` so that this can be |
| /// performed without touching `task`'s state lock. |
| pub fn set_thread_role( |
| &self, |
| task: &Task, |
| scheduler_state: SchedulerState, |
| ) -> Result<(), Errno> { |
| let Some(role_manager) = self.role_manager.as_ref() else { |
| log_debug!("no role manager for setting role"); |
| return Ok(()); |
| }; |
| |
| let role_name = self.role_name_inner(task, scheduler_state)?; |
| let thread = task.thread.read(); |
| let Some(thread) = thread.as_ref() else { |
| log_debug!("thread role update requested for task without thread, skipping"); |
| return Ok(()); |
| }; |
| Self::set_thread_role_inner(role_manager, thread, role_name) |
| } |
| |
| fn set_thread_role_inner( |
| role_manager: &RoleManagerSynchronousProxy, |
| thread: &zx::Thread, |
| role_name: &str, |
| ) -> Result<(), Errno> { |
| log_debug!(role_name; "setting thread role"); |
| |
| let thread = thread.duplicate_handle(zx::Rights::SAME_RIGHTS).map_err(impossible_error)?; |
| let request = RoleManagerSetRoleRequest { |
| target: Some(RoleTarget::Thread(thread)), |
| role: Some(RoleName { role: role_name.to_string() }), |
| ..Default::default() |
| }; |
| let _ = role_manager.set_role(request, zx::MonotonicInstant::INFINITE).map_err(|err| { |
| log_warn!(err:%; "Unable to set thread role."); |
| errno!(EINVAL) |
| })?; |
| Ok(()) |
| } |
| } |
| |
| /// The task normal priority, used for favoring or disfavoring a task running |
| /// with some non-real-time scheduling policies. Ranges from -20 to +19 in |
| /// "user-space" representation and +1 to +40 in "kernel-internal" |
| /// representation. See "The nice value" at sched(7) for full specification. |
| #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] |
| pub struct NormalPriority { |
| /// 1 (weakest) to 40 (strongest) (in "kernel-internal" representation), |
| /// from setpriority() |
| value: u8, |
| } |
| |
| impl NormalPriority { |
| const MIN_VALUE: u8 = 1; |
| const DEFAULT_VALUE: u8 = 20; |
| const MAX_VALUE: u8 = 40; |
| |
| /// Creates a normal priority from a value to be interpreted according |
| /// to the "user-space nice" (-20..=19) scale, clamping values outside |
| /// that scale. |
| /// |
| /// It would be strange for this to be called from anywhere outside of |
| /// the setpriority system call. |
| pub(crate) fn from_setpriority_syscall(user_nice: i32) -> Self { |
| Self { |
| value: (Self::DEFAULT_VALUE as i32) |
| .saturating_sub(user_nice) |
| .clamp(Self::MIN_VALUE as i32, Self::MAX_VALUE as i32) as u8, |
| } |
| } |
| |
| /// Creates a normal priority from a value to be interpreted according |
| /// to the "user-space nice" (-20..=19) scale, rejecting values outside |
| /// that scale. |
| /// |
| /// It would be strange for this to be called from anywhere outside of |
| /// our Binder implementation. |
| pub fn from_binder(user_nice: i8) -> Result<Self, Errno> { |
| let value = (Self::DEFAULT_VALUE as i8).saturating_sub(user_nice); |
| if value < (Self::MIN_VALUE as i8) || value > (Self::MAX_VALUE as i8) { |
| return error!(EINVAL); |
| } |
| Ok(Self { value: u8::try_from(value).expect("normal priority should fit in a u8") }) |
| } |
| |
| /// Returns this normal priority's integer representation according |
| /// to the "user-space nice" (-20..=19) scale. |
| pub fn as_nice(&self) -> i8 { |
| (Self::DEFAULT_VALUE as i8) - (self.value as i8) |
| } |
| |
| /// Returns this normal priority's integer representation according |
| /// to the "kernel space nice" (1..=40) scale. |
| pub(crate) fn raw_priority(&self) -> u8 { |
| self.value |
| } |
| |
| /// Returns whether this normal priority exceeds the given limit. |
| pub(crate) fn exceeds(&self, limit: u64) -> bool { |
| limit < (self.value as u64) |
| } |
| } |
| |
| impl std::default::Default for NormalPriority { |
| fn default() -> Self { |
| Self { value: Self::DEFAULT_VALUE } |
| } |
| } |
| |
| /// The task real-time priority, used for favoring or disfavoring a task |
| /// running with real-time scheduling policies. See "Scheduling policies" |
| /// at sched(7) for full specification. |
| #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] |
| pub(crate) struct RealtimePriority { |
| /// 1 (weakest) to 99 (strongest), from sched_setscheduler() and |
| /// sched_setparam(). Only meaningfully used for Fifo and |
| /// Round-Robin; set to 0 for other policies. |
| value: u8, |
| } |
| |
| impl RealtimePriority { |
| const NON_REAL_TIME_VALUE: u8 = 0; |
| const MIN_VALUE: u8 = 1; |
| const MAX_VALUE: u8 = 99; |
| |
| const NON_REAL_TIME: RealtimePriority = RealtimePriority { value: Self::NON_REAL_TIME_VALUE }; |
| |
| pub(crate) fn exceeds(&self, limit: u64) -> bool { |
| limit < (self.value as u64) |
| } |
| } |
| |
| /// The scheduling policies described in "Scheduling policies" at sched(7). |
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
| pub(crate) enum SchedulingPolicy { |
| Normal, |
| Batch, |
| Idle, |
| Fifo, |
| RoundRobin, |
| } |
| |
| impl SchedulingPolicy { |
| fn realtime_priority_min(&self) -> u8 { |
| match self { |
| Self::Normal | Self::Batch | Self::Idle => RealtimePriority::NON_REAL_TIME_VALUE, |
| Self::Fifo | Self::RoundRobin => RealtimePriority::MIN_VALUE, |
| } |
| } |
| |
| fn realtime_priority_max(&self) -> u8 { |
| match self { |
| Self::Normal | Self::Batch | Self::Idle => RealtimePriority::NON_REAL_TIME_VALUE, |
| Self::Fifo | Self::RoundRobin => RealtimePriority::MAX_VALUE, |
| } |
| } |
| |
| pub(crate) fn realtime_priority_from(&self, priority: i32) -> Result<RealtimePriority, Errno> { |
| let priority = u8::try_from(priority).map_err(|_| errno!(EINVAL))?; |
| if priority < self.realtime_priority_min() || priority > self.realtime_priority_max() { |
| return error!(EINVAL); |
| } |
| Ok(RealtimePriority { value: priority }) |
| } |
| } |
| |
| impl TryFrom<u32> for SchedulingPolicy { |
| type Error = Errno; |
| |
| fn try_from(value: u32) -> Result<Self, Errno> { |
| Ok(match value { |
| SCHED_NORMAL => Self::Normal, |
| SCHED_BATCH => Self::Batch, |
| SCHED_IDLE => Self::Idle, |
| SCHED_FIFO => Self::Fifo, |
| SCHED_RR => Self::RoundRobin, |
| _ => { |
| return error!(EINVAL); |
| } |
| }) |
| } |
| } |
| |
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
| pub struct SchedulerState { |
| pub(crate) policy: SchedulingPolicy, |
| /// Although nice is only used for Normal and Batch, normal priority |
| /// ("nice") is still maintained, observable, and alterable when a |
| /// task is using Idle, Fifo, and RoundRobin. |
| pub(crate) normal_priority: NormalPriority, |
| /// 1 (weakest) to 99 (strongest), from sched_setscheduler() and |
| /// sched_setparam(). Only used for Fifo and Round-Robin. |
| pub(crate) realtime_priority: RealtimePriority, |
| pub(crate) reset_on_fork: bool, |
| } |
| |
| impl SchedulerState { |
| pub fn is_default(&self) -> bool { |
| self == &Self::default() |
| } |
| |
| /// Create a policy according to the "sched_policy" and "priority" bits of |
| /// a flat_binder_object_flags bitmask (see uapi/linux/android/binder.h). |
| /// |
| /// It would be very strange for this to need to be called anywhere outside |
| /// of our Binder implementation. |
| pub fn from_binder(policy: u8, priority_or_nice: u8) -> Result<Self, Errno> { |
| let (policy, normal_priority, realtime_priority) = match policy as u32 { |
| SCHED_NORMAL => ( |
| SchedulingPolicy::Normal, |
| NormalPriority::from_binder(priority_or_nice as i8)?, |
| RealtimePriority::NON_REAL_TIME, |
| ), |
| SCHED_BATCH => ( |
| SchedulingPolicy::Batch, |
| NormalPriority::from_binder(priority_or_nice as i8)?, |
| RealtimePriority::NON_REAL_TIME, |
| ), |
| SCHED_FIFO => ( |
| SchedulingPolicy::Fifo, |
| NormalPriority::default(), |
| SchedulingPolicy::Fifo.realtime_priority_from(priority_or_nice as i32)?, |
| ), |
| SCHED_RR => ( |
| SchedulingPolicy::RoundRobin, |
| NormalPriority::default(), |
| SchedulingPolicy::RoundRobin.realtime_priority_from(priority_or_nice as i32)?, |
| ), |
| _ => return error!(EINVAL), |
| }; |
| Ok(Self { policy, normal_priority, realtime_priority, reset_on_fork: false }) |
| } |
| |
| pub fn fork(self) -> Self { |
| if self.reset_on_fork { |
| let (policy, normal_priority, realtime_priority) = if self.is_realtime() { |
| // If the calling task has a real-time scheduling policy, the |
| // policy given to child processes is SCHED_OTHER and the nice is |
| // NormalPriority::default() (in all such cases and without caring |
| // about whether the caller's nice had been stronger or weaker than |
| // NormalPriority::default()). |
| ( |
| SchedulingPolicy::Normal, |
| NormalPriority::default(), |
| RealtimePriority::NON_REAL_TIME, |
| ) |
| } else { |
| // If the calling task has a non-real-time scheduling policy, the |
| // state given to child processes is the same as that of the |
| // caller except with the caller's nice clamped to |
| // NormalPriority::default() at the strongest. |
| ( |
| self.policy, |
| std::cmp::min(self.normal_priority, NormalPriority::default()), |
| RealtimePriority::NON_REAL_TIME, |
| ) |
| }; |
| Self { |
| policy, |
| normal_priority, |
| realtime_priority, |
| // This flag is disabled in child processes created by fork(2). |
| reset_on_fork: false, |
| } |
| } else { |
| self |
| } |
| } |
| |
| /// Return the policy as an integer (SCHED_NORMAL, SCHED_BATCH, &c) bitwise-ored |
| /// with the current reset-on-fork status (SCHED_RESET_ON_FORK or 0, depending). |
| /// |
| /// It would be strange for this to need to be called anywhere outside the |
| /// implementation of the sched_getscheduler system call. |
| pub fn policy_for_sched_getscheduler(&self) -> u32 { |
| let mut base = match self.policy { |
| SchedulingPolicy::Normal => SCHED_NORMAL, |
| SchedulingPolicy::Batch => SCHED_BATCH, |
| SchedulingPolicy::Idle => SCHED_IDLE, |
| SchedulingPolicy::Fifo => SCHED_FIFO, |
| SchedulingPolicy::RoundRobin => SCHED_RR, |
| }; |
| if self.reset_on_fork { |
| base |= SCHED_RESET_ON_FORK; |
| } |
| base |
| } |
| |
| /// Return the priority as a field in a sched_param struct. |
| /// |
| /// It would be strange for this to need to be called anywhere outside the |
| /// implementation of the sched_getparam system call. |
| pub fn get_sched_param(&self) -> sched_param { |
| sched_param { |
| sched_priority: (if self.is_realtime() { |
| self.realtime_priority.value |
| } else { |
| RealtimePriority::NON_REAL_TIME_VALUE |
| }) as i32, |
| } |
| } |
| |
| pub fn normal_priority(&self) -> NormalPriority { |
| self.normal_priority |
| } |
| |
| pub fn is_realtime(&self) -> bool { |
| match self.policy { |
| SchedulingPolicy::Normal | SchedulingPolicy::Batch | SchedulingPolicy::Idle => false, |
| SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => true, |
| } |
| } |
| |
| /// Returns a number 0-31 (inclusive) mapping Linux scheduler priority to a Zircon priority |
| /// level for the fair scheduler. |
| /// |
| /// The range of 32 Zircon priorities is divided into a region for each flavor of Linux |
| /// scheduling: |
| /// |
| /// 1. 0 is used for SCHED_IDLE, the lowest priority Linux tasks. |
| /// 2. 6-15 (inclusive) is used for lower-than-default-priority SCHED_OTHER/SCHED_BATCH tasks. |
| /// 3. 16 is used for the default priority SCHED_OTHER/SCHED_BATCH, the same as Zircon's |
| /// default for Fuchsia processes. |
| /// 4. 17-26 (inclusive) is used for higher-than-default-priority SCHED_OTHER/SCHED_BATCH tasks. |
| /// 5. Realtime tasks receive their own profile name. |
| fn role_name(&self) -> &'static str { |
| match self.policy { |
| // Mapped to 0; see "the [...] nice value has no influence for [the SCHED_IDLE] policy" |
| // at sched(7). |
| SchedulingPolicy::Idle => FAIR_PRIORITY_ROLE_NAMES[0], |
| |
| // Configured with nice 0-40 and mapped to 6-26. 20 is the default nice which we want to |
| // map to 16. |
| SchedulingPolicy::Normal => { |
| FAIR_PRIORITY_ROLE_NAMES[(self.normal_priority.value as usize / 2) + 6] |
| } |
| SchedulingPolicy::Batch => { |
| track_stub!(TODO("https://fxbug.dev/308055542"), "SCHED_BATCH hinting"); |
| FAIR_PRIORITY_ROLE_NAMES[(self.normal_priority.value as usize / 2) + 6] |
| } |
| |
| // Configured with priority 1-99, mapped to a constant bandwidth profile. Priority |
| // between realtime tasks is ignored because we don't currently have a way to tell the |
| // scheduler that a given realtime task is more important than another without |
| // specifying an earlier deadline for the higher priority task. We can't specify |
| // deadlines at runtime, so we'll treat their priorities all the same. |
| SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => REALTIME_ROLE_NAME, |
| } |
| } |
| |
| // TODO: https://fxbug.dev/425726327 - better understand what are Binder's requirements when |
| // comparing one scheduling with another. |
| pub fn is_less_than_for_binder(&self, other: Self) -> bool { |
| match self.policy { |
| SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => match other.policy { |
| SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => { |
| self.realtime_priority < other.realtime_priority |
| } |
| SchedulingPolicy::Normal | SchedulingPolicy::Batch | SchedulingPolicy::Idle => { |
| false |
| } |
| }, |
| SchedulingPolicy::Normal => match other.policy { |
| SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => true, |
| SchedulingPolicy::Normal => { |
| self.normal_priority.value < other.normal_priority.value |
| } |
| SchedulingPolicy::Batch | SchedulingPolicy::Idle => false, |
| }, |
| SchedulingPolicy::Batch => match other.policy { |
| SchedulingPolicy::Fifo |
| | SchedulingPolicy::RoundRobin |
| | SchedulingPolicy::Normal => true, |
| SchedulingPolicy::Batch => self.normal_priority.value < other.normal_priority.value, |
| SchedulingPolicy::Idle => false, |
| }, |
| // see "the [...] nice value has no influence for [the SCHED_IDLE] policy" at sched(7). |
| SchedulingPolicy::Idle => match other.policy { |
| SchedulingPolicy::Fifo |
| | SchedulingPolicy::RoundRobin |
| | SchedulingPolicy::Normal |
| | SchedulingPolicy::Batch => true, |
| SchedulingPolicy::Idle => false, |
| }, |
| } |
| } |
| } |
| |
| impl std::default::Default for SchedulerState { |
| fn default() -> Self { |
| Self { |
| policy: SchedulingPolicy::Normal, |
| normal_priority: NormalPriority::default(), |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: false, |
| } |
| } |
| } |
| |
| pub fn min_priority_for_sched_policy(policy: u32) -> Result<u8, Errno> { |
| Ok(match policy { |
| SCHED_DEADLINE => RealtimePriority::NON_REAL_TIME_VALUE, |
| _ => SchedulingPolicy::try_from(policy)?.realtime_priority_min(), |
| }) |
| } |
| |
| pub fn max_priority_for_sched_policy(policy: u32) -> Result<u8, Errno> { |
| Ok(match policy { |
| SCHED_DEADLINE => RealtimePriority::NON_REAL_TIME_VALUE, |
| _ => SchedulingPolicy::try_from(policy)?.realtime_priority_max(), |
| }) |
| } |
| |
| /// Names of RoleManager roles for each static Zircon priority in the fair scheduler. |
| /// The index in the array is equal to the static priority. |
| // LINT.IfChange |
| const FAIR_PRIORITY_ROLE_NAMES: [&str; 32] = [ |
| "fuchsia.starnix.fair.0", |
| "fuchsia.starnix.fair.1", |
| "fuchsia.starnix.fair.2", |
| "fuchsia.starnix.fair.3", |
| "fuchsia.starnix.fair.4", |
| "fuchsia.starnix.fair.5", |
| "fuchsia.starnix.fair.6", |
| "fuchsia.starnix.fair.7", |
| "fuchsia.starnix.fair.8", |
| "fuchsia.starnix.fair.9", |
| "fuchsia.starnix.fair.10", |
| "fuchsia.starnix.fair.11", |
| "fuchsia.starnix.fair.12", |
| "fuchsia.starnix.fair.13", |
| "fuchsia.starnix.fair.14", |
| "fuchsia.starnix.fair.15", |
| "fuchsia.starnix.fair.16", |
| "fuchsia.starnix.fair.17", |
| "fuchsia.starnix.fair.18", |
| "fuchsia.starnix.fair.19", |
| "fuchsia.starnix.fair.20", |
| "fuchsia.starnix.fair.21", |
| "fuchsia.starnix.fair.22", |
| "fuchsia.starnix.fair.23", |
| "fuchsia.starnix.fair.24", |
| "fuchsia.starnix.fair.25", |
| "fuchsia.starnix.fair.26", |
| "fuchsia.starnix.fair.27", |
| "fuchsia.starnix.fair.28", |
| "fuchsia.starnix.fair.29", |
| "fuchsia.starnix.fair.30", |
| "fuchsia.starnix.fair.31", |
| ]; |
| const REALTIME_ROLE_NAME: &str = "fuchsia.starnix.realtime"; |
| // LINT.ThenChange(src/starnix/config/starnix.profiles) |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use assert_matches::assert_matches; |
| |
| #[fuchsia::test] |
| fn default_role_name() { |
| assert_eq!(SchedulerState::default().role_name(), "fuchsia.starnix.fair.16"); |
| } |
| |
| #[fuchsia::test] |
| fn normal_with_non_default_nice_role_name() { |
| assert_eq!( |
| SchedulerState { |
| policy: SchedulingPolicy::Normal, |
| normal_priority: NormalPriority { value: 10 }, |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: false |
| } |
| .role_name(), |
| "fuchsia.starnix.fair.11" |
| ); |
| assert_eq!( |
| SchedulerState { |
| policy: SchedulingPolicy::Normal, |
| normal_priority: NormalPriority { value: 27 }, |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: false |
| } |
| .role_name(), |
| "fuchsia.starnix.fair.19" |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn fifo_role_name() { |
| assert_eq!( |
| SchedulerState { |
| policy: SchedulingPolicy::Fifo, |
| normal_priority: NormalPriority::default(), |
| realtime_priority: RealtimePriority { value: 1 }, |
| reset_on_fork: false |
| } |
| .role_name(), |
| "fuchsia.starnix.realtime", |
| ); |
| assert_eq!( |
| SchedulerState { |
| policy: SchedulingPolicy::Fifo, |
| normal_priority: NormalPriority::default(), |
| realtime_priority: RealtimePriority { value: 2 }, |
| reset_on_fork: false |
| } |
| .role_name(), |
| "fuchsia.starnix.realtime", |
| ); |
| assert_eq!( |
| SchedulerState { |
| policy: SchedulingPolicy::Fifo, |
| normal_priority: NormalPriority::default(), |
| realtime_priority: RealtimePriority { value: 99 }, |
| reset_on_fork: false |
| } |
| .role_name(), |
| "fuchsia.starnix.realtime", |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn idle_role_name() { |
| assert_eq!( |
| SchedulerState { |
| policy: SchedulingPolicy::Idle, |
| normal_priority: NormalPriority { value: 1 }, |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: false, |
| } |
| .role_name(), |
| "fuchsia.starnix.fair.0" |
| ); |
| assert_eq!( |
| SchedulerState { |
| policy: SchedulingPolicy::Idle, |
| normal_priority: NormalPriority::default(), |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: false, |
| } |
| .role_name(), |
| "fuchsia.starnix.fair.0" |
| ); |
| assert_eq!( |
| SchedulerState { |
| policy: SchedulingPolicy::Idle, |
| normal_priority: NormalPriority { value: 40 }, |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: false, |
| } |
| .role_name(), |
| "fuchsia.starnix.fair.0" |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn build_policy_from_binder() { |
| assert_matches!(SchedulerState::from_binder(SCHED_NORMAL as u8, 0), Ok(_)); |
| assert_matches!( |
| SchedulerState::from_binder(SCHED_NORMAL as u8, ((-21) as i8) as u8), |
| Err(_) |
| ); |
| assert_matches!( |
| SchedulerState::from_binder(SCHED_NORMAL as u8, ((-20) as i8) as u8), |
| Ok(SchedulerState { |
| policy: SchedulingPolicy::Normal, |
| normal_priority: NormalPriority { value: 40 }, |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: false, |
| }) |
| ); |
| assert_matches!(SchedulerState::from_binder(SCHED_NORMAL as u8, 1), Ok(_)); |
| assert_matches!(SchedulerState::from_binder(SCHED_NORMAL as u8, 19), Ok(_)); |
| assert_matches!(SchedulerState::from_binder(SCHED_NORMAL as u8, 20), Err(_)); |
| assert_matches!(SchedulerState::from_binder(SCHED_FIFO as u8, 0), Err(_)); |
| assert_matches!(SchedulerState::from_binder(SCHED_FIFO as u8, 1), Ok(_)); |
| assert_matches!(SchedulerState::from_binder(SCHED_FIFO as u8, 99), Ok(_)); |
| assert_matches!(SchedulerState::from_binder(SCHED_FIFO as u8, 100), Err(_)); |
| assert_matches!(SchedulerState::from_binder(SCHED_RR as u8, 0), Err(_)); |
| assert_matches!(SchedulerState::from_binder(SCHED_RR as u8, 1), Ok(_)); |
| assert_matches!(SchedulerState::from_binder(SCHED_RR as u8, 99), Ok(_)); |
| assert_matches!(SchedulerState::from_binder(SCHED_RR as u8, 100), Err(_)); |
| assert_matches!(SchedulerState::from_binder(SCHED_BATCH as u8, 11), Ok(_)); |
| assert_eq!(SchedulerState::from_binder(SCHED_IDLE as u8, 11), error!(EINVAL)); |
| assert_matches!(SchedulerState::from_binder(42, 0), Err(_)); |
| assert_matches!(SchedulerState::from_binder(42, 0), Err(_)); |
| } |
| |
| // NOTE(https://fxbug.dev/425726327): some or all of this test may need to change based |
| // on what is learned in https://fxbug.dev/425726327. |
| #[fuchsia::test] |
| fn is_less_than_for_binder() { |
| let rr_50 = SchedulerState { |
| policy: SchedulingPolicy::RoundRobin, |
| normal_priority: NormalPriority { value: 1 }, |
| realtime_priority: RealtimePriority { value: 50 }, |
| reset_on_fork: false, |
| }; |
| let rr_40 = SchedulerState { |
| policy: SchedulingPolicy::RoundRobin, |
| normal_priority: NormalPriority { value: 1 }, |
| realtime_priority: RealtimePriority { value: 40 }, |
| reset_on_fork: false, |
| }; |
| let fifo_50 = SchedulerState { |
| policy: SchedulingPolicy::Fifo, |
| normal_priority: NormalPriority { value: 1 }, |
| realtime_priority: RealtimePriority { value: 50 }, |
| reset_on_fork: false, |
| }; |
| let fifo_40 = SchedulerState { |
| policy: SchedulingPolicy::Fifo, |
| normal_priority: NormalPriority { value: 1 }, |
| realtime_priority: RealtimePriority { value: 40 }, |
| reset_on_fork: false, |
| }; |
| let normal_40 = SchedulerState { |
| policy: SchedulingPolicy::Normal, |
| normal_priority: NormalPriority { value: 40 }, |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: true, |
| }; |
| let normal_10 = SchedulerState { |
| policy: SchedulingPolicy::Normal, |
| normal_priority: NormalPriority { value: 10 }, |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: true, |
| }; |
| let batch_40 = SchedulerState { |
| policy: SchedulingPolicy::Batch, |
| normal_priority: NormalPriority { value: 40 }, |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: true, |
| }; |
| let batch_30 = SchedulerState { |
| policy: SchedulingPolicy::Batch, |
| normal_priority: NormalPriority { value: 30 }, |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: true, |
| }; |
| let idle_40 = SchedulerState { |
| policy: SchedulingPolicy::Idle, |
| normal_priority: NormalPriority { value: 40 }, |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: true, |
| }; |
| let idle_30 = SchedulerState { |
| policy: SchedulingPolicy::Idle, |
| normal_priority: NormalPriority { value: 30 }, |
| realtime_priority: RealtimePriority::NON_REAL_TIME, |
| reset_on_fork: true, |
| }; |
| assert!(!fifo_50.is_less_than_for_binder(fifo_50)); |
| assert!(!rr_50.is_less_than_for_binder(rr_50)); |
| assert!(!fifo_50.is_less_than_for_binder(rr_50)); |
| assert!(!rr_50.is_less_than_for_binder(fifo_50)); |
| assert!(!fifo_50.is_less_than_for_binder(rr_40)); |
| assert!(rr_40.is_less_than_for_binder(fifo_50)); |
| assert!(!rr_50.is_less_than_for_binder(fifo_40)); |
| assert!(fifo_40.is_less_than_for_binder(rr_50)); |
| assert!(!fifo_40.is_less_than_for_binder(normal_40)); |
| assert!(normal_40.is_less_than_for_binder(fifo_40)); |
| assert!(!rr_40.is_less_than_for_binder(normal_40)); |
| assert!(normal_40.is_less_than_for_binder(rr_40)); |
| assert!(!normal_40.is_less_than_for_binder(normal_40)); |
| assert!(!normal_40.is_less_than_for_binder(normal_10)); |
| assert!(normal_10.is_less_than_for_binder(normal_40)); |
| assert!(!normal_10.is_less_than_for_binder(batch_40)); |
| assert!(batch_40.is_less_than_for_binder(normal_10)); |
| assert!(!batch_40.is_less_than_for_binder(batch_40)); |
| assert!(!batch_40.is_less_than_for_binder(batch_30)); |
| assert!(batch_30.is_less_than_for_binder(batch_40)); |
| assert!(!batch_30.is_less_than_for_binder(idle_40)); |
| assert!(idle_40.is_less_than_for_binder(batch_30)); |
| assert!(!idle_40.is_less_than_for_binder(idle_40)); |
| assert!(!idle_40.is_less_than_for_binder(idle_30)); |
| assert!(!idle_30.is_less_than_for_binder(idle_40)); |
| } |
| |
| #[fuchsia::test] |
| async fn role_overrides_non_realtime() { |
| crate::testing::spawn_kernel_and_run_sync(|_locked, current_task| { |
| let mut builder = RoleOverrides::new(); |
| builder.add("my_task", "my_task", "overridden_role"); |
| let overrides = builder.build().unwrap(); |
| let manager = SchedulerManager { role_manager: None, role_overrides: overrides }; |
| |
| current_task.set_command_name(starnix_task_command::TaskCommand::new(b"my_task")); |
| |
| let mut state = SchedulerState::default(); |
| state.policy = SchedulingPolicy::Normal; |
| |
| let role = manager.role_name_inner(current_task, state).expect("role_name"); |
| assert_eq!(role, "overridden_role"); |
| }) |
| .await; |
| } |
| } |