diff --git a/services/libs/jinux-std/src/process/clone.rs b/services/libs/jinux-std/src/process/clone.rs index e0d9b4dbd..eab642640 100644 --- a/services/libs/jinux-std/src/process/clone.rs +++ b/services/libs/jinux-std/src/process/clone.rs @@ -275,15 +275,31 @@ 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) - .process_group(current.process_group().unwrap()); + .sig_dispositions(child_sig_dispositions); process_builder.build()? }; - current!().add_child(child.clone()); - process_table::add_process(child.clone()); + // Adds child to parent's process group, and parent's child processes. + let process_group = current!().process_group().unwrap(); + let mut process_table_mut = process_table::process_table_mut(); + let mut group_inner = process_group.inner.lock(); + let mut child_group_mut = child.process_group.lock(); + let mut children_mut = current.children().lock(); + + children_mut.insert(child.pid(), child.clone()); + + group_inner.processes.insert(child.pid(), child.clone()); + *child_group_mut = Arc::downgrade(&process_group); + + process_table_mut.insert(child.pid(), child.clone()); + drop(process_table_mut); + drop(group_inner); + drop(child_group_mut); + drop(children_mut); + + // Deals with clone flags let child_thread = thread_table::tid_to_thread(child_tid).unwrap(); let child_posix_thread = child_thread.as_posix_thread().unwrap(); clone_parent_settid(child_tid, clone_args.parent_tidptr, clone_flags)?; diff --git a/services/libs/jinux-std/src/process/exit.rs b/services/libs/jinux-std/src/process/exit.rs index 8857923f9..2bd217f71 100644 --- a/services/libs/jinux-std/src/process/exit.rs +++ b/services/libs/jinux-std/src/process/exit.rs @@ -37,9 +37,11 @@ pub fn do_exit_group(term_status: TermStatus) { // Move children to the init process if !is_init_process(¤t) { if let Some(init_process) = get_init_process() { + let mut init_children = init_process.children().lock(); for (_, child_process) in current.children().lock().extract_if(|_, _| true) { - child_process.set_parent(Arc::downgrade(&init_process)); - init_process.add_child(child_process); + let mut parent = child_process.parent.lock(); + init_children.insert(child_process.pid(), child_process.clone()); + *parent = Arc::downgrade(&init_process); } } } @@ -56,7 +58,7 @@ const INIT_PROCESS_PID: Pid = 1; /// Get the init process fn get_init_process() -> Option> { - process_table::pid_to_process(INIT_PROCESS_PID) + process_table::get_process(&INIT_PROCESS_PID) } fn is_init_process(process: &Process) -> bool { diff --git a/services/libs/jinux-std/src/process/mod.rs b/services/libs/jinux-std/src/process/mod.rs index b467a525a..5aa8884a8 100644 --- a/services/libs/jinux-std/src/process/mod.rs +++ b/services/libs/jinux-std/src/process/mod.rs @@ -4,7 +4,6 @@ pub mod posix_thread; #[allow(clippy::module_inception)] mod process; mod process_filter; -mod process_group; pub mod process_table; mod process_vm; mod program_loader; @@ -17,9 +16,10 @@ mod wait; pub use clone::{clone_child, CloneArgs, CloneFlags}; pub use exit::do_exit_group; pub use process::ProcessBuilder; -pub use process::{current, ExitCode, Pgid, Pid, Process}; +pub use process::{ + current, ExitCode, JobControl, Pgid, Pid, Process, ProcessGroup, Session, Sid, Terminal, +}; pub use process_filter::ProcessFilter; -pub use process_group::ProcessGroup; pub use program_loader::{check_executable_file, load_program_to_vm}; pub use rlimit::ResourceType; pub use term_status::TermStatus; diff --git a/services/libs/jinux-std/src/process/process/builder.rs b/services/libs/jinux-std/src/process/process/builder.rs index def036130..dd49dcc39 100644 --- a/services/libs/jinux-std/src/process/process/builder.rs +++ b/services/libs/jinux-std/src/process/process/builder.rs @@ -1,12 +1,10 @@ use crate::fs::file_table::FileTable; use crate::fs::fs_resolver::FsResolver; use crate::fs::utils::FileCreationMask; -use crate::process::posix_thread::PosixThreadBuilder; -use crate::process::process_group::ProcessGroup; -use crate::process::process_table; +use crate::process::posix_thread::{PosixThreadBuilder, PosixThreadExt}; use crate::process::process_vm::ProcessVm; use crate::process::rlimit::ResourceLimits; -use crate::process::{posix_thread::PosixThreadExt, signal::sig_disposition::SigDispositions}; +use crate::process::signal::sig_disposition::SigDispositions; use crate::thread::Thread; use super::{Pid, Process}; @@ -23,7 +21,6 @@ pub struct ProcessBuilder<'a> { argv: Option>, envp: Option>, process_vm: Option, - process_group: Option>, file_table: Option>>, fs: Option>>, umask: Option>>, @@ -41,7 +38,6 @@ impl<'a> ProcessBuilder<'a> { argv: None, envp: None, process_vm: None, - process_group: None, file_table: None, fs: None, umask: None, @@ -60,11 +56,6 @@ impl<'a> ProcessBuilder<'a> { self } - pub fn process_group(&mut self, process_group: Arc) -> &mut Self { - self.process_group = Some(process_group); - self - } - pub fn file_table(&mut self, file_table: Arc>) -> &mut Self { self.file_table = Some(file_table); self @@ -126,7 +117,6 @@ impl<'a> ProcessBuilder<'a> { argv, envp, process_vm, - process_group, file_table, fs, umask, @@ -136,10 +126,6 @@ impl<'a> ProcessBuilder<'a> { let process_vm = process_vm.or_else(|| Some(ProcessVm::alloc())).unwrap(); - let process_group_ref = process_group - .as_ref() - .map_or_else(Weak::new, Arc::downgrade); - let file_table = file_table .or_else(|| Some(Arc::new(Mutex::new(FileTable::new_with_stdio())))) .unwrap(); @@ -168,7 +154,6 @@ impl<'a> ProcessBuilder<'a> { threads, executable_path.to_string(), process_vm, - process_group_ref, file_table, fs, umask, @@ -194,15 +179,6 @@ impl<'a> ProcessBuilder<'a> { process.threads().lock().push(thread); - if let Some(process_group) = process_group { - process_group.add_process(process.clone()); - } else { - let new_process_group = Arc::new(ProcessGroup::new(process.clone())); - let pgid = new_process_group.pgid(); - process.set_process_group(Arc::downgrade(&new_process_group)); - process_table::add_process_group(new_process_group); - } - process.set_runnable(); Ok(process) diff --git a/services/libs/jinux-std/src/process/process/job_control.rs b/services/libs/jinux-std/src/process/process/job_control.rs new file mode 100644 index 000000000..d3e81ecee --- /dev/null +++ b/services/libs/jinux-std/src/process/process/job_control.rs @@ -0,0 +1,140 @@ +use crate::prelude::*; +use crate::process::signal::constants::{SIGCONT, SIGHUP}; +use crate::process::signal::signals::kernel::KernelSignal; +use crate::process::{ProcessGroup, Session}; + +/// The job control for terminals like tty and pty. +/// +/// This struct is used to support shell job control, which allows users to +/// run commands in the foreground or in the background. This struct manages +/// the session and foreground process group for a terminal. +pub struct JobControl { + foreground: SpinLock>, + session: SpinLock>, +} + +impl JobControl { + /// Creates a new `TtyJobControl` + pub fn new() -> Self { + Self { + foreground: SpinLock::new(Weak::new()), + session: SpinLock::new(Weak::new()), + } + } + + // *************** Session *************** + + /// Returns the session whose controlling terminal is the terminal. + fn session(&self) -> Option> { + self.session.lock().upgrade() + } + + /// Sets the terminal as the controlling terminal of the `session`. + /// + /// # Panic + /// + /// This terminal should not belong to any session. + pub fn set_session(&self, session: &Arc) { + debug_assert!(self.session().is_none()); + *self.session.lock() = Arc::downgrade(session); + } + + /// Sets the terminal as the controlling terminal of the session of current process. + /// + /// # Panic + /// + /// This function should only be called in process context. + pub fn set_current_session(&self) -> Result<()> { + if self.session().is_some() { + return_errno_with_message!( + Errno::EPERM, + "the terminal is already controlling terminal of another session" + ); + } + + let current = current!(); + + let process_group = current.process_group().unwrap(); + *self.foreground.lock() = Arc::downgrade(&process_group); + + let session = current.session().unwrap(); + *self.session.lock() = Arc::downgrade(&session); + + Ok(()) + } + + /// Releases the current session from this terminal. + pub fn release_current_session(&self) -> Result<()> { + let Some(session) = self.session() else { + return_errno_with_message!( + Errno::ENOTTY, + "the terminal is not controlling terminal now" + ); + }; + + if let Some(foreground) = self.foreground() { + foreground.kernel_signal(KernelSignal::new(SIGHUP)); + foreground.kernel_signal(KernelSignal::new(SIGCONT)); + } + + Ok(()) + } + + // *************** Foreground process group *************** + + /// Returns the foreground process group + pub fn foreground(&self) -> Option> { + self.foreground.lock().upgrade() + } + + /// Sets the foreground process group. + /// + /// # Panic + /// + /// The process group should belong to one session. + pub fn set_foreground(&self, process_group: Option<&Arc>) -> Result<()> { + let Some(process_group) = process_group else { + // FIXME: should we allow this branch? + *self.foreground.lock() = Weak::new(); + return Ok(()); + }; + + let session = process_group.session().unwrap(); + let Some(terminal_session) = self.session() else { + return_errno_with_message!( + Errno::EPERM, + "the terminal does not become controlling terminal of one session." + ); + }; + + if !Arc::ptr_eq(&terminal_session, &session) { + return_errno_with_message!( + Errno::EPERM, + "the process proup belongs to different session" + ); + } + + *self.foreground.lock() = Arc::downgrade(process_group); + Ok(()) + } + + /// Determines whether current process belongs to the foreground process group. If + /// the foreground process group is None, returns true. + /// + /// # Panic + /// + /// This function should only be called in process context. + pub fn current_belongs_to_foreground(&self) -> bool { + let Some(foreground) = self.foreground() else { + return true; + }; + + foreground.contains_process(current!().pid()) + } +} + +impl Default for JobControl { + fn default() -> Self { + Self::new() + } +} diff --git a/services/libs/jinux-std/src/process/process/mod.rs b/services/libs/jinux-std/src/process/process/mod.rs index 476786124..20947bafe 100644 --- a/services/libs/jinux-std/src/process/process/mod.rs +++ b/services/libs/jinux-std/src/process/process/mod.rs @@ -1,7 +1,4 @@ -mod builder; - use super::posix_thread::PosixThreadExt; -use super::process_group::ProcessGroup; use super::process_vm::user_heap::UserHeap; use super::process_vm::ProcessVm; use super::rlimit::ResourceLimits; @@ -13,7 +10,7 @@ use super::signal::signals::Signal; use super::signal::{Pauser, SigEvents, SigEventsFilter}; use super::status::ProcessStatus; use super::{process_table, TermStatus}; -use crate::device::tty::get_n_tty; +use crate::device::tty::open_ntty_as_controlling_terminal; use crate::events::Observer; use crate::fs::file_table::FileTable; use crate::fs::fs_resolver::FsResolver; @@ -23,10 +20,25 @@ use crate::thread::{allocate_tid, Thread}; use crate::vm::vmar::Vmar; use jinux_rights::Full; -pub use builder::ProcessBuilder; +mod builder; +mod job_control; +mod process_group; +mod session; +mod terminal; +pub use builder::ProcessBuilder; +pub use job_control::JobControl; +pub use process_group::ProcessGroup; +pub use session::Session; +pub use terminal::Terminal; + +/// Process id. pub type Pid = u32; +/// Process group id. pub type Pgid = u32; +/// Session Id. +pub type Sid = u32; + pub type ExitCode = i32; /// Process stands for a set of threads that shares the same userspace. @@ -46,11 +58,11 @@ pub struct Process { /// Process status status: Mutex, /// Parent process - parent: Mutex>, + pub(super) parent: Mutex>, /// Children processes children: Mutex>>, /// Process group - process_group: Mutex>, + pub(super) process_group: Mutex>, /// File table file_table: Arc>, /// FsResolver @@ -75,7 +87,6 @@ impl Process { threads: Vec>, executable_path: String, process_vm: ProcessVm, - process_group: Weak, file_table: Arc>, fs: Arc>, umask: Arc>, @@ -99,7 +110,7 @@ impl Process { status: Mutex::new(ProcessStatus::Uninit), parent: Mutex::new(parent), children: Mutex::new(BTreeMap::new()), - process_group: Mutex::new(process_group), + process_group: Mutex::new(Weak::new()), file_table, fs, umask, @@ -118,11 +129,9 @@ impl Process { // spawn user process should give an absolute path debug_assert!(executable_path.starts_with('/')); let process = Process::create_user_process(executable_path, argv, envp)?; - // FIXME: How to determine the fg process group? - let process_group = Weak::clone(&process.process_group.lock()); - // FIXME: tty should be a parameter? - let tty = get_n_tty(); - tty.set_fg(process_group); + + open_ntty_as_controlling_terminal(&process)?; + process.run(); Ok(process) } @@ -141,7 +150,23 @@ impl Process { }; let process = process_builder.build()?; - process_table::add_process(process.clone()); + + let mut session_table_mut = process_table::session_table_mut(); + let mut group_table_mut = process_table::group_table_mut(); + let mut process_table_mut = process_table::process_table_mut(); + + // Creates new group + let group = ProcessGroup::new(process.clone()); + *process.process_group.lock() = Arc::downgrade(&group); + group_table_mut.insert(group.pgid(), group.clone()); + + // Creates new session + let session = Session::new(group.clone()); + group.inner.lock().session = Arc::downgrade(&session); + session.inner.lock().leader = Some(process.clone()); + session_table_mut.insert(session.sid(), session); + + process_table_mut.insert(process.pid(), process.clone()); Ok(process) } @@ -180,30 +205,25 @@ impl Process { } // *********** Parent and child *********** - - pub fn add_child(&self, child: Arc) { - let child_pid = child.pid(); - self.children.lock().insert(child_pid, child); - } - - pub fn set_parent(&self, parent: Weak) { - *self.parent.lock() = parent; - } - pub fn parent(&self) -> Option> { self.parent.lock().upgrade() } - pub fn children(&self) -> &Mutex>> { + pub(super) fn children(&self) -> &Mutex>> { &self.children } + pub fn has_child(&self, pid: &Pid) -> bool { + self.children.lock().contains_key(pid) + } + pub fn children_pauser(&self) -> &Arc { &self.children_pauser } - // *********** Process group *********** + // *********** Process group & Session*********** + /// Returns the process group id of the process. pub fn pgid(&self) -> Pgid { if let Some(process_group) = self.process_group.lock().upgrade() { process_group.pgid() @@ -212,19 +232,219 @@ impl Process { } } - /// Set process group for current process. If old process group exists, - /// remove current process from old process group. - pub fn set_process_group(&self, process_group: Weak) { - if let Some(old_process_group) = self.process_group() { - old_process_group.remove_process(self.pid()); - } - *self.process_group.lock() = process_group; - } - + /// Returns the process group which the process belongs to. pub fn process_group(&self) -> Option> { self.process_group.lock().upgrade() } + /// Returns whether `self` is the leader of process group. + fn is_group_leader(self: &Arc) -> bool { + let Some(process_group) = self.process_group() else { + return false; + }; + + let Some(leader) = process_group.leader() else { + return false; + }; + + Arc::ptr_eq(self, &leader) + } + + /// Returns the session which the process belongs to. + pub fn session(&self) -> Option> { + let process_group = self.process_group()?; + process_group.session() + } + + /// Returns whether the process is session leader. + pub fn is_session_leader(self: &Arc) -> bool { + let session = self.session().unwrap(); + + let Some(leading_process) = session.leader() else { + return false; + }; + + Arc::ptr_eq(self, &leading_process) + } + + /// Moves the process to the new session. + /// + /// If the process is already session leader, this method does nothing. + /// + /// Otherwise, this method creates a new process group in a new session + /// and moves the process to the session, returning the new session. + /// + /// This method may return the following errors: + /// * `EPERM`, if the process is a process group leader, or some existing session + /// or process group has the same id as the process. + pub fn to_new_session(self: &Arc) -> Result> { + if self.is_session_leader() { + return Ok(self.session().unwrap()); + } + + if self.is_group_leader() { + return_errno_with_message!( + Errno::EPERM, + "process group leader cannot be moved to new session." + ); + } + + let session = self.session().unwrap(); + let mut session_inner = session.inner.lock(); + let mut self_group_mut = self.process_group.lock(); + let mut group_table_mut = process_table::group_table_mut(); + let mut session_table_mut = process_table::session_table_mut(); + + if session_table_mut.contains_key(&self.pid) { + return_errno_with_message!(Errno::EPERM, "cannot create new session"); + } + + if group_table_mut.contains_key(&self.pid) { + return_errno_with_message!(Errno::EPERM, "cannot create process group"); + } + + // Removes the process from session. + session_inner.remove_process(self); + + // Removes the process from old group + if let Some(old_group) = self.process_group() { + let mut group_inner = old_group.inner.lock(); + group_inner.remove_process(&self.pid); + *self_group_mut = Weak::new(); + + if group_inner.is_empty() { + group_table_mut.remove(&old_group.pgid()); + debug_assert!(session_inner.process_groups.contains_key(&old_group.pgid())); + session_inner.process_groups.remove(&old_group.pgid()); + + if session_inner.is_empty() { + session_table_mut.remove(&session.sid()); + } + } + } + + // Creates a new process group + let new_group = ProcessGroup::new(self.clone()); + *self_group_mut = Arc::downgrade(&new_group); + group_table_mut.insert(new_group.pgid(), new_group.clone()); + + // Creates a new session + let new_session = Session::new(new_group.clone()); + let mut new_group_inner = new_group.inner.lock(); + new_group_inner.session = Arc::downgrade(&new_session); + new_session.inner.lock().leader = Some(self.clone()); + session_table_mut.insert(new_session.sid(), new_session.clone()); + + Ok(new_session) + } + + /// Moves the process to other process group. + /// + /// * If the group already exists, the process and the group should belong to the same session. + /// * If the group does not exist, this method creates a new group for the process and move the + /// process to the group. The group is added to the session of the process. + /// + /// This method may return `EPERM` in following cases: + /// * The process is session leader; + /// * The group already exists, but the group does not belong to the same session as the process; + /// * The group does not exist, but `pgid` is not equal to `pid` of the process. + pub fn to_other_group(self: &Arc, pgid: Pgid) -> Result<()> { + // if the process already belongs to the process group + if self.pgid() == pgid { + return Ok(()); + } + + if self.is_session_leader() { + return_errno_with_message!(Errno::EPERM, "the process cannot be a session leader"); + } + + if let Some(process_group) = process_table::get_process_group(&pgid) { + let session = self.session().unwrap(); + if !session.contains_process_group(&process_group) { + return_errno_with_message!( + Errno::EPERM, + "the group and process does not belong to same session" + ); + } + self.to_specified_group(&process_group)?; + } else { + if pgid != self.pid() { + return_errno_with_message!( + Errno::EPERM, + "the new process group should have the same id as the process." + ); + } + + self.to_new_group()?; + } + + Ok(()) + } + + /// Creates a new process group and moves the process to the group. + /// + /// The new group will be added to the same session as the process. + fn to_new_group(self: &Arc) -> Result<()> { + let session = self.session().unwrap(); + let mut session_inner = session.inner.lock(); + let mut self_group_mut = self.process_group.lock(); + let mut group_table_mut = process_table::group_table_mut(); + + // Removes the process from old group + if let Some(old_group) = self.process_group() { + let mut group_inner = old_group.inner.lock(); + group_inner.remove_process(&self.pid); + *self_group_mut = Weak::new(); + + if group_inner.is_empty() { + group_table_mut.remove(&old_group.pgid()); + debug_assert!(session_inner.process_groups.contains_key(&old_group.pgid())); + // The old session won't be empty, since we will add a new group to the session. + session_inner.process_groups.remove(&old_group.pgid()); + } + } + + // Creates a new process group. Adds the new group to group table and session. + let new_group = ProcessGroup::new(self.clone()); + *self_group_mut = Arc::downgrade(&new_group); + + group_table_mut.insert(new_group.pgid(), new_group.clone()); + + session_inner + .process_groups + .insert(new_group.pgid(), new_group.clone()); + let mut new_group_inner = new_group.inner.lock(); + new_group_inner.session = Arc::downgrade(&session); + + Ok(()) + } + + /// Moves the process to a specified group. + /// + /// The caller needs to ensure that the process and the group belongs to the same session. + fn to_specified_group(self: &Arc, group: &Arc) -> Result<()> { + let mut self_group_mut = self.process_group.lock(); + let mut group_table_mut = process_table::group_table_mut(); + let mut group_inner = group.inner.lock(); + + // Removes the process from old group + if let Some(old_group) = self.process_group() { + let mut group_inner = old_group.inner.lock(); + group_inner.remove_process(&self.pid); + *self_group_mut = Weak::new(); + + if group_inner.is_empty() { + group_table_mut.remove(&old_group.pgid()); + } + } + + // Adds the process to the specified group + group_inner.processes.insert(self.pid, self.clone()); + *self_group_mut = Arc::downgrade(group); + + Ok(()) + } + // ************** Virtual Memory ************* pub fn vm(&self) -> &ProcessVm { diff --git a/services/libs/jinux-std/src/process/process/process_group.rs b/services/libs/jinux-std/src/process/process/process_group.rs new file mode 100644 index 000000000..4da6c5d15 --- /dev/null +++ b/services/libs/jinux-std/src/process/process/process_group.rs @@ -0,0 +1,92 @@ +use super::{Pgid, Pid, Process, Session}; +use crate::prelude::*; +use crate::process::signal::signals::kernel::KernelSignal; +use crate::process::signal::signals::user::UserSignal; + +/// `ProcessGroup` represents a set of processes. Each `ProcessGroup` has a unique +/// identifier `pgid`. +pub struct ProcessGroup { + pgid: Pgid, + pub(in crate::process) inner: Mutex, +} + +pub(in crate::process) struct Inner { + pub(in crate::process) processes: BTreeMap>, + pub(in crate::process) leader: Option>, + pub(in crate::process) session: Weak, +} + +impl Inner { + pub(in crate::process) fn remove_process(&mut self, pid: &Pid) { + let Some(process) = self.processes.remove(pid) else { + return; + }; + + if let Some(leader) = &self.leader && Arc::ptr_eq(leader, &process) { + self.leader = None; + } + } + + pub(in crate::process) fn is_empty(&self) -> bool { + self.processes.is_empty() + } +} + +impl ProcessGroup { + /// Creates a new process group with one process. The pgid is the same as the process + /// id. The process will become the leading process of the new process group. + /// + /// The caller needs to ensure that the process does not belong to any group. + pub(super) fn new(process: Arc) -> Arc { + let pid = process.pid(); + + let inner = { + let mut processes = BTreeMap::new(); + processes.insert(pid, process.clone()); + Inner { + processes, + leader: Some(process.clone()), + session: Weak::new(), + } + }; + + Arc::new(ProcessGroup { + pgid: pid, + inner: Mutex::new(inner), + }) + } + + /// Returns whether self contains a process with `pid`. + pub(super) fn contains_process(&self, pid: Pid) -> bool { + self.inner.lock().processes.contains_key(&pid) + } + + /// Returns the process group identifier + pub fn pgid(&self) -> Pgid { + self.pgid + } + + /// Sends kernel signal to all processes in the group + pub fn kernel_signal(&self, signal: KernelSignal) { + for process in self.inner.lock().processes.values() { + process.enqueue_signal(Box::new(signal)); + } + } + + /// Sends user signal to all processes in the group + pub fn user_signal(&self, signal: UserSignal) { + for process in self.inner.lock().processes.values() { + process.enqueue_signal(Box::new(signal)); + } + } + + /// Returns the leader process. + pub(super) fn leader(&self) -> Option> { + self.inner.lock().leader.clone() + } + + /// Returns the session which the group belongs to + pub fn session(&self) -> Option> { + self.inner.lock().session.upgrade() + } +} diff --git a/services/libs/jinux-std/src/process/process/session.rs b/services/libs/jinux-std/src/process/process/session.rs new file mode 100644 index 000000000..6c956b657 --- /dev/null +++ b/services/libs/jinux-std/src/process/process/session.rs @@ -0,0 +1,125 @@ +use crate::prelude::*; + +use super::{Pgid, Process, ProcessGroup, Sid, Terminal}; + +/// A `Session` is a collection of related process groups. Each session has a +/// unique identifier `sid`. Process groups and sessions form a two-level +/// hierarchical relationship between processes. +/// +/// **Leader**: A *session leader* is the process that creates a new session and whose process +/// ID becomes the session ID. +/// +/// **Controlling terminal**: The terminal can be used to manage all processes in the session. The +/// controlling terminal is established when the session leader first opens a terminal. +pub struct Session { + sid: Sid, + pub(in crate::process) inner: Mutex, +} + +pub(in crate::process) struct Inner { + pub(in crate::process) process_groups: BTreeMap>, + pub(in crate::process) leader: Option>, + pub(in crate::process) terminal: Option>, +} + +impl Inner { + pub(in crate::process) fn is_empty(&self) -> bool { + self.process_groups.is_empty() + } + + pub(in crate::process) fn remove_process(&mut self, process: &Arc) { + if let Some(leader) = &self.leader && Arc::ptr_eq(leader, process) { + self.leader = None; + } + } + + pub(in crate::process) fn remove_process_group(&mut self, pgid: &Pgid) { + self.process_groups.remove(pgid); + } +} + +impl Session { + /// Creates a new session for the process group. The process group becomes the member of + /// the new session. + /// + /// The caller needs to ensure that the group does not belong to any session, and the caller + /// should set the leader process after creating the session. + pub(super) fn new(group: Arc) -> Arc { + let sid = group.pgid(); + let inner = { + let mut process_groups = BTreeMap::new(); + process_groups.insert(group.pgid(), group); + + Inner { + process_groups, + leader: None, + terminal: None, + } + }; + Arc::new(Self { + sid, + inner: Mutex::new(inner), + }) + } + + /// Returns the session id + pub fn sid(&self) -> Sid { + self.sid + } + + /// Returns the leader process. + pub(super) fn leader(&self) -> Option> { + self.inner.lock().leader.clone() + } + + /// Returns whether `self` contains the `process_group` + pub(super) fn contains_process_group( + self: &Arc, + process_group: &Arc, + ) -> bool { + self.inner + .lock() + .process_groups + .contains_key(&process_group.pgid()) + } + + /// Sets terminal as the controlling terminal of the session. + /// + /// If the session already has controlling terminal, this method will return `Err(EPERM)`. + pub fn set_terminal Result>>(&self, terminal: F) -> Result<()> { + let mut inner = self.inner.lock(); + + if inner.terminal.is_some() { + return_errno_with_message!( + Errno::EPERM, + "current session already has controlling terminal" + ); + } + + let terminal = terminal()?; + inner.terminal = Some(terminal); + Ok(()) + } + + /// Releases the controlling terminal of the session. + /// + /// If the session does not have controlling terminal, this method will return `ENOTTY`. + pub fn release_terminal Result<()>>(&self, release_session: F) -> Result<()> { + let mut inner = self.inner.lock(); + if inner.terminal.is_none() { + return_errno_with_message!( + Errno::ENOTTY, + "current session does not has controlling terminal" + ); + } + + release_session()?; + inner.terminal = None; + Ok(()) + } + + /// Returns the controlling terminal of `self`. + pub fn terminal(&self) -> Option> { + self.inner.lock().terminal.clone() + } +} diff --git a/services/libs/jinux-std/src/process/process/terminal.rs b/services/libs/jinux-std/src/process/process/terminal.rs new file mode 100644 index 000000000..309dd9574 --- /dev/null +++ b/services/libs/jinux-std/src/process/process/terminal.rs @@ -0,0 +1,104 @@ +use crate::fs::inode_handle::FileIo; +use crate::prelude::*; +use crate::process::{process_table, Pgid, ProcessGroup}; + +use super::JobControl; + +/// A termial is used to interact with system. A terminal can support the shell +/// job control. +/// +/// We currently support two kinds of terminal, the tty and pty. +pub trait Terminal: Send + Sync + FileIo { + // *************** Foreground *************** + + /// Returns the foreground process group + fn foreground(&self) -> Option> { + self.job_control().foreground() + } + + /// Sets the foreground process group of this terminal. + /// + /// If the terminal is not controlling terminal, this method returns `ENOTTY`. + /// + /// # Panic + /// + /// This method should be called in process context. + fn set_foreground(&self, pgid: &Pgid) -> Result<()> { + if !self.is_controlling_terminal() { + return_errno_with_message!(Errno::ENOTTY, "self is not controlling terminal"); + } + + let foreground = process_table::get_process_group(pgid); + + self.job_control().set_foreground(foreground.as_ref()) + } + + // *************** Session and controlling terminal *************** + + /// Returns whether the terminal is the controlling terminal of current process. + /// + /// # Panic + /// + /// This method should be called in process context. + fn is_controlling_terminal(&self) -> bool { + let session = current!().session().unwrap(); + let Some(terminal) = session.terminal() else { + return false; + }; + + let arc_self = self.arc_self(); + Arc::ptr_eq(&terminal, &arc_self) + } + + /// Sets the terminal as the controlling terminal of the session of current process. + /// + /// If self is not session leader, or the terminal is controlling terminal of other session, + /// or the session already has controlling terminal, this method returns `EPERM`. + /// + /// # Panic + /// + /// This method should only be called in process context. + fn set_current_session(&self) -> Result<()> { + if !current!().is_session_leader() { + return_errno_with_message!(Errno::EPERM, "current process is not session leader"); + } + + let terminal = || { + self.job_control().set_current_session()?; + Ok(self.arc_self()) + }; + + let session = current!().session().unwrap(); + session.set_terminal(terminal) + } + + /// Releases the terminal from the session of current process if the terminal is the controlling + /// terminal of the session. + /// + /// If the terminal is not the controlling terminal of the session, this method will return `ENOTTY`. + /// + /// # Panic + /// + /// This method should only be called in process context. + fn release_current_session(&self) -> Result<()> { + if !self.is_controlling_terminal() { + return_errno_with_message!(Errno::ENOTTY, "release wrong tty"); + } + + let current = current!(); + if !current.is_session_leader() { + warn!("TODO: release tty for process that is not session leader"); + return Ok(()); + } + + let release_session = || self.job_control().release_current_session(); + + let session = current.session().unwrap(); + session.release_terminal(release_session) + } + + /// Returns the job control of the terminal. + fn job_control(&self) -> &JobControl; + + fn arc_self(&self) -> Arc; +} diff --git a/services/libs/jinux-std/src/process/process_group.rs b/services/libs/jinux-std/src/process/process_group.rs deleted file mode 100644 index 1a18ea0cd..000000000 --- a/services/libs/jinux-std/src/process/process_group.rs +++ /dev/null @@ -1,85 +0,0 @@ -use super::{ - process_table, - signal::signals::{kernel::KernelSignal, user::UserSignal}, - Pgid, Pid, Process, -}; -use crate::prelude::*; - -pub struct ProcessGroup { - inner: Mutex, -} - -struct ProcessGroupInner { - pgid: Pgid, - processes: BTreeMap>, - leader_process: Option>, -} - -impl ProcessGroup { - fn default() -> Self { - ProcessGroup { - inner: Mutex::new(ProcessGroupInner { - pgid: 0, - processes: BTreeMap::new(), - leader_process: None, - }), - } - } - - pub fn new(process: Arc) -> Self { - let process_group = ProcessGroup::default(); - let pid = process.pid(); - process_group.set_pgid(pid); - process_group.add_process(process.clone()); - process_group.set_leader_process(process); - process_group - } - - pub fn set_pgid(&self, pgid: Pgid) { - self.inner.lock().pgid = pgid; - } - - pub fn set_leader_process(&self, leader_process: Arc) { - self.inner.lock().leader_process = Some(leader_process); - } - - pub fn add_process(&self, process: Arc) { - self.inner.lock().processes.insert(process.pid(), process); - } - - pub fn contains_process(&self, pid: Pid) -> bool { - self.inner.lock().processes.contains_key(&pid) - } - - /// remove a process from this process group. - /// If this group contains no processes now, the group itself will be deleted from global table. - pub fn remove_process(&self, pid: Pid) { - let mut inner_lock = self.inner.lock(); - inner_lock.processes.remove(&pid); - let len = inner_lock.processes.len(); - let pgid = inner_lock.pgid; - // if self contains no process, remove self from table - if len == 0 { - // this must be the last statement - process_table::remove_process_group(pgid); - } - } - - pub fn pgid(&self) -> Pgid { - self.inner.lock().pgid - } - - /// send kernel signal to all processes in the group - pub fn kernel_signal(&self, signal: KernelSignal) { - for process in self.inner.lock().processes.values() { - process.enqueue_signal(Box::new(signal)); - } - } - - /// send user signal to all processes in the group - pub fn user_signal(&self, signal: UserSignal) { - for process in self.inner.lock().processes.values() { - process.enqueue_signal(Box::new(signal)); - } - } -} diff --git a/services/libs/jinux-std/src/process/process_table.rs b/services/libs/jinux-std/src/process/process_table.rs index 428659843..d5d216c34 100644 --- a/services/libs/jinux-std/src/process/process_table.rs +++ b/services/libs/jinux-std/src/process/process_table.rs @@ -5,35 +5,25 @@ use crate::events::{Events, Observer, Subject}; use crate::prelude::*; -use super::{process_group::ProcessGroup, Pgid, Pid, Process}; +use super::{Pgid, Pid, Process, ProcessGroup, Session, Sid}; -lazy_static! { - static ref PROCESS_TABLE: Mutex>> = Mutex::new(BTreeMap::new()); - static ref PROCESS_GROUP_TABLE: Mutex>> = - Mutex::new(BTreeMap::new()); - static ref PROCESS_TABLE_SUBJECT: Subject = Subject::new(); +static PROCESS_TABLE: Mutex>> = Mutex::new(BTreeMap::new()); +static PROCESS_GROUP_TABLE: Mutex>> = Mutex::new(BTreeMap::new()); +static PROCESS_TABLE_SUBJECT: Subject = Subject::new(); +static SESSION_TABLE: Mutex>> = Mutex::new(BTreeMap::new()); + +// ************ Process ************* + +/// Gets a process with pid +pub fn get_process(pid: &Pid) -> Option> { + PROCESS_TABLE.lock().get(pid).cloned() } -/// add a process to global table -pub fn add_process(process: Arc) { - let pid = process.pid(); - PROCESS_TABLE.lock().insert(pid, process); +pub(super) fn process_table_mut() -> MutexGuard<'static, BTreeMap>> { + PROCESS_TABLE.lock() } -/// remove a process from global table -pub fn remove_process(pid: Pid) { - PROCESS_TABLE.lock().remove(&pid); - - let events = PidEvent::Exit(pid); - PROCESS_TABLE_SUBJECT.notify_observers(&events); -} - -/// get a process with pid -pub fn pid_to_process(pid: Pid) -> Option> { - PROCESS_TABLE.lock().get(&pid).cloned() -} - -/// get all processes +/// Gets all processes pub fn get_all_processes() -> Vec> { PROCESS_TABLE .lock() @@ -42,26 +32,41 @@ pub fn get_all_processes() -> Vec> { .collect() } -/// add process group to global table -pub fn add_process_group(process_group: Arc) { - let pgid = process_group.pgid(); - PROCESS_GROUP_TABLE.lock().insert(pgid, process_group); +// ************ Process Group ************* + +/// Gets a process group with `pgid` +pub fn get_process_group(pgid: &Pgid) -> Option> { + PROCESS_GROUP_TABLE.lock().get(pgid).cloned() } -/// remove process group from global table -pub fn remove_process_group(pgid: Pgid) { - PROCESS_GROUP_TABLE.lock().remove(&pgid); +/// Returns whether process table contains process group with pgid +pub fn contain_process_group(pgid: &Pgid) -> bool { + PROCESS_GROUP_TABLE.lock().contains_key(pgid) } -/// get a process group with pgid -pub fn pgid_to_process_group(pgid: Pgid) -> Option> { - PROCESS_GROUP_TABLE.lock().get(&pgid).cloned() +pub(super) fn group_table_mut() -> MutexGuard<'static, BTreeMap>> { + PROCESS_GROUP_TABLE.lock() } +// ************ Session ************* + +/// Gets a session with `sid`. +pub fn get_session(sid: &Sid) -> Option> { + SESSION_TABLE.lock().get(sid).map(Arc::clone) +} + +pub(super) fn session_table_mut() -> MutexGuard<'static, BTreeMap>> { + SESSION_TABLE.lock() +} + +// ************ Observer ************* + +/// Registers an observer which watches `PidEvent`. pub fn register_observer(observer: Weak>) { PROCESS_TABLE_SUBJECT.register_observer(observer, ()); } +/// Unregisters an observer which watches `PidEvent`. pub fn unregister_observer(observer: &Weak>) { PROCESS_TABLE_SUBJECT.unregister_observer(observer); } diff --git a/services/libs/jinux-std/src/process/signal/sig_queues.rs b/services/libs/jinux-std/src/process/signal/sig_queues.rs index 4272c9bf8..9b687ea8a 100644 --- a/services/libs/jinux-std/src/process/signal/sig_queues.rs +++ b/services/libs/jinux-std/src/process/signal/sig_queues.rs @@ -81,8 +81,11 @@ impl SigQueues { // POSIX leaves unspecified which to deliver first if there are multiple // pending standard signals. So we are free to define our own. The // principle is to give more urgent signals higher priority (like SIGKILL). + + // FIXME: the gvisor pty_test JobControlTest::ReleaseTTY requires that + // the SIGHUP signal should be handled before SIGCONT. const ORDERED_STD_SIGS: [SigNum; COUNT_STD_SIGS] = [ - SIGKILL, SIGTERM, SIGSTOP, SIGCONT, SIGSEGV, SIGILL, SIGHUP, SIGINT, SIGQUIT, SIGTRAP, + SIGKILL, SIGTERM, SIGSTOP, SIGSEGV, SIGILL, SIGHUP, SIGCONT, SIGINT, SIGQUIT, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGUSR1, SIGUSR2, SIGPIPE, SIGALRM, SIGSTKFLT, SIGCHLD, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS, diff --git a/services/libs/jinux-std/src/process/wait.rs b/services/libs/jinux-std/src/process/wait.rs index 4732be621..8db08d16f 100644 --- a/services/libs/jinux-std/src/process/wait.rs +++ b/services/libs/jinux-std/src/process/wait.rs @@ -80,9 +80,30 @@ fn reap_zombie_child(process: &Process, pid: Pid) -> u32 { for thread in &*child_process.threads().lock() { thread_table::remove_thread(thread.tid()); } - process_table::remove_process(child_process.pid()); - if let Some(process_group) = child_process.process_group() { - process_group.remove_process(child_process.pid()); + + let mut process_table_mut = process_table::process_table_mut(); + let mut group_table_mut = process_table::group_table_mut(); + let mut session_table_mut = process_table::session_table_mut(); + let mut child_group_mut = child_process.process_group.lock(); + + let process_group = child_process.process_group().unwrap(); + let mut group_inner = process_group.inner.lock(); + let session = group_inner.session.upgrade().unwrap(); + let mut session_inner = session.inner.lock(); + + group_inner.remove_process(&child_process.pid()); + session_inner.remove_process(&child_process); + *child_group_mut = Weak::new(); + + if group_inner.is_empty() { + group_table_mut.remove(&process_group.pgid()); + session_inner.remove_process_group(&process_group.pgid()); + + if session_inner.is_empty() { + session_table_mut.remove(&session.sid()); + } } + + process_table_mut.remove(&child_process.pid()); child_process.exit_code().unwrap() }