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