diff --git a/changelog/2640.added.md b/changelog/2640.added.md new file mode 100644 index 0000000000..0c2c3957ec --- /dev/null +++ b/changelog/2640.added.md @@ -0,0 +1 @@ +Added new api methods for working with PREEMPT_RT thread priorities. \ No newline at end of file diff --git a/src/sched.rs b/src/sched.rs index 617d00493f..cb5392e1ab 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -316,6 +316,168 @@ mod sched_affinity { } } +// musl has additional sched_param fields that we don't support yet +#[cfg(all( + linux_android, + not(target_env = "musl"), + not(target_env = "ohos") +))] +pub use self::sched_priority::*; + +#[cfg(all(linux_android, not(target_env = "musl"), not(target_env = "ohos")))] +mod sched_priority { + use std::mem::MaybeUninit; + + use crate::errno::Errno; + use crate::unistd::Pid; + use crate::Result; + use libc; + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + /// Schedule parameters for a thread (currently only priority is supported). + /// This is a wrapper around `libc::sched_param` + pub struct SchedParam { + /// Priority of the current schedule. + pub sched_priority: libc::c_int, + } + + impl SchedParam { + /// Create schedule parameters with a given priority. Priority must be between + /// min and max for the chosen schedule type. + pub fn from_priority(priority: libc::c_int) -> Self { + SchedParam { + sched_priority: priority, + } + } + } + + impl From for libc::sched_param { + fn from(param: SchedParam) -> Self { + libc::sched_param { + sched_priority: param.sched_priority, + } + } + + // #[cfg(any(target_env = "musl", target_env = "ohos"))] + // fn from(param: SchedParam) -> Self { + // // note: non-priority values are dummy values used by deadline scheduler, + // // don't use this abstraction if you're using deadline. + // let zero_ts = libc::timespec { + // tv_sec: 0, + // tv_nsec: 0, + // }; + // libc::sched_param { + // sched_priority: param.sched_priority, + // sched_ss_low_priority: 0, + // sched_ss_repl_period: zero_ts.clone(), + // sched_ss_init_budget: zero_ts, + // sched_ss_max_repl: 0, + // } + // } + } + impl From for SchedParam { + fn from(param: libc::sched_param) -> Self { + SchedParam { + sched_priority: param.sched_priority, + } + } + } + + libc_enum! { + #[repr(i32)] + /// The type of scheduler for use with [`sched_getscheduler`] and [`sched_setscheduler`]. + /// See [man_sched(7)](https://man7.org/linux/man-pages/man7/sched.7.html) for more details + /// on the differences in behavior. + pub enum Scheduler { + /// The default scheduler on non-realtime linux - also known as SCHED_OTHER. + SCHED_NORMAL, + /// The realtime FIFO scheduler. All FIFO threads have priority higher than 0 and + /// preempt SCHED_OTHER threads. Threads are executed in priority order, using + /// first-in-first-out lists to handle two threads with the same priority. + SCHED_FIFO, + /// Round-robin scheduler + SCHED_RR, + /// Batch scheduler, similar to SCHED_OTHER but assumes the thread is CPU intensive. + /// The kernel applies a mild penalty to switching to this thread. + /// As of Linux 2.6.16, the only valid priority is 0. + SCHED_BATCH, + /// The idle scheduler only executes the thread when there are idle CPUs. SCHED_IDLE + /// threads have no progress guarantees. + SCHED_IDLE, + /// Deadline scheduler, attempting to provide guaranteed latency for requests. + /// See the [linux kernel docs](https://docs.kernel.org/scheduler/sched-deadline.html) + /// for details. + SCHED_DEADLINE, + } + impl TryFrom + } + + /// Get the highest priority value for a given scheduler. + pub fn sched_get_priority_max(sched: Scheduler) -> Result { + let res = unsafe { libc::sched_get_priority_max(sched as libc::c_int) }; + Errno::result(res).map(|int| int as libc::c_int) + } + + /// Get the lowest priority value for a given scheduler. + pub fn sched_get_priority_min(sched: Scheduler) -> Result { + let res = unsafe { libc::sched_get_priority_min(sched as libc::c_int) }; + Errno::result(res).map(|int| int as libc::c_int) + } + + /// Get the current scheduler in use for a given process or thread. + /// Using `Pid::from_raw(0)` will fetch the scheduler for the calling thread. + pub fn sched_getscheduler(pid: Pid) -> Result { + let res = unsafe { libc::sched_getscheduler(pid.into()) }; + + Errno::result(res).and_then(Scheduler::try_from) + } + + /// Set the scheduler and parameters for a given process or thread. + /// Using `Pid::from_raw(0)` will set the scheduler for the calling thread. + /// + /// SCHED_OTHER, SCHED_IDLE and SCHED_BATCH only support a priority of `0`, and can be used + /// outside a Linux PREEMPT_RT context. + /// + /// SCHED_FIFO and SCHED_RR allow priorities between the min and max inclusive. + /// + /// SCHED_DEADLINE cannot be set with this function, libc::sched_setattr must be used instead. + pub fn sched_setscheduler( + pid: Pid, + sched: Scheduler, + param: SchedParam, + ) -> Result<()> { + let param: libc::sched_param = param.into(); + let res = unsafe { + libc::sched_setscheduler(pid.into(), sched as libc::c_int, ¶m) + }; + + Errno::result(res).map(drop) + } + + /// Get the schedule parameters (currently only priority) for a given thread. + /// Using `Pid::from_raw(0)` will return the parameters for the calling thread. + pub fn sched_getparam(pid: Pid) -> Result { + let mut param: MaybeUninit = MaybeUninit::uninit(); + let res = + unsafe { libc::sched_getparam(pid.into(), param.as_mut_ptr()) }; + + Errno::result(res).map(|_| unsafe { param.assume_init() }.into()) + } + + /// Set the schedule parameters (currently only priority) for a given thread. + /// Using `Pid::from_raw(0)` will return the parameters for the calling thread. + /// + /// Changing the priority to something other than `0` requires using a SCHED_FIFO or SCHED_RR + /// and using a Linux kernel with PREEMPT_RT enabled. + pub fn sched_setparam(pid: Pid, param: SchedParam) -> Result<()> { + let param: libc::sched_param = param.into(); + let res = unsafe { libc::sched_setparam(pid.into(), ¶m) }; + + Errno::result(res).map(drop) + } +} + /// Explicitly yield the processor to other threads. /// /// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_yield.html) diff --git a/test/test_sched.rs b/test/test_sched.rs index c52616b8bb..a41f6b9da3 100644 --- a/test/test_sched.rs +++ b/test/test_sched.rs @@ -37,3 +37,45 @@ fn test_sched_affinity() { // Finally, reset the initial CPU set sched_setaffinity(Pid::from_raw(0), &initial_affinity).unwrap(); } + +#[cfg(all(linux_android, not(target_env = "musl"), not(target_env = "ohos")))] +#[test] +fn test_sched_priority() { + use nix::sched::{ + sched_get_priority_max, sched_get_priority_min, sched_getparam, + sched_getscheduler, sched_setscheduler, SchedParam, Scheduler, + }; + + let pid = Pid::from_raw(0); + let sched = sched_getscheduler(pid).unwrap(); + // default is NORMAL aka OTHER + assert_eq!(sched, Scheduler::SCHED_NORMAL); + + let priority = sched_getparam(pid).unwrap().sched_priority; + assert_eq!(priority, 0); + + let max = sched_get_priority_max(Scheduler::SCHED_FIFO).unwrap(); + let _ = sched_get_priority_min(Scheduler::SCHED_FIFO).unwrap(); + + // can't set priority unless process has correct capabilities and PREEMPT_RT kernel + match sched_setscheduler( + pid, + Scheduler::SCHED_FIFO, + SchedParam::from_priority(max), + ) { + Ok(_) => { + assert_eq!(sched_getscheduler(pid).unwrap(), Scheduler::SCHED_FIFO); + assert_eq!(sched_getparam(pid).unwrap().sched_priority, max); + } + Err(nix::errno::Errno::EPERM) => { + // expected, assert that it didn't change + assert_eq!( + sched_getscheduler(pid).unwrap(), + Scheduler::SCHED_NORMAL + ); + } + Err(e) => { + panic!("unexpected error: {}", e); + } + } +}