use core::mem::{MaybeUninit, size_of};
use std::io;
use crate::scheduler::{RunningSchedulerInfo, SchedulerKind, SchedulerProfile};
#[repr(C)]
#[derive(Default)]
struct SchedAttr {
size: u32,
sched_policy: u32,
sched_flags: u64,
sched_nice: i32,
sched_priority: u32,
sched_runtime: u64,
sched_deadline: u64,
sched_period: u64,
}
const SYS_SCHED_SETATTR: libc::c_long = libc::SYS_sched_setattr;
const SYS_SCHED_GETATTR: libc::c_long = libc::SYS_sched_getattr;
const SCHED_OTHER: u32 = 0;
const SCHED_FIFO: u32 = 1;
const SCHED_RR: u32 = 2;
const SCHED_DEADLINE: u32 = 6;
pub(crate) fn apply_scheduler(profile: &SchedulerProfile) -> io::Result<()> {
let mut attr = build_attr(profile);
let rc = unsafe {
libc::syscall(
SYS_SCHED_SETATTR,
0i32, (&mut attr) as *mut SchedAttr, 0u32, )
};
if rc < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn build_attr(profile: &SchedulerProfile) -> SchedAttr {
let mut a = SchedAttr {
size: size_of::<SchedAttr>() as u32,
..SchedAttr::default()
};
match *profile {
SchedulerProfile::Default => {
a.sched_policy = SCHED_OTHER;
}
SchedulerProfile::RealtimeFifo { priority } => {
a.sched_policy = SCHED_FIFO;
a.sched_priority = u32::from(priority);
}
SchedulerProfile::RealtimeRoundRobin { priority } => {
a.sched_policy = SCHED_RR;
a.sched_priority = u32::from(priority);
}
SchedulerProfile::Deadline {
runtime_ns,
deadline_ns,
period_ns,
} => {
a.sched_policy = SCHED_DEADLINE;
a.sched_runtime = runtime_ns;
a.sched_deadline = deadline_ns;
a.sched_period = period_ns;
}
}
a
}
pub(crate) fn read_scheduler() -> io::Result<RunningSchedulerInfo> {
let mut attr = MaybeUninit::<SchedAttr>::zeroed();
let p = attr.as_mut_ptr();
unsafe {
(*p).size = size_of::<SchedAttr>() as u32;
}
let rc = unsafe {
libc::syscall(
SYS_SCHED_GETATTR,
0i32,
attr.as_mut_ptr(),
size_of::<SchedAttr>() as u32,
0u32, )
};
if rc < 0 {
return Err(io::Error::last_os_error());
}
let attr = unsafe { attr.assume_init() };
Ok(parse_attr(&attr))
}
fn parse_attr(a: &SchedAttr) -> RunningSchedulerInfo {
let kind = match a.sched_policy {
SCHED_FIFO => SchedulerKind::Fifo,
SCHED_RR => SchedulerKind::RoundRobin,
SCHED_DEADLINE => SchedulerKind::Deadline,
_ => SchedulerKind::Other,
};
RunningSchedulerInfo {
kind,
priority: a.sched_priority as u8,
runtime_ns: a.sched_runtime,
deadline_ns: a.sched_deadline,
period_ns: a.sched_period,
}
}
fn zeroed_cpu_set() -> libc::cpu_set_t {
unsafe { MaybeUninit::<libc::cpu_set_t>::zeroed().assume_init() }
}
pub(crate) fn pin_to_cpus(cpus: &[usize]) -> io::Result<()> {
if cpus.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"cpu set must not be empty",
));
}
let max_cpu = cpus.iter().copied().max().unwrap_or(0);
if max_cpu >= libc::CPU_SETSIZE as usize {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"cpu index exceeds CPU_SETSIZE",
));
}
let mut set = zeroed_cpu_set();
unsafe {
libc::CPU_ZERO(&mut set);
}
for cpu in cpus {
unsafe {
libc::CPU_SET(*cpu, &mut set);
}
}
let rc = unsafe { libc::sched_setaffinity(0, size_of::<libc::cpu_set_t>(), &set as *const _) };
if rc < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub(crate) fn get_cpus() -> io::Result<std::vec::Vec<usize>> {
let mut set = zeroed_cpu_set();
unsafe {
libc::CPU_ZERO(&mut set);
}
let sz = size_of::<libc::cpu_set_t>();
let p = &mut set as *mut _;
let rc = unsafe { libc::sched_getaffinity(0, sz, p) };
if rc < 0 {
return Err(io::Error::last_os_error());
}
let mut out = std::vec::Vec::new();
for cpu in 0..(libc::CPU_SETSIZE as usize) {
let on = unsafe { libc::CPU_ISSET(cpu, &set) };
if on {
out.push(cpu);
}
}
Ok(out)
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn build_attr_default_uses_sched_other() {
let a = build_attr(&SchedulerProfile::Default);
assert_eq!(a.sched_policy, SCHED_OTHER);
assert_eq!(a.sched_priority, 0);
}
#[test]
fn build_attr_fifo_carries_priority() {
let a = build_attr(&SchedulerProfile::RealtimeFifo { priority: 50 });
assert_eq!(a.sched_policy, SCHED_FIFO);
assert_eq!(a.sched_priority, 50);
}
#[test]
fn build_attr_rr_carries_priority() {
let a = build_attr(&SchedulerProfile::RealtimeRoundRobin { priority: 30 });
assert_eq!(a.sched_policy, SCHED_RR);
assert_eq!(a.sched_priority, 30);
}
#[test]
fn build_attr_deadline_carries_triple() {
let a = build_attr(&SchedulerProfile::Deadline {
runtime_ns: 1_000_000,
deadline_ns: 5_000_000,
period_ns: 10_000_000,
});
assert_eq!(a.sched_policy, SCHED_DEADLINE);
assert_eq!(a.sched_runtime, 1_000_000);
assert_eq!(a.sched_deadline, 5_000_000);
assert_eq!(a.sched_period, 10_000_000);
}
#[test]
fn parse_attr_other_maps_to_other_kind() {
let a = SchedAttr {
sched_policy: SCHED_OTHER,
..SchedAttr::default()
};
let info = parse_attr(&a);
assert_eq!(info.kind, SchedulerKind::Other);
}
#[test]
fn parse_attr_fifo_maps_to_fifo_kind() {
let a = SchedAttr {
sched_policy: SCHED_FIFO,
sched_priority: 10,
..SchedAttr::default()
};
let info = parse_attr(&a);
assert_eq!(info.kind, SchedulerKind::Fifo);
assert_eq!(info.priority, 10);
}
#[test]
fn read_scheduler_returns_some_kind() {
let info = read_scheduler().expect("getattr");
let _ = info.kind;
}
#[test]
fn get_cpus_returns_at_least_one() {
let cpus = get_cpus().expect("getaffinity");
assert!(!cpus.is_empty());
}
#[test]
fn pin_to_cpus_round_trip_on_first_cpu() {
let allowed = get_cpus().expect("getaffinity");
let target = allowed[0];
pin_to_cpus(&[target]).expect("setaffinity");
let after = get_cpus().expect("getaffinity post");
assert_eq!(after, std::vec![target]);
pin_to_cpus(&allowed).expect("restore");
}
#[test]
fn pin_to_cpus_empty_input_rejected() {
let err = pin_to_cpus(&[]).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
fn pin_to_cpus_oversized_rejected() {
let err = pin_to_cpus(&[libc::CPU_SETSIZE as usize + 1]).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
fn apply_default_sched_other_is_priviledge_free() {
apply_scheduler(&SchedulerProfile::Default).expect("setattr SCHED_OTHER");
}
#[test]
fn apply_fifo_with_priority_zero_does_not_panic() {
let _ = apply_scheduler(&SchedulerProfile::RealtimeFifo { priority: 0 });
}
#[test]
fn apply_deadline_without_priv_returns_eperm_or_einval() {
let res = apply_scheduler(&SchedulerProfile::Deadline {
runtime_ns: 1_000_000,
deadline_ns: 5_000_000,
period_ns: 10_000_000,
});
if let Err(e) = res {
assert!(matches!(
e.raw_os_error(),
Some(libc::EPERM) | Some(libc::EINVAL) | Some(libc::EBUSY)
));
}
}
}