diff --git a/docs/src/kernel/linux-compatibility.md b/docs/src/kernel/linux-compatibility.md index 3f6f6dd66..de5ea9e80 100644 --- a/docs/src/kernel/linux-compatibility.md +++ b/docs/src/kernel/linux-compatibility.md @@ -162,12 +162,12 @@ provided by Linux on x86-64 architecture. | 139 | sysfs | ❌ | | 140 | getpriority | ✅ | | 141 | setpriority | ✅ | -| 142 | sched_setparam | ❌ | -| 143 | sched_getparam | ❌ | -| 144 | sched_setscheduler | ❌ | -| 145 | sched_getscheduler | ❌ | -| 146 | sched_get_priority_max | ❌ | -| 147 | sched_get_priority_min | ❌ | +| 142 | sched_setparam | ✅ | +| 143 | sched_getparam | ✅ | +| 144 | sched_setscheduler | ✅ | +| 145 | sched_getscheduler | ✅ | +| 146 | sched_get_priority_max | ✅ | +| 147 | sched_get_priority_min | ✅ | | 148 | sched_rr_get_interval | ❌ | | 149 | mlock | ❌ | | 150 | munlock | ❌ | @@ -334,6 +334,8 @@ provided by Linux on x86-64 architecture. | 311 | process_vm_writev | ❌ | | 312 | kcmp | ❌ | | 313 | finit_module | ❌ | +| 314 | sched_setattr | ✅ | +| 315 | sched_getattr | ✅ | | 318 | getrandom | ✅ | | 322 | execveat | ✅ | | 327 | preadv2 | ✅ | diff --git a/kernel/src/error.rs b/kernel/src/error.rs index 62372e7c1..ab4d7046e 100644 --- a/kernel/src/error.rs +++ b/kernel/src/error.rs @@ -246,6 +246,12 @@ impl From for Error { } } +impl From for Error { + fn from(_: core::num::TryFromIntError) -> Self { + Error::with_message(Errno::EINVAL, "Invalid integer") + } +} + impl From for Error { fn from(_: core::str::Utf8Error) -> Self { Error::with_message(Errno::EINVAL, "Invalid utf-8 string") diff --git a/kernel/src/sched/mod.rs b/kernel/src/sched/mod.rs index 52ddebefa..3f8842d04 100644 --- a/kernel/src/sched/mod.rs +++ b/kernel/src/sched/mod.rs @@ -6,6 +6,6 @@ mod stats; pub use self::{ nice::{AtomicNice, Nice}, - sched_class::{init, SchedAttr, SchedPolicy}, + sched_class::{init, RealTimePolicy, RealTimePriority, SchedAttr, SchedPolicy}, stats::{loadavg, nr_queued_and_running}, }; diff --git a/kernel/src/sched/sched_class/mod.rs b/kernel/src/sched/sched_class/mod.rs index d296e2a51..ebc0dcc37 100644 --- a/kernel/src/sched/sched_class/mod.rs +++ b/kernel/src/sched/sched_class/mod.rs @@ -33,8 +33,11 @@ mod idle; mod real_time; mod stop; -pub use self::policy::SchedPolicy; use self::policy::{SchedPolicyKind, SchedPolicyState}; +pub use self::{ + policy::SchedPolicy, + real_time::{RealTimePolicy, RealTimePriority}, +}; type SchedEntity = (Arc, Arc); @@ -140,7 +143,7 @@ impl SchedAttr { real_time: { let (prio, policy) = match policy { SchedPolicy::RealTime { rt_prio, rt_policy } => (rt_prio.get(), rt_policy), - _ => (real_time::RtPrio::MAX.get(), Default::default()), + _ => (real_time::RealTimePriority::MAX.get(), Default::default()), }; real_time::RealTimeAttr::new(prio, policy) }, @@ -174,6 +177,10 @@ impl SchedAttr { }); } + pub fn update_policy(&self, f: impl FnOnce(&mut SchedPolicy) -> T) -> T { + self.policy.update(f) + } + fn last_cpu(&self) -> Option { self.last_cpu.get() } diff --git a/kernel/src/sched/sched_class/policy.rs b/kernel/src/sched/sched_class/policy.rs index 8dcb35900..3e04db3c2 100644 --- a/kernel/src/sched/sched_class/policy.rs +++ b/kernel/src/sched/sched_class/policy.rs @@ -6,7 +6,7 @@ use atomic_integer_wrapper::define_atomic_version_of_integer_like_type; use int_to_c_enum::TryFromInt; use ostd::sync::SpinLock; -pub use super::real_time::{RealTimePolicy, RtPrio}; +pub use super::real_time::{RealTimePolicy, RealTimePriority}; use crate::sched::nice::Nice; /// The User-chosen scheduling policy. @@ -16,7 +16,7 @@ use crate::sched::nice::Nice; pub enum SchedPolicy { Stop, RealTime { - rt_prio: RtPrio, + rt_prio: RealTimePriority, rt_policy: RealTimePolicy, }, Fair(Nice), @@ -101,4 +101,8 @@ impl SchedPolicyState { self.kind.store(policy.kind(), Relaxed); *this = policy; } + + pub fn update(&self, update: impl FnOnce(&mut SchedPolicy) -> T) -> T { + update(&mut *self.policy.disable_irq().lock()) + } } diff --git a/kernel/src/sched/sched_class/real_time.rs b/kernel/src/sched/sched_class/real_time.rs index e95350653..3303c94f7 100644 --- a/kernel/src/sched/sched_class/real_time.rs +++ b/kernel/src/sched/sched_class/real_time.rs @@ -19,7 +19,7 @@ use ostd::{ use super::{time::base_slice_clocks, CurrentRuntime, SchedAttr, SchedClassRq}; use crate::{sched::nice::RangedU8, thread::AsThread}; -pub type RtPrio = RangedU8<0, 99>; +pub type RealTimePriority = RangedU8<1, 99>; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum RealTimePolicy { diff --git a/kernel/src/syscall/arch/riscv.rs b/kernel/src/syscall/arch/riscv.rs index 10540f1a7..1132cb03d 100644 --- a/kernel/src/syscall/arch/riscv.rs +++ b/kernel/src/syscall/arch/riscv.rs @@ -26,6 +26,7 @@ use crate::syscall::{ flock::sys_flock, fsync::{sys_fdatasync, sys_fsync}, futex::sys_futex, + get_priority::sys_get_priority, getcpu::sys_getcpu, getcwd::sys_getcwd, getdents64::sys_getdents64, @@ -81,6 +82,14 @@ use crate::syscall::{ rt_sigprocmask::sys_rt_sigprocmask, rt_sigsuspend::sys_rt_sigsuspend, sched_affinity::{sys_sched_getaffinity, sys_sched_setaffinity}, + sched_get_priority_max::sys_sched_get_priority_max, + sched_get_priority_min::sys_sched_get_priority_min, + sched_getattr::sys_sched_getattr, + sched_getparam::sys_sched_getparam, + sched_getscheduler::sys_sched_getscheduler, + sched_setattr::sys_sched_setattr, + sched_setparam::sys_sched_setparam, + sched_setscheduler::sys_sched_setscheduler, sched_yield::sys_sched_yield, semctl::sys_semctl, semget::sys_semget, @@ -88,7 +97,7 @@ use crate::syscall::{ sendfile::sys_sendfile, sendmsg::sys_sendmsg, sendto::sys_sendto, - set_get_priority::{sys_get_priority, sys_set_priority}, + set_priority::sys_set_priority, set_robust_list::sys_set_robust_list, set_tid_address::sys_set_tid_address, setfsgid::sys_setfsgid, @@ -192,9 +201,15 @@ impl_syscall_nums_and_dispatch_fn! { SYS_SETITIMER = 103 => sys_setitimer(args[..3]); SYS_TIMER_CREATE = 107 => sys_timer_create(args[..3]); SYS_TIMER_DELETE = 111 => sys_timer_delete(args[..1]); + SYS_SCHED_SETPARAM = 118 => sys_sched_setparam(args[..2]); + SYS_SCHED_SETSCHEDULER = 119 => sys_sched_setscheduler(args[..3]); + SYS_SCHED_GETSCHEDULER = 120 => sys_sched_getscheduler(args[..1]); + SYS_SCHED_GETPARAM = 121 => sys_sched_getparam(args[..2]); SYS_SCHED_SETAFFINITY = 122 => sys_sched_setaffinity(args[..3]); SYS_SCHED_GETAFFINITY = 123 => sys_sched_getaffinity(args[..3]); SYS_SCHED_YIELD = 124 => sys_sched_yield(args[..0]); + SYS_SCHED_GET_PRIORITY_MAX = 125 => sys_sched_get_priority_max(args[..1]); + SYS_SCHED_GET_PRIORITY_MIN = 126 => sys_sched_get_priority_min(args[..1]); SYS_KILL = 129 => sys_kill(args[..2]); SYS_TGKILL = 131 => sys_tgkill(args[..3]); SYS_SIGALTSTACK = 132 => sys_sigaltstack(args[..2]); @@ -264,6 +279,8 @@ impl_syscall_nums_and_dispatch_fn! { SYS_ACCEPT4 = 242 => sys_accept4(args[..4]); SYS_WAIT4 = 260 => sys_wait4(args[..4]); SYS_PRLIMIT64 = 261 => sys_prlimit64(args[..4]); + SYS_SCHED_SETATTR = 274 => sys_sched_setattr(args[..3]); + SYS_SCHED_GETATTR = 275 => sys_sched_getattr(args[..4]); SYS_GETRANDOM = 278 => sys_getrandom(args[..3]); SYS_EXECVEAT = 281 => sys_execveat(args[..5], &mut user_ctx); SYS_PREADV2 = 286 => sys_preadv2(args[..5]); diff --git a/kernel/src/syscall/arch/x86.rs b/kernel/src/syscall/arch/x86.rs index 62e270dca..4de933e61 100644 --- a/kernel/src/syscall/arch/x86.rs +++ b/kernel/src/syscall/arch/x86.rs @@ -29,6 +29,7 @@ use crate::syscall::{ fork::sys_fork, fsync::{sys_fdatasync, sys_fsync}, futex::sys_futex, + get_priority::sys_get_priority, getcpu::sys_getcpu, getcwd::sys_getcwd, getdents64::{sys_getdents, sys_getdents64}, @@ -89,6 +90,14 @@ use crate::syscall::{ rt_sigreturn::sys_rt_sigreturn, rt_sigsuspend::sys_rt_sigsuspend, sched_affinity::{sys_sched_getaffinity, sys_sched_setaffinity}, + sched_get_priority_max::sys_sched_get_priority_max, + sched_get_priority_min::sys_sched_get_priority_min, + sched_getattr::sys_sched_getattr, + sched_getparam::sys_sched_getparam, + sched_getscheduler::sys_sched_getscheduler, + sched_setattr::sys_sched_setattr, + sched_setparam::sys_sched_setparam, + sched_setscheduler::sys_sched_setscheduler, sched_yield::sys_sched_yield, select::sys_select, semctl::sys_semctl, @@ -97,7 +106,7 @@ use crate::syscall::{ sendfile::sys_sendfile, sendmsg::sys_sendmsg, sendto::sys_sendto, - set_get_priority::{sys_get_priority, sys_set_priority}, + set_priority::sys_set_priority, set_robust_list::sys_set_robust_list, set_tid_address::sys_set_tid_address, setfsgid::sys_setfsgid, @@ -260,6 +269,12 @@ impl_syscall_nums_and_dispatch_fn! { SYS_FSTATFS = 138 => sys_fstatfs(args[..2]); SYS_GET_PRIORITY = 140 => sys_get_priority(args[..2]); SYS_SET_PRIORITY = 141 => sys_set_priority(args[..3]); + SYS_SCHED_SETPARAM = 142 => sys_sched_setparam(args[..2]); + SYS_SCHED_GETPARAM = 143 => sys_sched_getparam(args[..2]); + SYS_SCHED_SETSCHEDULER = 144 => sys_sched_setscheduler(args[..3]); + SYS_SCHED_GETSCHEDULER = 145 => sys_sched_getscheduler(args[..1]); + SYS_SCHED_GET_PRIORITY_MAX = 146 => sys_sched_get_priority_max(args[..1]); + SYS_SCHED_GET_PRIORITY_MIN = 147 => sys_sched_get_priority_min(args[..1]); SYS_PRCTL = 157 => sys_prctl(args[..5]); SYS_ARCH_PRCTL = 158 => sys_arch_prctl(args[..2], &mut user_ctx); SYS_SETRLIMIT = 160 => sys_setrlimit(args[..2]); @@ -316,6 +331,8 @@ impl_syscall_nums_and_dispatch_fn! { SYS_PWRITEV = 296 => sys_pwritev(args[..4]); SYS_PRLIMIT64 = 302 => sys_prlimit64(args[..4]); SYS_GETCPU = 309 => sys_getcpu(args[..3]); + SYS_SCHED_SETATTR = 314 => sys_sched_setattr(args[..3]); + SYS_SCHED_GETATTR = 315 => sys_sched_getattr(args[..4]); SYS_GETRANDOM = 318 => sys_getrandom(args[..3]); SYS_EXECVEAT = 322 => sys_execveat(args[..5], &mut user_ctx); SYS_PREADV2 = 327 => sys_preadv2(args[..5]); diff --git a/kernel/src/syscall/set_get_priority.rs b/kernel/src/syscall/get_priority.rs similarity index 80% rename from kernel/src/syscall/set_get_priority.rs rename to kernel/src/syscall/get_priority.rs index 9ab12c0c7..6e5561c59 100644 --- a/kernel/src/syscall/set_get_priority.rs +++ b/kernel/src/syscall/get_priority.rs @@ -9,29 +9,6 @@ use crate::{ sched::Nice, }; -pub fn sys_set_priority(which: i32, who: u32, prio: i32, ctx: &Context) -> Result { - let prio_target = PriorityTarget::new(which, who, ctx)?; - let new_nice: Nice = { - let nice_raw = prio.clamp( - Nice::MIN.value().get() as i32, - Nice::MAX.value().get() as i32, - ) as i8; - nice_raw.try_into().unwrap() - }; - - debug!( - "set_priority prio_target: {:?}, new_nice: {:?}", - prio_target, new_nice - ); - - let processes = get_processes(prio_target)?; - for process in processes.iter() { - process.nice().store(new_nice, Ordering::Relaxed); - } - - Ok(SyscallReturn::Return(0)) -} - pub fn sys_get_priority(which: i32, who: u32, ctx: &Context) -> Result { let prio_target = PriorityTarget::new(which, who, ctx)?; debug!("get_priority prio_target: {:?}", prio_target); @@ -55,7 +32,7 @@ pub fn sys_get_priority(which: i32, who: u32, ctx: &Context) -> Result Result>> { +pub(super) fn get_processes(prio_target: PriorityTarget) -> Result>> { Ok(match prio_target { PriorityTarget::Process(pid) => { let process = process_table::get_process(pid).ok_or(Error::new(Errno::ESRCH))?; @@ -90,14 +67,14 @@ fn get_processes(prio_target: PriorityTarget) -> Result>> { } #[derive(Debug)] -enum PriorityTarget { +pub(super) enum PriorityTarget { Process(Pid), ProcessGroup(Pgid), User(Uid), } impl PriorityTarget { - fn new(which: i32, who: u32, ctx: &Context) -> Result { + pub(super) fn new(which: i32, who: u32, ctx: &Context) -> Result { let which = Which::try_from(which) .map_err(|_| Error::with_message(Errno::EINVAL, "invalid which value"))?; Ok(match which { @@ -132,7 +109,7 @@ impl PriorityTarget { #[expect(non_camel_case_types)] #[derive(Clone, Debug, TryFromInt)] #[repr(i32)] -enum Which { +pub(super) enum Which { PRIO_PROCESS = 0, PRIO_PGRP = 1, PRIO_USER = 2, diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index f9f3a4211..617c0f72b 100644 --- a/kernel/src/syscall/mod.rs +++ b/kernel/src/syscall/mod.rs @@ -37,6 +37,7 @@ mod flock; mod fork; mod fsync; mod futex; +mod get_priority; mod getcpu; mod getcwd; mod getdents64; @@ -96,6 +97,14 @@ mod rt_sigprocmask; mod rt_sigreturn; mod rt_sigsuspend; mod sched_affinity; +mod sched_get_priority_max; +mod sched_get_priority_min; +mod sched_getattr; +mod sched_getparam; +mod sched_getscheduler; +mod sched_setattr; +mod sched_setparam; +mod sched_setscheduler; mod sched_yield; mod select; mod semctl; @@ -104,7 +113,7 @@ mod semop; mod sendfile; mod sendmsg; mod sendto; -mod set_get_priority; +mod set_priority; mod set_robust_list; mod set_tid_address; mod setfsgid; diff --git a/kernel/src/syscall/sched_get_priority_max.rs b/kernel/src/syscall/sched_get_priority_max.rs new file mode 100644 index 000000000..8eda7e37a --- /dev/null +++ b/kernel/src/syscall/sched_get_priority_max.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::ops::RangeInclusive; + +use super::SyscallReturn; +use crate::{prelude::*, sched::RealTimePriority}; + +pub(super) const fn rt_to_static(prio: RealTimePriority) -> u32 { + (100 - prio.get()) as u32 +} + +pub(super) const fn static_to_rt(prio: u32) -> Result { + if *RT_PRIORITY_RANGE.start() <= prio && prio <= *RT_PRIORITY_RANGE.end() { + Ok(RealTimePriority::new((100 - prio) as u8)) + } else { + Err(Error::with_message(Errno::EINVAL, "invalid priority")) + } +} + +pub(super) const RT_PRIORITY_RANGE: RangeInclusive = + rt_to_static(RealTimePriority::MAX)..=rt_to_static(RealTimePriority::MIN); +pub(super) const SCHED_PRIORITY_RANGE: &[RangeInclusive] = &[ + 0..=0, // SCHED_NORMAL + RT_PRIORITY_RANGE, // SCHED_FIFO + RT_PRIORITY_RANGE, // SCHED_RR + 0..=0, // SCHED_BATCH + 0..=0, // SCHED_ISO + 0..=0, // SCHED_IDLE + 0..=0, // SCHED_DEADLINE + 0..=0, // SCHED_EXT +]; + +pub fn sys_sched_get_priority_max(policy: u32, _: &Context) -> Result { + let range = SCHED_PRIORITY_RANGE + .get(policy as usize) + .ok_or_else(|| Error::with_message(Errno::EINVAL, "invalid scheduling policy"))?; + Ok(SyscallReturn::Return(*range.end() as isize)) +} diff --git a/kernel/src/syscall/sched_get_priority_min.rs b/kernel/src/syscall/sched_get_priority_min.rs new file mode 100644 index 000000000..ef32e6666 --- /dev/null +++ b/kernel/src/syscall/sched_get_priority_min.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::{sched_get_priority_max::SCHED_PRIORITY_RANGE, SyscallReturn}; +use crate::prelude::*; + +pub fn sys_sched_get_priority_min(policy: u32, _: &Context) -> Result { + let range = SCHED_PRIORITY_RANGE + .get(policy as usize) + .ok_or_else(|| Error::with_message(Errno::EINVAL, "invalid scheduling policy"))?; + Ok(SyscallReturn::Return(*range.start() as isize)) +} diff --git a/kernel/src/syscall/sched_getattr.rs b/kernel/src/syscall/sched_getattr.rs new file mode 100644 index 000000000..e471bb213 --- /dev/null +++ b/kernel/src/syscall/sched_getattr.rs @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::mem; + +use super::{ + sched_get_priority_max::{rt_to_static, static_to_rt, SCHED_PRIORITY_RANGE}, + SyscallReturn, +}; +use crate::{ + prelude::*, + process::posix_thread::thread_table, + sched::{Nice, RealTimePolicy, SchedAttr, SchedPolicy}, + thread::Tid, +}; + +pub(super) const SCHED_NORMAL: u32 = 0; +pub(super) const SCHED_FIFO: u32 = 1; +pub(super) const SCHED_RR: u32 = 2; +// pub(super) const SCHED_BATCH: u32 = 3; // not supported (never). +// SCHED_ISO: reserved but not implemented yet on Linux. +pub(super) const SCHED_IDLE: u32 = 5; +// pub(super) const SCHED_DEADLINE: u32 = 6; // not supported yet. +// pub(super) const SCHED_EXT: u32 = 7; // not supported (never). + +#[derive(Default, Debug, Pod, Clone, Copy)] +#[repr(C)] +pub(super) struct LinuxSchedAttr { + // Size of this structure + pub(super) size: u32, + + // Policy (SCHED_*) + pub(super) sched_policy: u32, + // Flags + pub(super) sched_flags: u64, + + // Nice value (SCHED_NORMAL, SCHED_BATCH) + pub(super) sched_nice: i32, + + // Static priority (SCHED_FIFO, SCHED_RR) + pub(super) sched_priority: u32, + + // For SCHED_DEADLINE + pub(super) sched_runtime: u64, + pub(super) sched_deadline: u64, + pub(super) sched_period: u64, + + // Utilization hints + pub(super) sched_util_min: u32, + pub(super) sched_util_max: u32, +} + +impl TryFrom for LinuxSchedAttr { + type Error = Error; + + fn try_from(value: SchedPolicy) -> Result { + Ok(match value { + SchedPolicy::Stop => LinuxSchedAttr { + sched_policy: SCHED_FIFO, + sched_priority: 99, // Linux uses 99 as the default priority for STOP tasks. + ..Default::default() + }, + + SchedPolicy::RealTime { rt_prio, rt_policy } => LinuxSchedAttr { + sched_policy: match rt_policy { + RealTimePolicy::Fifo => SCHED_FIFO, + RealTimePolicy::RoundRobin { .. } => SCHED_RR, + }, + sched_priority: rt_to_static(rt_prio), + ..Default::default() + }, + + SchedPolicy::Fair(nice) => LinuxSchedAttr { + sched_policy: SCHED_NORMAL, + sched_nice: nice.value().get().into(), + ..Default::default() + }, + + SchedPolicy::Idle => LinuxSchedAttr { + sched_policy: SCHED_IDLE, + ..Default::default() + }, + }) + } +} + +impl TryFrom for SchedPolicy { + type Error = Error; + + fn try_from(value: LinuxSchedAttr) -> Result { + Ok(match value.sched_policy { + SCHED_FIFO | SCHED_RR => SchedPolicy::RealTime { + rt_prio: static_to_rt(value.sched_priority)?, + rt_policy: match value.sched_policy { + SCHED_FIFO => RealTimePolicy::Fifo, + SCHED_RR => RealTimePolicy::RoundRobin { + base_slice_factor: None, + }, + _ => unreachable!(), + }, + }, + + _ if value.sched_priority != 0 => { + return Err(Error::with_message( + Errno::EINVAL, + "Invalid scheduling priority", + )) + } + + SCHED_NORMAL => SchedPolicy::Fair(Nice::new( + i8::try_from(value.sched_nice)? + .try_into() + .map_err(|msg| Error::with_message(Errno::EINVAL, msg))?, + )), + + SCHED_IDLE => SchedPolicy::Idle, + + _ => { + return Err(Error::with_message( + Errno::EINVAL, + "Invalid scheduling policy", + )) + } + }) + } +} + +pub(super) fn read_linux_sched_attr_from_user( + addr: Vaddr, + ctx: &Context, +) -> Result { + let type_size = mem::size_of::(); + + let space = CurrentUserSpace::new(ctx.task); + + let mut attr = LinuxSchedAttr::default(); + + space.read_bytes( + addr, + &mut VmWriter::from(&mut attr.as_bytes_mut()[..mem::size_of::()]), + )?; + + let size = type_size.min(attr.size as usize); + space.read_bytes(addr, &mut VmWriter::from(&mut attr.as_bytes_mut()[..size]))?; + + if let Some(additional_size) = attr.size.checked_sub(type_size as u32) { + let mut buf = vec![0; additional_size as usize]; + space.read_bytes(addr + type_size, &mut VmWriter::from(&mut *buf))?; + + if buf.iter().any(|&b| b != 0) { + return Err(Error::with_message(Errno::E2BIG, "too big sched_attr")); + } + } + + Ok(attr) +} + +pub(super) fn write_linux_sched_attr_to_user( + mut attr: LinuxSchedAttr, + addr: Vaddr, + user_size: u32, + ctx: &Context, +) -> Result<()> { + let space = CurrentUserSpace::new(ctx.task); + + attr.size = (mem::size_of::() as u32).min(user_size); + + let range = SCHED_PRIORITY_RANGE + .get(attr.sched_policy as usize) + .ok_or_else(|| Error::with_message(Errno::EINVAL, "invalid scheduling policy"))?; + attr.sched_util_min = *range.start(); + attr.sched_util_max = *range.end(); + + space.write_bytes( + addr, + &mut VmReader::from(&attr.as_bytes()[..attr.size as usize]), + )?; + Ok(()) +} + +pub(super) fn access_sched_attr_with( + tid: Tid, + ctx: &Context, + f: impl FnOnce(&SchedAttr) -> Result, +) -> Result { + match tid { + 0 => f(&ctx.thread.sched_attr()), + _ if tid > (i32::MAX as u32) => Err(Error::with_message(Errno::EINVAL, "invalid tid")), + _ => f(&thread_table::get_thread(tid) + .ok_or_else(|| Error::with_message(Errno::ESRCH, "thread does not exist"))? + .sched_attr()), + } +} + +pub fn sys_sched_getattr( + tid: Tid, + addr: Vaddr, + user_size: u32, + flags: u32, + ctx: &Context, +) -> Result { + if flags != 0 { + // TODO: support flags soch as `RESET_ON_FORK`. + return Err(Error::with_message(Errno::EINVAL, "unsupported flags")); + } + + let policy = access_sched_attr_with(tid, ctx, |attr| Ok(attr.policy()))?; + let attr: LinuxSchedAttr = policy.try_into()?; + write_linux_sched_attr_to_user(attr, addr, user_size, ctx) + .map_err(|_| Error::new(Errno::EINVAL))?; + + Ok(SyscallReturn::Return(0)) +} diff --git a/kernel/src/syscall/sched_getparam.rs b/kernel/src/syscall/sched_getparam.rs new file mode 100644 index 000000000..e63abf5db --- /dev/null +++ b/kernel/src/syscall/sched_getparam.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::{sched_getattr::access_sched_attr_with, SyscallReturn}; +use crate::{prelude::*, sched::SchedPolicy, thread::Tid}; + +pub fn sys_sched_getparam(tid: Tid, addr: Vaddr, ctx: &Context) -> Result { + let policy = access_sched_attr_with(tid, ctx, |attr| Ok(attr.policy()))?; + let rt_prio = match policy { + SchedPolicy::RealTime { rt_prio, .. } => rt_prio.get().into(), + _ => 0, + }; + + let space = CurrentUserSpace::new(ctx.task); + space + .write_val(addr, &rt_prio) + .map_err(|_| Error::new(Errno::EINVAL))?; + + Ok(SyscallReturn::Return(0)) +} diff --git a/kernel/src/syscall/sched_getscheduler.rs b/kernel/src/syscall/sched_getscheduler.rs new file mode 100644 index 000000000..4c7fc634b --- /dev/null +++ b/kernel/src/syscall/sched_getscheduler.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::{ + sched_getattr::{access_sched_attr_with, LinuxSchedAttr}, + SyscallReturn, +}; +use crate::{prelude::*, thread::Tid}; + +pub fn sys_sched_getscheduler(tid: Tid, ctx: &Context) -> Result { + let policy = access_sched_attr_with(tid, ctx, |attr| Ok(attr.policy()))?; + let policy = LinuxSchedAttr::try_from(policy)?.sched_policy; + Ok(SyscallReturn::Return(policy as isize)) +} diff --git a/kernel/src/syscall/sched_setattr.rs b/kernel/src/syscall/sched_setattr.rs new file mode 100644 index 000000000..bf5140a9f --- /dev/null +++ b/kernel/src/syscall/sched_setattr.rs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::{ + sched_getattr::{access_sched_attr_with, read_linux_sched_attr_from_user}, + SyscallReturn, +}; +use crate::{prelude::*, sched::SchedPolicy, thread::Tid}; + +pub fn sys_sched_setattr( + tid: Tid, + addr: Vaddr, + flags: u32, + ctx: &Context, +) -> Result { + if flags != 0 { + // TODO: support flags soch as `RESET_ON_FORK`. + return Err(Error::with_message(Errno::EINVAL, "unsupported flags")); + } + + let attr = read_linux_sched_attr_from_user(addr, ctx).map_err(|_| Error::new(Errno::EINVAL))?; + let policy = SchedPolicy::try_from(attr)?; + access_sched_attr_with(tid, ctx, |attr| { + attr.set_policy(policy); + Ok(()) + })?; + + Ok(SyscallReturn::Return(0)) +} diff --git a/kernel/src/syscall/sched_setparam.rs b/kernel/src/syscall/sched_setparam.rs new file mode 100644 index 000000000..caa977787 --- /dev/null +++ b/kernel/src/syscall/sched_setparam.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::{sched_getattr::access_sched_attr_with, SyscallReturn}; +use crate::{prelude::*, sched::SchedPolicy, thread::Tid}; + +pub fn sys_sched_setparam(tid: Tid, addr: Vaddr, ctx: &Context) -> Result { + let space = CurrentUserSpace::new(ctx.task); + let prio: i32 = space + .read_val(addr) + .map_err(|_| Error::new(Errno::EINVAL))?; + + let update = |policy: &mut SchedPolicy| { + match policy { + SchedPolicy::RealTime { rt_prio, .. } => { + *rt_prio = u8::try_from(prio)? + .try_into() + .map_err(|msg| Error::with_message(Errno::EINVAL, msg))?; + } + _ if prio != 0 => return Err(Error::with_message(Errno::EINVAL, "invalid priority")), + _ => {} + } + Ok(()) + }; + access_sched_attr_with(tid, ctx, |attr| attr.update_policy(update))?; + + Ok(SyscallReturn::Return(0)) +} diff --git a/kernel/src/syscall/sched_setscheduler.rs b/kernel/src/syscall/sched_setscheduler.rs new file mode 100644 index 000000000..8d62bf484 --- /dev/null +++ b/kernel/src/syscall/sched_setscheduler.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::{ + sched_getattr::{access_sched_attr_with, LinuxSchedAttr}, + SyscallReturn, +}; +use crate::{prelude::*, thread::Tid}; + +pub fn sys_sched_setscheduler( + tid: Tid, + policy: i32, + addr: Vaddr, + ctx: &Context, +) -> Result { + let space = CurrentUserSpace::new(&ctx.task); + let prio = space + .read_val(addr) + .map_err(|_| Error::new(Errno::EINVAL))?; + + let attr = LinuxSchedAttr { + sched_policy: policy as u32, + sched_priority: prio, + ..Default::default() + }; + + let policy = attr.try_into()?; + access_sched_attr_with(tid, ctx, |attr| { + attr.set_policy(policy); + Ok(()) + })?; + + Ok(SyscallReturn::Return(0)) +} diff --git a/kernel/src/syscall/set_priority.rs b/kernel/src/syscall/set_priority.rs new file mode 100644 index 000000000..2b9f7d043 --- /dev/null +++ b/kernel/src/syscall/set_priority.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::sync::atomic::Ordering; + +use super::SyscallReturn; +use crate::{ + prelude::*, + process::ResourceType::RLIMIT_NICE, + sched::Nice, + syscall::get_priority::{get_processes, PriorityTarget}, +}; + +pub fn sys_set_priority(which: i32, who: u32, prio: i32, ctx: &Context) -> Result { + let prio_target = PriorityTarget::new(which, who, ctx)?; + let new_nice: Nice = { + let nice_raw = prio.clamp( + Nice::MIN.value().get() as i32, + Nice::MAX.value().get() as i32, + ) as i8; + nice_raw.try_into().unwrap() + }; + + debug!( + "set_priority prio_target: {:?}, new_nice: {:?}", + prio_target, new_nice + ); + + let processes = get_processes(prio_target)?; + for process in processes.iter() { + let rlimit = process.resource_limits().lock(); + let limit = (rlimit.get_rlimit(RLIMIT_NICE).get_cur() as i8) + .try_into() + .map_err(|msg| Error::with_message(Errno::EINVAL, msg))?; + + if new_nice < limit { + return_errno!(Errno::EACCES); + } + process.nice().store(new_nice, Ordering::Relaxed); + } + + Ok(SyscallReturn::Return(0)) +} diff --git a/test/apps/Makefile b/test/apps/Makefile index 78987c5b5..e4e60f1e2 100644 --- a/test/apps/Makefile +++ b/test/apps/Makefile @@ -36,6 +36,7 @@ TEST_APPS := \ prctl \ pthread \ pty \ + sched \ shm \ signal_c \ vsock \ diff --git a/test/apps/sched/Makefile b/test/apps/sched/Makefile new file mode 100644 index 000000000..c603a781a --- /dev/null +++ b/test/apps/sched/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: MPL-2.0 + +include ../test_common.mk + +EXTRA_C_FLAGS := diff --git a/test/apps/sched/sched_attr.c b/test/apps/sched/sched_attr.c new file mode 100644 index 000000000..9e2885c30 --- /dev/null +++ b/test/apps/sched/sched_attr.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MPL-2.0 + +#define _GNU_SOURCE + +#include "../network/test.h" +#include +#include +#include +#include + +FN_SETUP() +{ +} +END_SETUP() + +FN_TEST(sched_param) +{ + TEST_ERRNO(sched_getscheduler(-100), EINVAL); + TEST_ERRNO(sched_getscheduler(1234567890), ESRCH); + TEST_RES(sched_getscheduler(0), _ret == SCHED_OTHER); + + struct sched_param param; + + TEST_ERRNO(sched_getparam(0, NULL), EINVAL); + TEST_RES(sched_getparam(0, ¶m), + _ret == 0 && param.sched_priority == 0); + + param.sched_priority = 50; + TEST_ERRNO(sched_setscheduler(0, SCHED_FIFO, NULL), EINVAL); + TEST_ERRNO(sched_setscheduler(0, 1234567890, ¶m), EINVAL); + TEST_ERRNO(sched_setscheduler(-100, SCHED_FIFO, ¶m), EINVAL); + TEST_ERRNO(sched_setscheduler(1234567890, SCHED_FIFO, ¶m), ESRCH); + TEST_RES(sched_setscheduler(0, SCHED_FIFO, ¶m), _ret == 0); + sleep(1); + + TEST_RES(sched_getscheduler(0), _ret == SCHED_FIFO); + TEST_RES(sched_getparam(0, ¶m), + _ret == 0 && param.sched_priority == 50); + + param.sched_priority = 1234567890; + TEST_ERRNO(sched_setparam(0, NULL), EINVAL); + TEST_ERRNO(sched_setparam(-100, ¶m), EINVAL); + TEST_ERRNO(sched_setparam(1234567890, ¶m), ESRCH); + TEST_ERRNO(sched_setparam(0, ¶m), EINVAL); + param.sched_priority = 51; + TEST_RES(sched_setparam(0, ¶m), _ret == 0); + sleep(1); + + TEST_RES(sched_getparam(0, ¶m), + _ret == 0 && param.sched_priority == 51); +} +END_TEST() + +FN_TEST(sched_prio_limit) +{ + TEST_ERRNO(sched_get_priority_max(-100), EINVAL); + TEST_ERRNO(sched_get_priority_max(1234567890), EINVAL); + TEST_ERRNO(sched_get_priority_min(-100), EINVAL); + TEST_ERRNO(sched_get_priority_min(1234567890), EINVAL); + + TEST_RES(sched_get_priority_max(SCHED_OTHER), _ret == 0); + TEST_RES(sched_get_priority_min(SCHED_OTHER), _ret == 0); + + TEST_RES(sched_get_priority_max(SCHED_FIFO), _ret == 99); + TEST_RES(sched_get_priority_min(SCHED_FIFO), _ret == 1); + + TEST_RES(sched_get_priority_max(SCHED_RR), _ret == 99); + TEST_RES(sched_get_priority_min(SCHED_RR), _ret == 1); + +#ifdef __USE_GNU + TEST_RES(sched_get_priority_max(SCHED_IDLE), _ret == 0); + TEST_RES(sched_get_priority_min(SCHED_IDLE), _ret == 0); +#endif +} +END_TEST() \ No newline at end of file diff --git a/test/apps/scripts/process.sh b/test/apps/scripts/process.sh index ae263abe0..25d71231e 100755 --- a/test/apps/scripts/process.sh +++ b/test/apps/scripts/process.sh @@ -30,6 +30,7 @@ mmap/mmap_shared_filebacked mmap/mmap_readahead pthread/pthread_test pty/open_pty +sched/sched_attr shm/posix_shm signal_c/parent_death_signal signal_c/signal_test diff --git a/test/syscall_test/Makefile b/test/syscall_test/Makefile index 4378c54a9..e95686f7d 100644 --- a/test/syscall_test/Makefile +++ b/test/syscall_test/Makefile @@ -38,6 +38,8 @@ TESTS ?= \ readv_test \ rename_test \ rlimits_test \ + sched_test \ + sched_yield_test \ semaphore_test \ sendfile_test \ sigaltstack_test \