diff --git a/Cargo.lock b/Cargo.lock index c380aa73..4188a816 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,8 +199,11 @@ dependencies = [ "aster-time", "aster-util", "aster-virtio", + "atomic", "bitflags 1.3.2", "bitvec", + "bytemuck", + "bytemuck_derive", "controlled", "core2", "cpio-decoder", @@ -308,6 +311,15 @@ dependencies = [ "x86_64", ] +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-polyfill" version = "0.1.11" @@ -364,9 +376,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" dependencies = [ "bytemuck_derive", ] diff --git a/kernel/aster-nix/Cargo.toml b/kernel/aster-nix/Cargo.toml index 3f2ada40..45f88f69 100644 --- a/kernel/aster-nix/Cargo.toml +++ b/kernel/aster-nix/Cargo.toml @@ -65,6 +65,9 @@ bitvec = { version = "1.0", default-features = false, features = ["alloc"] } static_assertions = "1.1.0" inherit-methods-macro = { git = "https://github.com/asterinas/inherit-methods-macro", rev = "98f7e3e" } getset = "0.1.2" +atomic = "0.6" +bytemuck = "1.14.3" +bytemuck_derive = "1.5.0" [dependencies.lazy_static] version = "1.0" diff --git a/kernel/aster-nix/src/fs/procfs/mod.rs b/kernel/aster-nix/src/fs/procfs/mod.rs index 2c0833a1..ebf39049 100644 --- a/kernel/aster-nix/src/fs/procfs/mod.rs +++ b/kernel/aster-nix/src/fs/procfs/mod.rs @@ -112,8 +112,7 @@ impl DirOps for RootDirOps { let mut cached_children = this.cached_children().write(); cached_children.put_entry_if_not_found("self", || SelfSymOps::new_inode(this_ptr.clone())); - let processes = process_table::get_all_processes(); - for process in processes { + for process in process_table::process_table().iter() { let pid = process.pid().to_string(); cached_children.put_entry_if_not_found(&pid, || { PidDirOps::new_inode(process.clone(), this_ptr.clone()) diff --git a/kernel/aster-nix/src/process/clone.rs b/kernel/aster-nix/src/process/clone.rs index 7f2ab8bc..a9108c17 100644 --- a/kernel/aster-nix/src/process/clone.rs +++ b/kernel/aster-nix/src/process/clone.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +use core::sync::atomic::Ordering; + use aster_frame::{cpu::UserContext, user::UserSpace, vm::VmIo}; use aster_rights::Full; @@ -260,6 +262,9 @@ fn clone_child_process(parent_context: UserContext, clone_args: CloneArgs) -> Re *sigmask }; + // inherit parent's nice value + let child_nice = current.nice().load(Ordering::Relaxed); + let child_tid = allocate_tid(); let child = { @@ -286,7 +291,8 @@ fn clone_child_process(parent_context: UserContext, clone_args: CloneArgs) -> Re .file_table(child_file_table) .fs(child_fs) .umask(child_umask) - .sig_dispositions(child_sig_dispositions); + .sig_dispositions(child_sig_dispositions) + .nice(child_nice); process_builder.build()? }; diff --git a/kernel/aster-nix/src/process/kill.rs b/kernel/aster-nix/src/process/kill.rs index 6b737239..2222a9aa 100644 --- a/kernel/aster-nix/src/process/kill.rs +++ b/kernel/aster-nix/src/process/kill.rs @@ -89,13 +89,12 @@ pub fn tgkill(tid: Tid, tgid: Pid, signal: Option) -> Result<()> { /// if it is authorized to send the signal to the target group. pub fn kill_all(signal: Option) -> Result<()> { let current = current!(); - let processes = process_table::get_all_processes(); - for process in processes { - if Arc::ptr_eq(¤t, &process) || process.is_init_process() { + for process in process_table::process_table().iter() { + if Arc::ptr_eq(¤t, process) || process.is_init_process() { continue; } - kill_process(&process, signal)?; + kill_process(process, signal)?; } Ok(()) diff --git a/kernel/aster-nix/src/process/posix_thread/mod.rs b/kernel/aster-nix/src/process/posix_thread/mod.rs index ccdfc01c..1cf9a022 100644 --- a/kernel/aster-nix/src/process/posix_thread/mod.rs +++ b/kernel/aster-nix/src/process/posix_thread/mod.rs @@ -246,7 +246,7 @@ impl PosixThread { } /// Gets the read-only credentials of the thread. - pub(in crate::process) fn credentials(&self) -> Credentials { + pub fn credentials(&self) -> Credentials { self.credentials.dup().restrict() } diff --git a/kernel/aster-nix/src/process/process/builder.rs b/kernel/aster-nix/src/process/process/builder.rs index b1fce90b..3408e7e3 100644 --- a/kernel/aster-nix/src/process/process/builder.rs +++ b/kernel/aster-nix/src/process/process/builder.rs @@ -11,6 +11,7 @@ use crate::{ signal::sig_disposition::SigDispositions, Credentials, }, + sched::nice::Nice, thread::Thread, }; @@ -31,6 +32,7 @@ pub struct ProcessBuilder<'a> { resource_limits: Option, sig_dispositions: Option>>, credentials: Option, + nice: Option, } impl<'a> ProcessBuilder<'a> { @@ -49,6 +51,7 @@ impl<'a> ProcessBuilder<'a> { resource_limits: None, sig_dispositions: None, credentials: None, + nice: None, } } @@ -102,6 +105,11 @@ impl<'a> ProcessBuilder<'a> { self } + pub fn nice(&mut self, nice: Nice) -> &mut Self { + self.nice = Some(nice); + self + } + fn check_build(&self) -> Result<()> { if self.main_thread_builder.is_some() { debug_assert!(self.parent.upgrade().is_some()); @@ -136,6 +144,7 @@ impl<'a> ProcessBuilder<'a> { resource_limits, sig_dispositions, credentials, + nice, } = self; let process_vm = process_vm.or_else(|| Some(ProcessVm::alloc())).unwrap(); @@ -160,6 +169,8 @@ impl<'a> ProcessBuilder<'a> { .or_else(|| Some(Arc::new(Mutex::new(SigDispositions::new())))) .unwrap(); + let nice = nice.or_else(|| Some(Nice::default())).unwrap(); + let process = { let threads = Vec::new(); Arc::new(Process::new( @@ -173,6 +184,7 @@ impl<'a> ProcessBuilder<'a> { umask, sig_dispositions, resource_limits, + nice, )) }; diff --git a/kernel/aster-nix/src/process/process/mod.rs b/kernel/aster-nix/src/process/process/mod.rs index d16c2238..7792c97f 100644 --- a/kernel/aster-nix/src/process/process/mod.rs +++ b/kernel/aster-nix/src/process/process/mod.rs @@ -16,6 +16,7 @@ use crate::{ device::tty::open_ntty_as_controlling_terminal, fs::{file_table::FileTable, fs_resolver::FsResolver, utils::FileCreationMask}, prelude::*, + sched::nice::Nice, thread::{allocate_tid, Thread}, vm::vmar::Vmar, }; @@ -27,6 +28,7 @@ mod session; mod terminal; use aster_rights::Full; +use atomic::Atomic; pub use builder::ProcessBuilder; pub use job_control::JobControl; pub use process_group::ProcessGroup; @@ -72,6 +74,10 @@ pub struct Process { umask: Arc>, /// resource limits resource_limits: Mutex, + /// Scheduling priority nice value + /// According to POSIX.1, the nice value is a per-process attribute, + /// the threads in a process should share a nice value. + nice: Atomic, // Signal /// Sig dispositions @@ -91,6 +97,7 @@ impl Process { umask: Arc>, sig_dispositions: Arc>, resource_limits: ResourceLimits, + nice: Nice, ) -> Self { let children_pauser = { // SIGCHID does not interrupt pauser. Child process will @@ -114,6 +121,7 @@ impl Process { umask, sig_dispositions, resource_limits: Mutex::new(resource_limits), + nice: Atomic::new(nice), } } @@ -206,7 +214,11 @@ impl Process { &self.resource_limits } - fn main_thread(&self) -> Option> { + pub fn nice(&self) -> &Atomic { + &self.nice + } + + pub fn main_thread(&self) -> Option> { self.threads .lock() .iter() @@ -602,6 +614,7 @@ mod test { Arc::new(RwLock::new(FileCreationMask::default())), Arc::new(Mutex::new(SigDispositions::default())), ResourceLimits::default(), + Nice::default(), )) } diff --git a/kernel/aster-nix/src/process/process/process_group.rs b/kernel/aster-nix/src/process/process/process_group.rs index b0d7e056..eeaa7bdd 100644 --- a/kernel/aster-nix/src/process/process/process_group.rs +++ b/kernel/aster-nix/src/process/process/process_group.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +use alloc::collections::btree_map::Values; + use super::{Pgid, Pid, Process, Session}; use crate::{prelude::*, process::signal::signals::Signal}; @@ -68,6 +70,13 @@ impl ProcessGroup { self.pgid } + /// Acquires a lock on the process group. + pub fn lock(&self) -> ProcessGroupGuard { + ProcessGroupGuard { + inner: self.inner.lock(), + } + } + /// Broadcasts signal to all processes in the group. /// /// This method should only be used to broadcast fault signal and kernel signal. @@ -89,3 +98,32 @@ impl ProcessGroup { self.inner.lock().session.upgrade() } } + +/// A scoped lock for a process group. +/// +/// It provides some public methods to prevent the exposure of the inner type. +pub struct ProcessGroupGuard<'a> { + inner: MutexGuard<'a, Inner>, +} + +impl<'a> ProcessGroupGuard<'a> { + /// Returns an iterator over the processes in the group. + pub fn iter(&self) -> ProcessGroupIter { + ProcessGroupIter { + inner: self.inner.processes.values(), + } + } +} + +/// An iterator over the processes of the process group. +pub struct ProcessGroupIter<'a> { + inner: Values<'a, Pid, Arc>, +} + +impl<'a> Iterator for ProcessGroupIter<'a> { + type Item = &'a Arc; + + fn next(&mut self) -> Option { + self.inner.next() + } +} diff --git a/kernel/aster-nix/src/process/process_table.rs b/kernel/aster-nix/src/process/process_table.rs index 499e8b3c..a1c5c165 100644 --- a/kernel/aster-nix/src/process/process_table.rs +++ b/kernel/aster-nix/src/process/process_table.rs @@ -4,6 +4,8 @@ //! This table can be used to get process with pid. //! TODO: progress group, thread all need similar mapping +use alloc::collections::btree_map::Values; + use super::{Pgid, Pid, Process, ProcessGroup, Session, Sid}; use crate::{ events::{Events, Observer, Subject}, @@ -26,13 +28,40 @@ pub(super) fn process_table_mut() -> MutexGuard<'static, BTreeMap Vec> { - PROCESS_TABLE - .lock() - .iter() - .map(|(_, process)| process.clone()) - .collect() +/// Acquires a lock on the process table and returns a `ProcessTable`. +pub fn process_table() -> ProcessTable<'static> { + ProcessTable { + inner: PROCESS_TABLE.lock(), + } +} + +/// A wrapper for the mutex-protected process table. +/// +/// It provides the `iter` method to iterator over the processes in the table. +pub struct ProcessTable<'a> { + inner: MutexGuard<'a, BTreeMap>>, +} + +impl<'a> ProcessTable<'a> { + /// Returns an iterator over the processes in the table. + pub fn iter(&self) -> ProcessTableIter { + ProcessTableIter { + inner: self.inner.values(), + } + } +} + +/// An iterator over the processes of the process table. +pub struct ProcessTableIter<'a> { + inner: Values<'a, Pid, Arc>, +} + +impl<'a> Iterator for ProcessTableIter<'a> { + type Item = &'a Arc; + + fn next(&mut self) -> Option { + self.inner.next() + } } // ************ Process Group ************* diff --git a/kernel/aster-nix/src/sched/mod.rs b/kernel/aster-nix/src/sched/mod.rs index bfa1437c..60593247 100644 --- a/kernel/aster-nix/src/sched/mod.rs +++ b/kernel/aster-nix/src/sched/mod.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 +pub mod nice; mod priority_scheduler; // There may be multiple scheduling policies in the system, diff --git a/kernel/aster-nix/src/sched/nice.rs b/kernel/aster-nix/src/sched/nice.rs new file mode 100644 index 00000000..837e4212 --- /dev/null +++ b/kernel/aster-nix/src/sched/nice.rs @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MPL-2.0 + +use bytemuck_derive::NoUninit; + +use crate::prelude::*; + +/// The process scheduling nice value. +/// +/// The nice value is an attribute that can be used to influence the +/// CPU scheduler to favor or disfavor a process in scheduling decisions. +/// +/// It is a value in the range -20 to 19, with -20 being the highest priority +/// and 19 being the lowest priority. The smaller values give a process a higher +/// scheduling priority. +#[repr(transparent)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, NoUninit)] +pub struct Nice { + value: i8, +} + +impl Nice { + /// The minimum nice, whose value is -20. + pub const MIN: Self = Self { value: -20 }; + + /// The maximum nice, whose value is 19. + pub const MAX: Self = Self { value: 19 }; + + /// Creates a new `Nice` from the raw value. + /// + /// Values given beyond the permissible range are automatically adjusted + /// to the nearest boundary value. + pub fn new(raw: i8) -> Self { + if raw > Self::MAX.to_raw() { + Self::MAX + } else if raw < Self::MIN.to_raw() { + Self::MIN + } else { + Self { value: raw } + } + } + + /// Converts to the raw value. + pub fn to_raw(self) -> i8 { + self.value + } +} + +#[allow(clippy::derivable_impls)] +impl Default for Nice { + fn default() -> Self { + Self { + // The default nice value is 0 + value: 0, + } + } +} + +impl From for Nice { + fn from(priority: Priority) -> Self { + Self { + value: 20 - priority.to_raw() as i8, + } + } +} + +/// The process scheduling priority value. +/// +/// It is a value in the range 1 (corresponding to a nice value of 19) +/// to 40 (corresponding to a nice value of -20), with 1 being the lowest priority +/// and 40 being the highest priority. The greater values give a process a higher +/// scheduling priority. +#[repr(transparent)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, NoUninit)] +pub struct Priority { + value: u8, +} + +impl Priority { + /// The minimum priority, whose value is 1. + pub const MIN: Self = Self { value: 1 }; + + /// The maximum priority, whose value is 40. + pub const MAX: Self = Self { value: 40 }; + + /// Creates a new `Priority` from the raw value. + /// + /// Values given beyond the permissible range are automatically adjusted + /// to the nearest boundary value. + pub fn new(raw: u8) -> Self { + if raw > Self::MAX.to_raw() { + Self::MAX + } else if raw < Self::MIN.to_raw() { + Self::MIN + } else { + Self { value: raw } + } + } + + /// Converts to the raw value. + pub fn to_raw(self) -> u8 { + self.value + } +} + +impl From for Priority { + fn from(nice: Nice) -> Self { + Self { + value: (20 - nice.to_raw()) as u8, + } + } +} diff --git a/kernel/aster-nix/src/syscall/mod.rs b/kernel/aster-nix/src/syscall/mod.rs index af227248..16052d7e 100644 --- a/kernel/aster-nix/src/syscall/mod.rs +++ b/kernel/aster-nix/src/syscall/mod.rs @@ -72,6 +72,7 @@ use crate::{ rt_sigreturn::sys_rt_sigreturn, sched_yield::sys_sched_yield, select::sys_select, + set_get_priority::{sys_get_priority, sys_set_priority}, set_robust_list::sys_set_robust_list, set_tid_address::sys_set_tid_address, setpgid::sys_setpgid, @@ -163,6 +164,7 @@ mod rt_sigreturn; mod sched_yield; mod select; mod sendto; +mod set_get_priority; mod set_robust_list; mod set_tid_address; mod setfsgid; @@ -321,6 +323,8 @@ define_syscall_nums!( SYS_SIGALTSTACK = 131, SYS_STATFS = 137, SYS_FSTATFS = 138, + SYS_GET_PRIORITY = 140, + SYS_SET_PRIORITY = 141, SYS_PRCTL = 157, SYS_ARCH_PRCTL = 158, SYS_SYNC = 162, @@ -505,6 +509,8 @@ pub fn syscall_dispatch( SYS_SIGALTSTACK => syscall_handler!(2, sys_sigaltstack, args), SYS_STATFS => syscall_handler!(2, sys_statfs, args), SYS_FSTATFS => syscall_handler!(2, sys_fstatfs, args), + SYS_GET_PRIORITY => syscall_handler!(2, sys_get_priority, args), + SYS_SET_PRIORITY => syscall_handler!(3, sys_set_priority, args), SYS_PRCTL => syscall_handler!(5, sys_prctl, args), SYS_ARCH_PRCTL => syscall_handler!(2, sys_arch_prctl, args, context), SYS_SYNC => syscall_handler!(0, sys_sync), diff --git a/kernel/aster-nix/src/syscall/set_get_priority.rs b/kernel/aster-nix/src/syscall/set_get_priority.rs new file mode 100644 index 00000000..c849c5c1 --- /dev/null +++ b/kernel/aster-nix/src/syscall/set_get_priority.rs @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::sync::atomic::Ordering; + +use super::{SyscallReturn, SYS_GET_PRIORITY, SYS_SET_PRIORITY}; +use crate::{ + log_syscall_entry, + prelude::*, + process::{credentials, posix_thread::PosixThreadExt, process_table, Pgid, Pid, Process, Uid}, + sched::nice::Nice, +}; + +pub fn sys_set_priority(which: i32, who: u32, prio: i32) -> Result { + log_syscall_entry!(SYS_SET_PRIORITY); + let prio_target = PriorityTarget::new(which, who)?; + let new_nice = { + let norm_prio = if prio > i8::MAX as i32 { + i8::MAX + } else if prio < i8::MIN as i32 { + i8::MIN + } else { + prio as i8 + }; + Nice::new(norm_prio) + }; + + 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) -> Result { + log_syscall_entry!(SYS_GET_PRIORITY); + let prio_target = PriorityTarget::new(which, who)?; + debug!("get_priority prio_target: {:?}", prio_target); + + let processes = get_processes(prio_target)?; + let highest_prio = { + let mut nice = Nice::MAX; + for process in processes.iter() { + let proc_nice = process.nice().load(Ordering::Relaxed); + // Returns the highest priority enjoyed by the processes + if proc_nice < nice { + nice = proc_nice; + } + } + + // The system call returns nice values translated to the range 40 to 1, + // since a negative return value would be interpreted as an error. + 20 - nice.to_raw() + }; + + Ok(SyscallReturn::Return(highest_prio as _)) +} + +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))?; + vec![process] + } + PriorityTarget::ProcessGroup(pgid) => { + let process_group = + process_table::get_process_group(&pgid).ok_or(Error::new(Errno::ESRCH))?; + let processes: Vec> = process_group.lock().iter().cloned().collect(); + if processes.is_empty() { + return_errno!(Errno::ESRCH); + } + processes + } + PriorityTarget::User(uid) => { + // Get the processes that are running under the specified user + let processes: Vec> = process_table::process_table() + .iter() + .filter(|process| { + let Some(main_thread) = process.main_thread() else { + return false; + }; + let Some(posix_thread) = main_thread.as_posix_thread() else { + return false; + }; + uid == posix_thread.credentials().ruid() + }) + .cloned() + .collect(); + if processes.is_empty() { + return_errno!(Errno::ESRCH); + } + processes + } + }) +} + +#[derive(Debug)] +enum PriorityTarget { + Process(Pid), + ProcessGroup(Pgid), + User(Uid), +} + +impl PriorityTarget { + fn new(which: i32, who: u32) -> Result { + let which = Which::try_from(which) + .map_err(|_| Error::with_message(Errno::EINVAL, "invalid which value"))?; + Ok(match which { + Which::PRIO_PROCESS => { + let pid = if who == 0 { + current!().pid() + } else { + who as Pid + }; + Self::Process(pid) + } + Which::PRIO_PGRP => { + let pgid = if who == 0 { + current!().pgid() + } else { + who as Pgid + }; + Self::ProcessGroup(pgid) + } + Which::PRIO_USER => { + let uid = if who == 0 { + credentials().ruid() + } else { + Uid::new(who) + }; + Self::User(uid) + } + }) + } +} + +#[allow(non_camel_case_types)] +#[derive(Clone, Debug, TryFromInt)] +#[repr(i32)] +enum Which { + PRIO_PROCESS = 0, + PRIO_PGRP = 1, + PRIO_USER = 2, +}