use alloc::{
collections::btree_set::BTreeSet,
sync::{Arc, Weak},
vec::Vec,
};
use core::{
fmt,
sync::atomic::{AtomicBool, Ordering},
};
use ax_kspin::SpinNoIrq;
use ax_lazyinit::LazyInit;
use weak_map::StrongMap;
use crate::{Pid, ProcessGroup, Session};
#[derive(Default)]
pub(crate) struct ThreadGroup {
pub(crate) threads: BTreeSet<Pid>,
pub(crate) exit_code: i32,
pub(crate) group_exited: bool,
}
pub struct Process {
pid: Pid,
is_zombie: AtomicBool,
pub(crate) tg: SpinNoIrq<ThreadGroup>,
children: SpinNoIrq<StrongMap<Pid, Arc<Process>>>,
parent: SpinNoIrq<Weak<Process>>,
group: SpinNoIrq<Arc<ProcessGroup>>,
}
impl Process {
pub fn pid(&self) -> Pid {
self.pid
}
pub fn is_init(self: &Arc<Self>) -> bool {
Arc::ptr_eq(self, INIT_PROC.get().unwrap())
}
}
impl Process {
pub fn parent(&self) -> Option<Arc<Process>> {
self.parent.lock().upgrade()
}
pub fn children(&self) -> Vec<Arc<Process>> {
self.children.lock().values().cloned().collect()
}
}
impl Process {
pub fn group(&self) -> Arc<ProcessGroup> {
self.group.lock().clone()
}
fn set_group(self: &Arc<Self>, group: &Arc<ProcessGroup>) {
let mut self_group = self.group.lock();
self_group.processes.lock().remove(&self.pid);
group.processes.lock().insert(self.pid, self);
*self_group = group.clone();
}
pub fn create_session(self: &Arc<Self>) -> Option<(Arc<Session>, Arc<ProcessGroup>)> {
if self.group.lock().session.sid() == self.pid {
return None;
}
let new_session = Session::new(self.pid);
let new_group = ProcessGroup::new(self.pid, &new_session);
self.set_group(&new_group);
Some((new_session, new_group))
}
pub fn create_group(self: &Arc<Self>) -> Option<Arc<ProcessGroup>> {
if self.group.lock().pgid() == self.pid {
return None;
}
let new_group = ProcessGroup::new(self.pid, &self.group.lock().session);
self.set_group(&new_group);
Some(new_group)
}
pub fn move_to_group(self: &Arc<Self>, group: &Arc<ProcessGroup>) -> bool {
if Arc::ptr_eq(&self.group.lock(), group) {
return true;
}
if !Arc::ptr_eq(&self.group.lock().session, &group.session) {
return false;
}
self.set_group(group);
true
}
}
impl Process {
pub fn add_thread(self: &Arc<Self>, tid: Pid) {
self.tg.lock().threads.insert(tid);
}
pub fn exit_thread(self: &Arc<Self>, tid: Pid, exit_code: i32) -> bool {
let mut tg = self.tg.lock();
if !tg.group_exited {
tg.exit_code = exit_code;
}
tg.threads.remove(&tid);
tg.threads.is_empty()
}
pub fn threads(&self) -> Vec<Pid> {
self.tg.lock().threads.iter().cloned().collect()
}
pub fn is_group_exited(&self) -> bool {
self.tg.lock().group_exited
}
pub fn group_exit(&self) {
self.tg.lock().group_exited = true;
}
pub fn exit_code(&self) -> i32 {
self.tg.lock().exit_code
}
}
impl Process {
pub fn is_zombie(&self) -> bool {
self.is_zombie.load(Ordering::Acquire)
}
pub fn exit(self: &Arc<Self>) {
let reaper = INIT_PROC.get().unwrap();
if Arc::ptr_eq(self, reaper) {
return;
}
let mut children = self.children.lock(); self.is_zombie.store(true, Ordering::Release);
let mut reaper_children = reaper.children.lock();
let reaper = Arc::downgrade(reaper);
for (pid, child) in core::mem::take(&mut *children) {
*child.parent.lock() = reaper.clone();
reaper_children.insert(pid, child);
}
}
pub fn free(&self) {
assert!(self.is_zombie(), "only zombie process can be freed");
if let Some(parent) = self.parent() {
parent.children.lock().remove(&self.pid);
}
}
}
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();
if tg.group_exited {
builder.field("group_exited", &tg.group_exited);
}
if self.is_zombie() {
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(pid: Pid, parent: Option<Arc<Process>>) -> Arc<Process> {
let group = parent.as_ref().map_or_else(
|| {
let session = Session::new(pid);
ProcessGroup::new(pid, &session)
},
|p| p.group(),
);
let process = Arc::new(Process {
pid,
is_zombie: AtomicBool::new(false),
tg: SpinNoIrq::new(ThreadGroup::default()),
children: SpinNoIrq::new(StrongMap::new()),
parent: SpinNoIrq::new(parent.as_ref().map(Arc::downgrade).unwrap_or_default()),
group: SpinNoIrq::new(group.clone()),
});
group.processes.lock().insert(pid, &process);
if let Some(parent) = parent {
parent.children.lock().insert(pid, process.clone());
} else {
INIT_PROC.init_once(process.clone());
}
process
}
pub fn new_init(pid: Pid) -> Arc<Process> {
Self::new(pid, None)
}
pub fn fork(self: &Arc<Process>, pid: Pid) -> Arc<Process> {
Self::new(pid, Some(self.clone()))
}
}
static INIT_PROC: LazyInit<Arc<Process>> = LazyInit::new();
pub fn init_proc() -> Arc<Process> {
INIT_PROC.get().unwrap().clone()
}