Skip to main content

zerodds_rt_linux/
scheduler.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Public Scheduler-API. Plattform-Routing nach `target_os`.
5//!
6//! Auf Linux delegiert dieses Modul an das interne `syscalls`-Modul, wo die
7//! `unsafe { libc::syscall(...) }`-Bloecke jeweils einzeln dokumentiert
8//! sind. Auf anderen Targets gibt jede Funktion `Unsupported` zurueck,
9//! aber die API kompiliert — der Workspace baut auf macOS/Windows.
10
11use std::io;
12
13/// Scheduler-Policy. Linux-spezifisch (siehe `sched(7)`).
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum SchedulerProfile {
16    /// Linux `SCHED_OTHER` (CFS) — der Default fuer alle Threads.
17    Default,
18    /// Linux `SCHED_FIFO` — strikt prioritaets-basiert, kein Quantum.
19    /// `priority` ist der Wert von `sched_priority` (1..=99, hoeher
20    /// schlaegt niedriger; 0 ist nicht erlaubt fuer FIFO/RR mit
21    /// nicht-leeren Queues, wird vom Kernel akzeptiert aber als
22    /// SCHED_OTHER-Fallback behandelt).
23    ///
24    /// Privilegien: `CAP_SYS_NICE` ab Priority > 0; je nach
25    /// `RLIMIT_RTPRIO` auch fuer Priority 0.
26    RealtimeFifo {
27        /// Wert von `sched_priority` (1..=99).
28        priority: u8,
29    },
30    /// Linux `SCHED_RR` — wie FIFO, aber mit Round-Robin-Quantum
31    /// pro Priority-Level.
32    RealtimeRoundRobin {
33        /// Wert von `sched_priority` (1..=99).
34        priority: u8,
35    },
36    /// Linux `SCHED_DEADLINE` (CBS+EDF) — harte Garantien per
37    /// (`runtime`, `deadline`, `period`)-Triple in Nanosekunden.
38    /// Spec siehe `sched_setattr(2)`. Bedingungen:
39    ///
40    /// * `runtime <= deadline <= period`
41    /// * Kernel berechnet eine Bandbreitenreservierung. EBUSY wenn
42    ///   die globale Reservierung das Limit (default 95%) sprengt.
43    ///
44    /// Privilegien: immer `CAP_SYS_NICE`. Forks duerfen nicht
45    /// vererben (sonst `EBUSY`).
46    Deadline {
47        /// Worst-Case-Execution-Time pro Periode (ns).
48        runtime_ns: u64,
49        /// Soft-Deadline ab Periodenstart (ns).
50        deadline_ns: u64,
51        /// Wiederholungsperiode (ns).
52        period_ns: u64,
53    },
54}
55
56impl SchedulerProfile {
57    /// Wendet das Profil auf den **aufrufenden Thread** an.
58    ///
59    /// # Errors
60    /// * `EPERM` (PermissionDenied) wenn die Privilegien fehlen.
61    /// * `EINVAL` (InvalidInput) bei inkonsistenten Deadline-Werten.
62    /// * `Unsupported` auf Nicht-Linux-Targets.
63    pub fn apply_to_current_thread(&self) -> io::Result<()> {
64        #[cfg(target_os = "linux")]
65        {
66            crate::syscalls::apply_scheduler(self)
67        }
68        #[cfg(not(target_os = "linux"))]
69        {
70            let _ = self;
71            Err(io::Error::new(
72                io::ErrorKind::Unsupported,
73                "SchedulerProfile::apply_to_current_thread requires Linux",
74            ))
75        }
76    }
77}
78
79/// Beschreibung einer aktiven Scheduling-Konfiguration.
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub struct RunningSchedulerInfo {
82    /// Ausgewaehlte Policy.
83    pub kind: SchedulerKind,
84    /// `sched_priority`. Nur bei FIFO/RR relevant.
85    pub priority: u8,
86    /// `sched_runtime` (ns). Nur bei Deadline relevant.
87    pub runtime_ns: u64,
88    /// `sched_deadline` (ns). Nur bei Deadline relevant.
89    pub deadline_ns: u64,
90    /// `sched_period` (ns). Nur bei Deadline relevant.
91    pub period_ns: u64,
92}
93
94/// Klassifikation der Linux-Scheduler-Policy.
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum SchedulerKind {
97    /// CFS (`SCHED_OTHER`/`SCHED_BATCH`/`SCHED_IDLE`).
98    Other,
99    /// `SCHED_FIFO`.
100    Fifo,
101    /// `SCHED_RR`.
102    RoundRobin,
103    /// `SCHED_DEADLINE`.
104    Deadline,
105}
106
107/// Liest die aktuelle Scheduler-Konfiguration des aufrufenden Threads.
108///
109/// Privilegienfrei.
110///
111/// # Errors
112/// Kernel-Fehler aus `sched_getattr(2)`.
113/// `Unsupported` auf Nicht-Linux-Targets.
114pub fn current_scheduler() -> io::Result<RunningSchedulerInfo> {
115    #[cfg(target_os = "linux")]
116    {
117        crate::syscalls::read_scheduler()
118    }
119    #[cfg(not(target_os = "linux"))]
120    {
121        Err(io::Error::new(
122            io::ErrorKind::Unsupported,
123            "current_scheduler() requires Linux",
124        ))
125    }
126}
127
128#[cfg(test)]
129#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn profile_is_send_sync_clone_eq() {
135        fn assert_traits<T: Send + Sync + Clone + Copy + PartialEq>() {}
136        assert_traits::<SchedulerProfile>();
137        assert_traits::<RunningSchedulerInfo>();
138        assert_traits::<SchedulerKind>();
139    }
140
141    #[test]
142    fn profile_default_is_distinct_from_fifo() {
143        assert_ne!(
144            SchedulerProfile::Default,
145            SchedulerProfile::RealtimeFifo { priority: 1 }
146        );
147    }
148
149    #[test]
150    #[cfg(not(target_os = "linux"))]
151    fn apply_returns_unsupported_off_linux() {
152        let err = SchedulerProfile::Default
153            .apply_to_current_thread()
154            .unwrap_err();
155        assert_eq!(err.kind(), io::ErrorKind::Unsupported);
156    }
157
158    #[test]
159    #[cfg(not(target_os = "linux"))]
160    fn current_scheduler_returns_unsupported_off_linux() {
161        let err = current_scheduler().unwrap_err();
162        assert_eq!(err.kind(), io::ErrorKind::Unsupported);
163    }
164
165    #[test]
166    #[cfg(target_os = "linux")]
167    fn linux_default_apply_round_trips_via_getattr() {
168        SchedulerProfile::Default
169            .apply_to_current_thread()
170            .expect("apply default");
171        let info = current_scheduler().expect("read");
172        assert_eq!(info.kind, SchedulerKind::Other);
173    }
174
175    #[test]
176    #[cfg(target_os = "linux")]
177    fn linux_eperm_for_deadline_without_caps() {
178        // Ohne CAP_SYS_NICE muss DEADLINE EPERM oder EINVAL/EBUSY
179        // liefern; auf gar keinen Fall ein Panic.
180        let res = SchedulerProfile::Deadline {
181            runtime_ns: 1_000_000,
182            deadline_ns: 5_000_000,
183            period_ns: 10_000_000,
184        }
185        .apply_to_current_thread();
186        if let Err(e) = res {
187            assert!(matches!(
188                e.kind(),
189                io::ErrorKind::PermissionDenied
190                    | io::ErrorKind::InvalidInput
191                    | io::ErrorKind::ResourceBusy
192                    | io::ErrorKind::Other,
193            ));
194        }
195    }
196}