Skip to main content

preempt_rt/
sched.rs

1use crate::sched::PreemptRtError::{PriorityAboveMax, PriorityBelowMin};
2use libc::c_int;
3use thiserror::Error;
4
5/// PreemptRt result type
6pub type RtResult<T> = Result<T, PreemptRtError>;
7
8#[derive(Debug, Error)]
9/// PreemptRt error type. When libc functions return -1, errno will be fetched via libc.
10pub enum PreemptRtError {
11    #[error("c function returned errno: {0}")]
12    Errno(c_int),
13    #[error("unknown scheduler for value {0}")]
14    UnknownScheduler(c_int),
15    #[error("priority {0} is higher than max priority {1}")]
16    PriorityAboveMax(c_int, c_int),
17    #[error("priority {0} is lower than min priority {1}")]
18    PriorityBelowMin(c_int, c_int),
19    #[error("current platform {0} does not support preempt-rt")]
20    NonLinuxPlatform(&'static str),
21}
22
23#[cfg(not(target_os = "windows"))]
24#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
25/// Wrapper around pid_t.
26pub struct Pid(libc::pid_t);
27
28#[cfg(target_os = "windows")]
29#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
30/// Dummy pid_t wrapper for windows.
31pub struct Pid(i32);
32
33impl Pid {
34    /// The current thread is represented by `0` when using sched_* functions.
35    /// Note that this is different from the value of calling `getpid()`, which returns the
36    /// actual pid for the current _process_.
37    pub const fn current_thread() -> Self {
38        Pid(0)
39    }
40}
41
42#[cfg(not(target_os = "windows"))]
43impl From<Pid> for libc::pid_t {
44    fn from(pid: Pid) -> Self {
45        pid.0
46    }
47}
48
49#[cfg(not(target_os = "windows"))]
50impl std::fmt::Display for Pid {
51    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
52        std::fmt::Display::fmt(&self.0, f)
53    }
54}
55
56#[repr(i32)]
57#[allow(non_camel_case_types)] // intentionally matching with libc / linux docs
58#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
59/// The type of scheduler for use with [`sched_getscheduler`](https://man7.org/linux/man-pages/man3/sched_getscheduler.3p.html)
60/// and [`sched_setscheduler`](https://man7.org/linux/man-pages/man2/sched_setscheduler.2.html).
61/// See [man_sched(7)](https://man7.org/linux/man-pages/man7/sched.7.html) for more details
62/// on the differences in behavior.
63///
64/// This type is a wrapper around the libc::SCHED_* enum.
65pub enum Scheduler {
66    #[cfg(target_os = "windows")]
67    SCHED_WINDOWS = 0,
68    /// The default scheduler on non-realtime linux - also known as SCHED_OTHER.
69    #[cfg(target_os = "linux")]
70    SCHED_NORMAL = libc::SCHED_NORMAL,
71    /// The default scheduler on non-realtime linux - also known as SCHED_OTHER.
72    #[cfg(target_os = "macos")]
73    SCHED_NORMAL = libc::SCHED_OTHER,
74    /// The realtime FIFO scheduler. All FIFO threads have priority higher than 0 and
75    /// preempt SCHED_NORMAL threads. Threads are executed in priority order, using
76    /// first-in-first-out lists to handle two threads with the same priority.
77    #[cfg(not(target_os = "windows"))]
78    SCHED_FIFO = libc::SCHED_FIFO,
79    #[cfg(target_os = "windows")]
80    SCHED_FIFO = 1,
81    /// Round-robin scheduler, similar to SCHED_FIFO but with a time quantum.
82    #[cfg(not(target_os = "windows"))]
83    SCHED_RR = libc::SCHED_RR,
84    /// Batch scheduler, similar to SCHED_OTHER but assumes the thread is CPU intensive.
85    /// The kernel applies a mild penalty to switching to this thread.
86    /// As of Linux 2.6.16, the only valid priority is 0.
87    #[cfg(target_os = "linux")]
88    SCHED_BATCH = libc::SCHED_BATCH,
89    /// The idle scheduler only executes the thread when there are idle CPUs. SCHED_IDLE
90    /// threads have no progress guarantees.
91    #[cfg(target_os = "linux")]
92    SCHED_IDLE = libc::SCHED_IDLE,
93    /// Deadline scheduler, attempting to provide guaranteed latency for requests.
94    /// See the [linux kernel docs](https://docs.kernel.org/scheduler/sched-deadline.html)
95    /// for details.
96    #[cfg(target_os = "linux")]
97    SCHED_DEADLINE = libc::SCHED_DEADLINE,
98}
99
100impl TryFrom<c_int> for Scheduler {
101    type Error = PreemptRtError;
102
103    fn try_from(value: c_int) -> RtResult<Self> {
104        match value {
105            #[cfg(target_os = "linux")]
106            libc::SCHED_NORMAL => Ok(Scheduler::SCHED_NORMAL),
107            #[cfg(target_os = "macos")]
108            libc::SCHED_OTHER => Ok(Scheduler::SCHED_NORMAL),
109            #[cfg(any(target_os = "linux", target_os = "macos"))]
110            libc::SCHED_FIFO => Ok(Scheduler::SCHED_FIFO),
111            #[cfg(any(target_os = "linux", target_os = "macos"))]
112            libc::SCHED_RR => Ok(Scheduler::SCHED_RR),
113            #[cfg(target_os = "linux")]
114            libc::SCHED_BATCH => Ok(Scheduler::SCHED_BATCH),
115            #[cfg(target_os = "linux")]
116            libc::SCHED_IDLE => Ok(Scheduler::SCHED_IDLE),
117            #[cfg(target_os = "linux")]
118            libc::SCHED_DEADLINE => Ok(Scheduler::SCHED_DEADLINE),
119            _ => Err(PreemptRtError::UnknownScheduler(value)),
120        }
121    }
122}
123
124#[cfg(not(target_os = "windows"))]
125fn handle_errno(result: c_int) -> RtResult<c_int> {
126    if result == -1 {
127        #[cfg(target_os = "linux")]
128        return Err(PreemptRtError::Errno(unsafe { *libc::__errno_location() }));
129        #[cfg(target_os = "macos")]
130        return Err(PreemptRtError::Errno(unsafe { *libc::__error() }));
131    } else {
132        Ok(result)
133    }
134}
135
136#[cfg(not(target_os = "windows"))]
137impl Scheduler {
138    /// Get the highest priority value for a given scheduler.
139    pub fn priority_max(&self) -> RtResult<c_int> {
140        let res = unsafe { libc::sched_get_priority_max(*self as c_int) };
141        handle_errno(res)
142    }
143
144    /// Get the lowest priority value for a given scheduler.
145    pub fn priority_min(&self) -> RtResult<c_int> {
146        let res = unsafe { libc::sched_get_priority_min(*self as c_int) };
147        handle_errno(res)
148    }
149
150    /// Create a ParameterizedScheduler with the given priority.
151    pub fn with_params(self, params: SchedulerParams) -> ParameterizedScheduler {
152        ParameterizedScheduler {
153            scheduler: self,
154            params,
155        }
156    }
157}
158
159#[cfg(target_os = "windows")]
160impl Scheduler {
161    /// Get the highest priority value for a given scheduler.
162    ///
163    /// Returns error on windows.
164    pub fn priority_max(&self) -> RtResult<c_int> {
165        Err(PreemptRtError::NonLinuxPlatform("windows"))
166    }
167
168    /// Get the lowest priority value for a given scheduler.
169    ///
170    /// Returns error on windows.
171    pub fn priority_min(&self) -> RtResult<c_int> {
172        Err(PreemptRtError::NonLinuxPlatform("windows"))
173    }
174
175    /// Create a ParameterizedScheduler with the given priority.
176    ///
177    /// Returns 0 value on windows.
178    pub fn with_params(self, params: SchedulerParams) -> ParameterizedScheduler {
179        ParameterizedScheduler {
180            scheduler: Scheduler::SCHED_WINDOWS,
181            params,
182        }
183    }
184}
185
186#[repr(C)]
187#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
188/// Schedule parameters for a thread. Priority is the only supported parameter by the kernel
189/// at the moment. This is a wrapper around `libc::sched_param`
190pub struct SchedulerParams {
191    /// Priority of the current schedule.
192    pub priority: c_int,
193}
194
195#[cfg(target_os = "linux")]
196impl From<SchedulerParams> for libc::sched_param {
197    #[cfg(not(any(target_env = "musl", target_env = "ohos")))]
198    fn from(param: SchedulerParams) -> Self {
199        libc::sched_param {
200            sched_priority: param.priority,
201        }
202    }
203
204    #[cfg(any(target_env = "musl", target_env = "ohos"))]
205    fn from(param: SchedulerParams) -> Self {
206        let ts_zero = libc::timespec {
207            tv_sec: 0,
208            tv_nsec: 0,
209        };
210        // musl and ohos have additional fields for SCHED_DEADLINE - this is not abstracted
211        // in this library yet.
212        libc::sched_param {
213            sched_priority: param.priority,
214            sched_ss_init_budget: ts_zero.clone(),
215            sched_ss_low_priority: 0,
216            sched_ss_repl_period: ts_zero.clone(),
217            sched_ss_max_repl: 0,
218        }
219    }
220}
221
222#[cfg(any(target_os = "macos", target_os = "linux"))]
223impl From<libc::sched_param> for SchedulerParams {
224    fn from(param: libc::sched_param) -> Self {
225        SchedulerParams {
226            priority: param.sched_priority,
227        }
228    }
229}
230
231pub trait IntoSchedParams {
232    fn into_sched_params(self) -> SchedulerParams;
233}
234
235impl IntoSchedParams for SchedulerParams {
236    fn into_sched_params(self) -> SchedulerParams {
237        self
238    }
239}
240
241impl IntoSchedParams for i32 {
242    fn into_sched_params(self) -> SchedulerParams {
243        SchedulerParams {
244            priority: self as c_int,
245        }
246    }
247}
248
249impl<T: IntoSchedParams> IntoSchedParams for Option<T> {
250    fn into_sched_params(self) -> SchedulerParams {
251        match self {
252            None => SchedulerParams { priority: 0 },
253            Some(param) => param.into_sched_params(),
254        }
255    }
256}
257
258#[cfg_attr(any(target_os = "macos", target_os = "windows"), allow(unused))]
259#[derive(Debug, Clone)]
260pub struct ParameterizedScheduler {
261    scheduler: Scheduler,
262    params: SchedulerParams,
263}
264
265impl ParameterizedScheduler {
266    /// Apply this scheduler + params on the current thread, validating that its priority is
267    /// between the valid min & max values for the chosen scheduler.
268    #[cfg_attr(
269        any(target_os = "macos", target_os = "windows"),
270        allow(unused_variables)
271    )]
272    pub fn set_on(self, pid: Pid) -> RtResult<()> {
273        let priority = self.params.priority;
274        let max = self.scheduler.priority_max()?;
275        let min = self.scheduler.priority_min()?;
276        if priority > max {
277            Err(PriorityAboveMax(priority, max))
278        } else if priority < min {
279            Err(PriorityBelowMin(priority, min))
280        } else {
281            #[cfg(target_os = "linux")]
282            return set_scheduler(pid, self.scheduler, self.params);
283            #[cfg(target_os = "macos")]
284            return Err(PreemptRtError::NonLinuxPlatform("macos"));
285            #[cfg(target_os = "windows")]
286            return Err(PreemptRtError::NonLinuxPlatform("windows"));
287        }
288    }
289
290    pub fn set_current(self) -> RtResult<()> {
291        self.set_on(Pid::current_thread())
292    }
293}
294
295#[cfg(target_os = "linux")]
296mod linux {
297    use super::*;
298    use std::mem::MaybeUninit;
299
300    /// Get the current scheduler in use for a given process or thread.
301    /// Using `Pid::from_raw(0)` will fetch the scheduler for the calling thread.
302    pub fn get_scheduler(pid: Pid) -> RtResult<Scheduler> {
303        let res = unsafe { libc::sched_getscheduler(pid.into()) };
304        handle_errno(res).and_then(Scheduler::try_from)
305    }
306
307    /// Set the scheduler and parameters for a given process or thread.
308    /// Using `Pid::from_raw(0)` will set the scheduler for the calling thread.
309    ///
310    /// SCHED_OTHER, SCHED_IDLE and SCHED_BATCH only support a priority of `0`, and can be used
311    /// outside a Linux PREEMPT_RT context.
312    ///
313    /// SCHED_FIFO and SCHED_RR allow priorities between the min and max inclusive.
314    ///
315    /// SCHED_DEADLINE cannot be set with this function, `libc::sched_setattr` must be used instead.
316    pub fn set_scheduler(pid: Pid, scheduler: Scheduler, param: SchedulerParams) -> RtResult<()> {
317        let param: libc::sched_param = param.into();
318        let res = unsafe { libc::sched_setscheduler(pid.into(), scheduler as c_int, &param) };
319
320        handle_errno(res).map(drop)
321    }
322
323    /// Get the schedule parameters (currently only priority) for a given thread.
324    /// Using `Pid::from_raw(0)` will return the parameters for the calling thread.
325    pub fn get_scheduler_params(pid: Pid) -> RtResult<SchedulerParams> {
326        let mut param: MaybeUninit<libc::sched_param> = MaybeUninit::uninit();
327        let res = unsafe { libc::sched_getparam(pid.into(), param.as_mut_ptr()) };
328
329        handle_errno(res).map(|_| unsafe { param.assume_init() }.into())
330    }
331    /// Set the schedule parameters (currently only priority) for a given thread.
332    /// Using `Pid::from_raw(0)` will return the parameters for the calling thread.
333    ///
334    /// Changing the priority to something other than `0` requires using a SCHED_FIFO or SCHED_RR
335    /// and using a Linux kernel with PREEMPT_RT enabled.
336    pub fn set_scheduler_params(pid: Pid, param: SchedulerParams) -> RtResult<()> {
337        let param: libc::sched_param = param.into();
338        let res = unsafe { libc::sched_setparam(pid.into(), &param) };
339        handle_errno(res).map(drop)
340    }
341}
342
343#[cfg(target_os = "linux")]
344pub use linux::*;