Skip to content

Add support for linux rt schedule functions #2640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions changelog/2640.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added new api methods for working with PREEMPT_RT thread priorities.
162 changes: 162 additions & 0 deletions src/sched.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SchedParam> 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<libc::sched_param> 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_NORMAL.
SCHED_OTHER,
/// 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<libc::c_int>
}

/// Get the highest priority value for a given scheduler.
pub fn sched_get_priority_max(sched: Scheduler) -> Result<libc::c_int> {
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<libc::c_int> {
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<Scheduler> {
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, &param)
};

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<SchedParam> {
let mut param: MaybeUninit<libc::sched_param> = 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(), &param) };

Errno::result(res).map(drop)
}
}

/// Explicitly yield the processor to other threads.
///
/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_yield.html)
Expand Down
42 changes: 42 additions & 0 deletions test/test_sched.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_OTHER);

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_OTHER
);
}
Err(e) => {
panic!("unexpected error: {}", e);
}
}
}
Loading