preempt_rt/
sched.rs

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