diff --git a/kernel/src/process/clone.rs b/kernel/src/process/clone.rs index f5adbef93..f6fe7abea 100644 --- a/kernel/src/process/clone.rs +++ b/kernel/src/process/clone.rs @@ -375,6 +375,13 @@ fn clone_child_process( // Sets parent process and group for child process. set_parent_and_group(process, &child); + // Updates `has_child_subreaper` for the child process after inserting + // it to its parent's children to make sure the `has_child_subreaper` + // state of the child process will be consistent with its parent. + if process.has_child_subreaper.load(Ordering::Relaxed) { + child.has_child_subreaper.store(true, Ordering::Relaxed); + } + Ok(child) } diff --git a/kernel/src/process/exit.rs b/kernel/src/process/exit.rs index f9aa65905..599d5b37e 100644 --- a/kernel/src/process/exit.rs +++ b/kernel/src/process/exit.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +use core::sync::atomic::Ordering; + use super::{posix_thread::ThreadLocal, process_table, Pid, Process}; use crate::{prelude::*, process::signal::signals::kernel::KernelSignal}; @@ -19,7 +21,7 @@ pub(super) fn exit_process(thread_local: &ThreadLocal, current_process: &Process send_parent_death_signal(current_process); - move_children_to_init(current_process); + move_children_to_reaper_process(current_process); send_child_death_signal(current_process); } @@ -41,22 +43,72 @@ fn send_parent_death_signal(current_process: &Process) { } } -/// Moves the children to the init process. -fn move_children_to_init(current_process: &Process) { +/// Finds a reaper process for `current_process`. +/// +/// If there is no reaper process for `current_process`, returns `None`. +fn find_reaper_process(current_process: &Process) -> Option> { + let mut parent = current_process.parent().lock().process(); + + while let Some(process) = parent.upgrade() { + if is_init_process(&process) { + return Some(process); + } + + if !process.has_child_subreaper.load(Ordering::Acquire) { + return None; + } + + let is_reaper = process.is_child_subreaper(); + let is_zombie = process.status().is_zombie(); + if is_reaper && !is_zombie { + return Some(process); + } + + parent = process.parent().lock().process(); + } + + None +} + +/// Moves the children of `current_process` to be the children of `reaper_process`. +/// +/// If the `reaper_process` is zombie, returns `Err(())`. +fn move_process_children( + current_process: &Process, + reaper_process: &Arc, +) -> core::result::Result<(), ()> { + // Take the lock first to avoid the race when the `reaper_process` is exiting concurrently. + let mut reaper_process_children = reaper_process.children().lock(); + let is_zombie = reaper_process.status().is_zombie(); + if is_zombie { + return Err(()); + } + + for (_, child_process) in current_process.children().lock().extract_if(|_, _| true) { + let mut parent = child_process.parent.lock(); + reaper_process_children.insert(child_process.pid(), child_process.clone()); + parent.set_process(reaper_process); + } + Ok(()) +} + +/// Moves the children to a reaper process. +fn move_children_to_reaper_process(current_process: &Process) { if is_init_process(current_process) { return; } + while let Some(reaper_process) = find_reaper_process(current_process) { + if move_process_children(current_process, &reaper_process).is_ok() { + return; + } + } + let Some(init_process) = get_init_process() else { return; }; - let mut init_children = init_process.children().lock(); - for (_, child_process) in current_process.children().lock().extract_if(|_, _| true) { - let mut parent = child_process.parent.lock(); - init_children.insert(child_process.pid(), child_process.clone()); - parent.set_process(&init_process); - } + let _ = move_process_children(current_process, &init_process); } /// Sends a child-death signal to the parent. diff --git a/kernel/src/process/process/mod.rs b/kernel/src/process/process/mod.rs index 9df61f3d8..af0852c79 100644 --- a/kernel/src/process/process/mod.rs +++ b/kernel/src/process/process/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use core::sync::atomic::{AtomicU32, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use self::timer_manager::PosixTimerManager; use super::{ @@ -88,6 +88,21 @@ pub struct Process { /// the threads in a process should share a nice value. nice: AtomicNice, + // Child reaper attribute + /// Whether the process is a child subreaper. + /// + /// A subreaper can be considered as a sort of "sub-init". + /// Instead of letting the init process to reap all orphan zombie processes, + /// a subreaper can reap orphan zombie processes among its descendants. + is_child_subreaper: AtomicBool, + + /// Whether the process has a subreaper that will reap it when the + /// process becomes orphaned. + /// + /// If `has_child_subreaper` is true in a `Process`, this attribute should + /// also be true for all of its descendants. + pub(super) has_child_subreaper: AtomicBool, + // Signal /// Sig dispositions sig_dispositions: Arc>, @@ -197,6 +212,8 @@ impl Process { parent: ParentProcess::new(parent), children: Mutex::new(BTreeMap::new()), process_group: Mutex::new(Weak::new()), + is_child_subreaper: AtomicBool::new(false), + has_child_subreaper: AtomicBool::new(false), sig_dispositions, parent_death_signal: AtomicSigNum::new_empty(), exit_signal: AtomicSigNum::new_empty(), @@ -667,6 +684,48 @@ impl Process { pub fn status(&self) -> &ProcessStatus { &self.status } + + // ******************* Subreaper ******************** + + /// Sets the child subreaper attribute of the current process. + pub fn set_child_subreaper(&self) { + self.is_child_subreaper.store(true, Ordering::Release); + let has_child_subreaper = self.has_child_subreaper.fetch_or(true, Ordering::AcqRel); + if !has_child_subreaper { + self.propagate_has_child_subreaper(); + } + } + + /// Unsets the child subreaper attribute of the current process. + pub fn unset_child_subreaper(&self) { + self.is_child_subreaper.store(false, Ordering::Release); + } + + /// Returns whether this process is a child subreaper. + pub fn is_child_subreaper(&self) -> bool { + self.is_child_subreaper.load(Ordering::Acquire) + } + + /// Sets all descendants of the current process as having child subreaper. + fn propagate_has_child_subreaper(&self) { + let mut process_queue = VecDeque::new(); + let children = self.children().lock(); + for child_process in children.values() { + if !child_process.has_child_subreaper.load(Ordering::Acquire) { + process_queue.push_back(child_process.clone()); + } + } + + while let Some(process) = process_queue.pop_front() { + process.has_child_subreaper.store(true, Ordering::Release); + let children = process.children().lock(); + for child_process in children.values() { + if !child_process.has_child_subreaper.load(Ordering::Acquire) { + process_queue.push_back(child_process.clone()); + } + } + } + } } #[cfg(ktest)] diff --git a/kernel/src/syscall/prctl.rs b/kernel/src/syscall/prctl.rs index 72d14b93a..7c85cb6ba 100644 --- a/kernel/src/syscall/prctl.rs +++ b/kernel/src/syscall/prctl.rs @@ -80,6 +80,19 @@ pub fn sys_prctl( thread_name.set_name(&new_thread_name)?; } } + PrctlCmd::PR_SET_CHILD_SUBREAPER(is_set) => { + let process = ctx.process; + if is_set { + process.set_child_subreaper(); + } else { + process.unset_child_subreaper(); + } + } + PrctlCmd::PR_GET_CHILD_SUBREAPER(write_addr) => { + let process = ctx.process; + ctx.user_space() + .write_val(write_addr, &(process.is_child_subreaper() as u32))?; + } _ => todo!(), } Ok(SyscallReturn::Return(0)) @@ -95,6 +108,8 @@ const PR_SET_NAME: i32 = 15; const PR_GET_NAME: i32 = 16; const PR_SET_TIMERSLACK: i32 = 29; const PR_GET_TIMERSLACK: i32 = 30; +const PR_SET_CHILD_SUBREAPER: i32 = 36; +const PR_GET_CHILD_SUBREAPER: i32 = 37; #[expect(non_camel_case_types)] #[derive(Debug, Clone, Copy)] @@ -111,6 +126,8 @@ pub enum PrctlCmd { PR_GET_TIMERSLACK, PR_SET_DUMPABLE(Dumpable), PR_GET_DUMPABLE, + PR_SET_CHILD_SUBREAPER(bool), + PR_GET_CHILD_SUBREAPER(Vaddr), } #[repr(u64)] @@ -137,6 +154,8 @@ impl PrctlCmd { PR_SET_TIMERSLACK => todo!(), PR_GET_KEEPCAPS => Ok(PrctlCmd::PR_GET_KEEPCAPS), PR_SET_KEEPCAPS => Ok(PrctlCmd::PR_SET_KEEPCAPS(arg2 as _)), + PR_SET_CHILD_SUBREAPER => Ok(PrctlCmd::PR_SET_CHILD_SUBREAPER(arg2 > 0)), + PR_GET_CHILD_SUBREAPER => Ok(PrctlCmd::PR_GET_CHILD_SUBREAPER(arg2 as _)), _ => { debug!("prctl cmd number: {}", option); return_errno_with_message!(Errno::EINVAL, "unsupported prctl command");