preempt_rt/
sched.rs

1use crate::sched::PreemptRtError::{PriorityAboveMax, PriorityBelowMin};
2use libc::{c_int, pid_t};
3use std::mem::MaybeUninit;
4use std::{fmt, result};
5use thiserror::Error;
6
7/// PreemptRt result type
8pub type Result<T> = result::Result<T, PreemptRtError>;
9
10#[derive(Debug, Error)]
11pub enum PreemptRtError {
12    #[error("c function returned errno: {0}")]
13    Errno(c_int),
14    #[error("unknown scheduler for value {0}")]
15    UnknownScheduler(c_int),
16    #[error("priority {0} is higher than max priority {1}")]
17    PriorityAboveMax(c_int, c_int),
18    #[error("priority {0} is lower than min priority {1}")]
19    PriorityBelowMin(c_int, c_int),
20}
21
22fn handle_errno(result: c_int) -> Result<c_int> {
23    if result == -1 {
24        Err(PreemptRtError::Errno(unsafe { *libc::__errno_location() }))
25    } else {
26        Ok(result)
27    }
28}
29
30#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
31pub struct Pid(pid_t);
32
33impl Pid {
34    pub const fn current_thread() -> Self {
35        Pid(0)
36    }
37}
38
39impl From<Pid> for pid_t {
40    fn from(pid: Pid) -> Self {
41        pid.0
42    }
43}
44
45impl fmt::Display for Pid {
46    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47        fmt::Display::fmt(&self.0, f)
48    }
49}
50
51#[repr(i32)]
52#[allow(non_camel_case_types)] // intentionally matching with libc / linux docs
53#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
54/// The type of scheduler for use with [`sched_getscheduler`] and [`sched_setscheduler`].
55/// See [man_sched(7)](https://man7.org/linux/man-pages/man7/sched.7.html) for more details
56/// on the differences in behavior.
57pub enum Scheduler {
58    /// The default scheduler on non-realtime linux - also known as SCHED_OTHER.
59    SCHED_NORMAL = libc::SCHED_NORMAL,
60    /// The realtime FIFO scheduler. All FIFO threads have priority higher than 0 and
61    /// preempt SCHED_NORMAL threads. Threads are executed in priority order, using
62    /// first-in-first-out lists to handle two threads with the same priority.
63    SCHED_FIFO = libc::SCHED_FIFO,
64    /// Round-robin scheduler, similar to SCHED_FIFO but with a time quantum.
65    SCHED_RR = libc::SCHED_RR,
66    /// Batch scheduler, similar to SCHED_OTHER but assumes the thread is CPU intensive.
67    /// The kernel applies a mild penalty to switching to this thread.
68    /// As of Linux 2.6.16, the only valid priority is 0.
69    SCHED_BATCH = libc::SCHED_BATCH,
70    /// The idle scheduler only executes the thread when there are idle CPUs. SCHED_IDLE
71    /// threads have no progress guarantees.
72    SCHED_IDLE = libc::SCHED_IDLE,
73    /// Deadline scheduler, attempting to provide guaranteed latency for requests.
74    /// See the [linux kernel docs](https://docs.kernel.org/scheduler/sched-deadline.html)
75    /// for details.
76    SCHED_DEADLINE = libc::SCHED_DEADLINE,
77}
78
79impl TryFrom<c_int> for Scheduler {
80    type Error = PreemptRtError;
81
82    fn try_from(value: c_int) -> Result<Self> {
83        match value {
84            libc::SCHED_NORMAL => Ok(Scheduler::SCHED_NORMAL),
85            libc::SCHED_FIFO => Ok(Scheduler::SCHED_FIFO),
86            libc::SCHED_RR => Ok(Scheduler::SCHED_RR),
87            libc::SCHED_BATCH => Ok(Scheduler::SCHED_BATCH),
88            libc::SCHED_IDLE => Ok(Scheduler::SCHED_IDLE),
89            libc::SCHED_DEADLINE => Ok(Scheduler::SCHED_DEADLINE),
90            _ => Err(PreemptRtError::UnknownScheduler(value)),
91        }
92    }
93}
94
95impl Scheduler {
96    /// Get the highest priority value for a given scheduler.
97    pub fn priority_max(&self) -> Result<c_int> {
98        let res = unsafe { libc::sched_get_priority_max(*self as c_int) };
99        handle_errno(res)
100    }
101
102    /// Get the lowest priority value for a given scheduler.
103    pub fn priority_min(&self) -> Result<c_int> {
104        let res = unsafe { libc::sched_get_priority_min(*self as c_int) };
105        handle_errno(res)
106    }
107
108    pub fn with_priority(self, priority: c_int) -> Result<ParameterizedScheduler> {
109        let max = self.priority_max()?;
110        let min = self.priority_min()?;
111        if priority > max {
112            Err(PriorityAboveMax(priority, max).into())
113        } else if priority < min {
114            Err(PriorityBelowMin(priority, min).into())
115        } else {
116            Ok(ParameterizedScheduler {
117                scheduler: self,
118                params: SchedulerParams { priority },
119            })
120        }
121    }
122}
123
124#[repr(C)]
125#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
126/// Schedule parameters for a thread. Priority is the only supported parameter by the kernel
127/// at the moment. This is a wrapper around `libc::sched_param`
128pub struct SchedulerParams {
129    /// Priority of the current schedule.
130    pub priority: c_int,
131}
132
133impl From<SchedulerParams> for libc::sched_param {
134    #[cfg(not(any(target_env = "musl", target_env = "ohos")))]
135    fn from(param: SchedulerParams) -> Self {
136        libc::sched_param {
137            sched_priority: param.priority,
138        }
139    }
140
141    #[cfg(any(target_env = "musl", target_env = "ohos"))]
142    fn from(param: SchedulerParams) -> Self {
143        let ts_zero = libc::timespec {
144            tv_sec: 0,
145            tv_nsec: 0,
146        };
147        // musl and ohos have additional fields for SCHED_DEADLINE - this is not abstracted
148        // in this library yet.
149        libc::sched_param {
150            sched_priority: param.priority,
151            sched_ss_init_budget: ts_zero.clone(),
152            sched_ss_low_priority: 0,
153            sched_ss_repl_period: ts_zero.clone(),
154            sched_ss_max_repl: 0,
155        }
156    }
157}
158
159impl From<libc::sched_param> for SchedulerParams {
160    fn from(param: libc::sched_param) -> Self {
161        SchedulerParams {
162            priority: param.sched_priority,
163        }
164    }
165}
166
167pub trait IntoSchedParam {
168    fn into_sched_param(self) -> SchedulerParams;
169}
170
171impl IntoSchedParam for i32 {
172    fn into_sched_param(self) -> SchedulerParams {
173        SchedulerParams {
174            priority: self as c_int,
175        }
176    }
177}
178
179impl<T: IntoSchedParam> IntoSchedParam for Option<T> {
180    fn into_sched_param(self) -> SchedulerParams {
181        match self {
182            None => SchedulerParams { priority: 0 },
183            Some(param) => param.into_sched_param(),
184        }
185    }
186}
187
188#[derive(Debug, Clone)]
189pub struct ParameterizedScheduler {
190    scheduler: Scheduler,
191    params: SchedulerParams,
192}
193
194impl ParameterizedScheduler {
195    pub fn set_on(self, pid: Pid) -> Result<()> {
196        set_scheduler(pid, self.scheduler, self.params)
197    }
198
199    pub fn set_current(self) -> Result<()> {
200        self.set_on(Pid::current_thread())
201    }
202}
203
204/// Get the current scheduler in use for a given process or thread.
205/// Using `Pid::from_raw(0)` will fetch the scheduler for the calling thread.
206pub fn get_scheduler(pid: Pid) -> Result<Scheduler> {
207    let res = unsafe { libc::sched_getscheduler(pid.into()) };
208    handle_errno(res).and_then(Scheduler::try_from)
209}
210
211/// Set the scheduler and parameters for a given process or thread.
212/// Using `Pid::from_raw(0)` will set the scheduler for the calling thread.
213///
214/// SCHED_OTHER, SCHED_IDLE and SCHED_BATCH only support a priority of `0`, and can be used
215/// outside a Linux PREEMPT_RT context.
216///
217/// SCHED_FIFO and SCHED_RR allow priorities between the min and max inclusive.
218///
219/// SCHED_DEADLINE cannot be set with this function, `libc::sched_setattr` must be used instead.
220pub fn set_scheduler(pid: Pid, scheduler: Scheduler, param: SchedulerParams) -> Result<()> {
221    let param: libc::sched_param = param.into();
222    let res = unsafe { libc::sched_setscheduler(pid.into(), scheduler as c_int, &param) };
223
224    handle_errno(res).map(drop)
225}
226
227/// Get the schedule parameters (currently only priority) for a given thread.
228/// Using `Pid::from_raw(0)` will return the parameters for the calling thread.
229pub fn get_scheduler_params(pid: Pid) -> Result<SchedulerParams> {
230    let mut param: MaybeUninit<libc::sched_param> = MaybeUninit::uninit();
231    let res = unsafe { libc::sched_getparam(pid.into(), param.as_mut_ptr()) };
232
233    handle_errno(res).map(|_| unsafe { param.assume_init() }.into())
234}
235/// Set the schedule parameters (currently only priority) for a given thread.
236/// Using `Pid::from_raw(0)` will return the parameters for the calling thread.
237///
238/// Changing the priority to something other than `0` requires using a SCHED_FIFO or SCHED_RR
239/// and using a Linux kernel with PREEMPT_RT enabled.
240pub fn set_scheduler_params(pid: Pid, param: SchedulerParams) -> Result<()> {
241    let param: libc::sched_param = param.into();
242    let res = unsafe { libc::sched_setparam(pid.into(), &param) };
243    handle_errno(res).map(drop)
244}