use alloc::{
collections::btree_set::BTreeSet,
sync::{Arc, Weak},
vec::Vec,
};
use core::{
fmt,
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use ax_lazyinit::LazyInit;
use weak_map::StrongMap;
use super::{ProcessGroup, Session};
use crate::{
sync::SpinLock,
task::{PidIdentity, TgidNumber, TidNumber},
};
const NESTED_CHILDREN_LOCK_SUBCLASS: u32 = 1;
#[derive(Default)]
pub(crate) struct ThreadGroup {
pub(crate) threads: BTreeSet<TidNumber>,
pub(crate) exit_code: i32,
pub(crate) group_exited: bool,
pub(crate) exited_cpu_time: ProcessCpuTime,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ProcessCpuTime {
user: Duration,
system: Duration,
}
impl ProcessCpuTime {
pub const fn new(user: Duration, system: Duration) -> Self {
Self { user, system }
}
pub const fn user(self) -> Duration {
self.user
}
pub const fn system(self) -> Duration {
self.system
}
fn add(&mut self, other: Self) {
self.user += other.user;
self.system += other.system;
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ThreadExit {
AlreadyExited,
Remaining,
Last(ProcessCpuTime),
}
pub struct Process {
pid: TgidNumber,
identity: Weak<PidIdentity>,
is_child_subreaper: AtomicBool,
pub(crate) tg: SpinLock<ThreadGroup>,
children: SpinLock<StrongMap<TgidNumber, Arc<Process>>>,
parent: SpinLock<Weak<Process>>,
job_control: SpinLock<()>,
group: SpinLock<Arc<ProcessGroup>>,
}
impl Process {
pub const fn pid(&self) -> TgidNumber {
self.pid
}
pub(crate) const fn pid_number(&self) -> TgidNumber {
self.pid
}
pub(crate) fn identity(&self) -> Arc<PidIdentity> {
self.identity
.upgrade()
.expect("process topology outlived its PID identity")
}
pub fn is_init(self: &Arc<Self>) -> bool {
Arc::ptr_eq(self, INIT_PROC.get().unwrap())
}
pub fn is_child_subreaper(&self) -> bool {
self.is_child_subreaper.load(Ordering::Acquire)
}
pub fn set_child_subreaper(&self, enabled: bool) {
self.is_child_subreaper.store(enabled, Ordering::Release);
}
}
impl Process {
pub fn parent(&self) -> Option<Arc<Process>> {
self.parent.lock_irqsave().upgrade()
}
pub fn children(&self) -> Vec<Arc<Process>> {
self.children.lock_irqsave().values().cloned().collect()
}
}
impl Process {
pub fn group(&self) -> Arc<ProcessGroup> {
self.group.lock_irqsave().clone()
}
fn set_group_locked(
self: &Arc<Self>,
old_group: &Arc<ProcessGroup>,
group: &Arc<ProcessGroup>,
) {
if Arc::ptr_eq(old_group, group) {
return;
}
if old_group.pgid_number() < group.pgid_number() {
let mut old_members = old_group.processes.lock_irqsave();
let mut new_members = group.processes.lock_irqsave();
old_members.remove(&self.pid);
new_members.insert(self.pid, self);
} else {
let mut new_members = group.processes.lock_irqsave();
let mut old_members = old_group.processes.lock_irqsave();
old_members.remove(&self.pid);
new_members.insert(self.pid, self);
}
*self.group.lock_irqsave() = group.clone();
}
pub fn create_session(self: &Arc<Self>) -> Option<(Arc<Session>, Arc<ProcessGroup>)> {
let _job_control = self.job_control.lock_irqsave();
let old_group = self.group();
if old_group.session.sid_number().pid_number() == self.pid.pid_number()
|| old_group.pgid_number().pid_number() == self.pid.pid_number()
{
return None;
}
let identity = self.identity();
let new_session = Session::new(identity.clone()).ok()?;
let new_group = ProcessGroup::get_or_create(identity, &new_session).ok()?;
self.set_group_locked(&old_group, &new_group);
Some((new_session, new_group))
}
pub fn create_group(self: &Arc<Self>) -> Option<Arc<ProcessGroup>> {
let _job_control = self.job_control.lock_irqsave();
let old_group = self.group();
if old_group.pgid_number().pid_number() == self.pid.pid_number() {
return None;
}
let new_group = ProcessGroup::get_or_create(self.identity(), &old_group.session).ok()?;
self.set_group_locked(&old_group, &new_group);
Some(new_group)
}
pub fn move_to_group(self: &Arc<Self>, group: &Arc<ProcessGroup>) -> bool {
let _job_control = self.job_control.lock_irqsave();
let old_group = self.group();
if Arc::ptr_eq(&old_group, group) {
return true;
}
if !Arc::ptr_eq(&old_group.session, &group.session) {
return false;
}
self.set_group_locked(&old_group, group);
true
}
}
impl Process {
pub fn add_thread(self: &Arc<Self>, tid: TidNumber) {
self.tg.lock_irqsave().threads.insert(tid);
}
pub fn exit_thread(
self: &Arc<Self>,
tid: TidNumber,
exit_code: i32,
cpu_time: ProcessCpuTime,
) -> ThreadExit {
let mut tg = self.tg.lock_irqsave();
if !tg.threads.remove(&tid) {
return ThreadExit::AlreadyExited;
}
if !tg.group_exited {
tg.exit_code = exit_code;
}
tg.exited_cpu_time.add(cpu_time);
if tg.threads.is_empty() {
ThreadExit::Last(tg.exited_cpu_time)
} else {
ThreadExit::Remaining
}
}
pub fn threads(&self) -> Vec<TidNumber> {
self.tg.lock_irqsave().threads.iter().copied().collect()
}
pub fn rename_thread(self: &Arc<Self>, old_tid: TidNumber, new_tid: TidNumber) {
let mut tg = self.tg.lock_irqsave();
tg.threads.remove(&old_tid);
tg.threads.insert(new_tid);
}
pub fn is_group_exited(&self) -> bool {
self.tg.lock_irqsave().group_exited
}
pub fn start_group_exit(&self, exit_code: i32) -> Option<Vec<TidNumber>> {
let mut tg = self.tg.lock_irqsave();
if tg.group_exited {
return None;
}
tg.group_exited = true;
tg.exit_code = exit_code;
Some(tg.threads.iter().copied().collect())
}
pub fn group_exit(&self) {
self.tg.lock_irqsave().group_exited = true;
}
pub fn exit_code(&self) -> i32 {
self.tg.lock_irqsave().exit_code
}
}
impl Process {
pub fn reparent_children_to(self: &Arc<Self>, reaper: &Arc<Process>) {
if self.is_init() || Arc::ptr_eq(self, reaper) {
return;
}
let reaper_parent = Arc::downgrade(reaper);
let mut reaper_children = reaper.children.lock_irqsave();
let mut children = self
.children
.lock_irqsave_nested(NESTED_CHILDREN_LOCK_SUBCLASS);
for (pid, child) in core::mem::take(&mut *children) {
*child.parent.lock_irqsave() = reaper_parent.clone();
reaper_children.insert(pid, child);
}
}
pub fn retire(self: &Arc<Self>) {
let _job_control = self.job_control.lock_irqsave();
let parent = self.parent();
let group = self.group();
let mut parent_children = parent.as_ref().map(|parent| parent.children.lock_irqsave());
let mut group_members = group.processes.lock_irqsave();
if let Some(children) = parent_children.as_mut()
&& children
.get(&self.pid)
.is_some_and(|registered| Arc::ptr_eq(registered, self))
{
children.remove(&self.pid);
}
if group_members
.get(&self.pid)
.is_some_and(|registered| Arc::ptr_eq(®istered, self))
{
group_members.remove(&self.pid);
}
*self.parent.lock_irqsave() = Weak::new();
}
}
impl fmt::Debug for Process {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut builder = f.debug_struct("Process");
builder.field("pid", &self.pid);
let tg = self.tg.lock_irqsave();
if tg.group_exited {
builder.field("group_exited", &tg.group_exited);
}
if tg.threads.is_empty() {
builder.field("exit_code", &tg.exit_code);
}
if let Some(parent) = self.parent() {
builder.field("parent", &parent.pid());
}
builder.field("group", &self.group());
builder.finish()
}
}
impl Process {
fn new_group_member(identity: Arc<PidIdentity>, parent: Option<&Arc<Process>>) -> Arc<Process> {
let pid = TgidNumber::from(identity.root_number());
let group = parent.map_or_else(
|| {
let session = Session::new(identity.clone())
.expect("init identity must acquire its unique SID role");
ProcessGroup::get_or_create(identity.clone(), &session)
.expect("init identity must acquire its unique PGID role")
},
|p| p.group(),
);
let process = Arc::new(Process {
pid,
identity: Arc::downgrade(&identity),
is_child_subreaper: AtomicBool::new(false),
tg: SpinLock::new(ThreadGroup::default()),
children: SpinLock::new(StrongMap::new()),
parent: SpinLock::new(parent.map(Arc::downgrade).unwrap_or_default()),
job_control: SpinLock::new(()),
group: SpinLock::new(group.clone()),
});
group.processes.lock_irqsave().insert(pid, &process);
process
}
fn new(identity: Arc<PidIdentity>, parent: Option<Arc<Process>>) -> Arc<Process> {
let pid = TgidNumber::from(identity.root_number());
let process = Self::new_group_member(identity, parent.as_ref());
if let Some(parent) = parent {
parent.children.lock_irqsave().insert(pid, process.clone());
} else {
INIT_PROC.init_once(process.clone());
}
process
}
pub fn new_init(identity: Arc<PidIdentity>) -> Arc<Process> {
Self::new(identity, None)
}
pub fn fork(self: &Arc<Process>, identity: Arc<PidIdentity>) -> Arc<Process> {
Self::new(identity, Some(self.clone()))
}
#[cfg(test)]
pub(super) fn new_isolated_for_test(identity: Arc<PidIdentity>) -> Arc<Process> {
Self::new_group_member(identity, None)
}
}
static INIT_PROC: LazyInit<Arc<Process>> = LazyInit::new();
pub fn init_proc() -> Arc<Process> {
INIT_PROC.get().unwrap().clone()
}
#[cfg(all(test, not(axtest)))]
mod tests {
extern crate std;
use alloc::sync::Arc;
use core::time::Duration;
use std::{
sync::{Arc as StdArc, Barrier},
thread,
time::Instant,
};
use super::{NESTED_CHILDREN_LOCK_SUBCLASS, Process};
#[test]
fn orphan_never_becomes_invisible_while_reparenting() {
let namespace = crate::task::new_test_pid_namespace();
let (init_identity, _init_tgid) = crate::task::new_test_process_identity(&namespace);
let init = Process::new_init(init_identity);
let (reaper_identity, _reaper_tgid) = crate::task::new_test_process_identity(&namespace);
let reaper = init.fork(reaper_identity);
reaper.set_child_subreaper(true);
let (parent_identity, _parent_tgid) = crate::task::new_test_process_identity(&namespace);
let parent = reaper.fork(parent_identity);
let (child_identity, _child_tgid) = crate::task::new_test_process_identity(&namespace);
let child = parent.fork(child_identity);
let child_pid = child.pid_number();
let reaper_children = reaper.children.lock_irqsave();
let start_exit = StdArc::new(Barrier::new(2));
let exit_parent = parent.clone();
let exit_reaper = reaper.clone();
let exit_start = start_exit.clone();
let exit_thread = thread::spawn(move || {
exit_start.wait();
exit_parent.reparent_children_to(&exit_reaper);
});
start_exit.wait();
let deadline = Instant::now() + Duration::from_millis(500);
let mut observed_invisible = false;
while Instant::now() < deadline {
let parent_has_child = parent
.children
.lock_irqsave_nested(NESTED_CHILDREN_LOCK_SUBCLASS)
.contains_key(&child_pid);
let reaper_has_child = reaper_children.contains_key(&child_pid);
if !parent_has_child && !reaper_has_child {
observed_invisible = true;
break;
}
thread::yield_now();
}
drop(reaper_children);
exit_thread.join().unwrap();
assert!(
!observed_invisible,
"orphan was removed from its old parent before it became visible to the reaper"
);
assert!(Arc::ptr_eq(&reaper, &child.parent().unwrap()));
assert!(reaper.children.lock_irqsave().contains_key(&child_pid));
}
}