starry_process/
process.rs

1use alloc::{
2    collections::btree_set::BTreeSet,
3    sync::{Arc, Weak},
4    vec::Vec,
5};
6use core::{
7    fmt,
8    sync::atomic::{AtomicBool, Ordering},
9};
10
11use kspin::SpinNoIrq;
12use lazyinit::LazyInit;
13use weak_map::StrongMap;
14
15use crate::{Pid, ProcessGroup, Session};
16
17#[derive(Default)]
18pub(crate) struct ThreadGroup {
19    pub(crate) threads: BTreeSet<Pid>,
20    pub(crate) exit_code: i32,
21    pub(crate) group_exited: bool,
22}
23
24/// A process.
25pub struct Process {
26    pid: Pid,
27    is_zombie: AtomicBool,
28    pub(crate) tg: SpinNoIrq<ThreadGroup>,
29
30    // TODO: child subreaper9
31    children: SpinNoIrq<StrongMap<Pid, Arc<Process>>>,
32    parent: SpinNoIrq<Weak<Process>>,
33
34    group: SpinNoIrq<Arc<ProcessGroup>>,
35}
36
37impl Process {
38    /// The [`Process`] ID.
39    pub fn pid(&self) -> Pid {
40        self.pid
41    }
42
43    /// Returns `true` if the [`Process`] is the init process.
44    ///
45    /// This is a convenience method for checking if the [`Process`]
46    /// [`Arc::ptr_eq`]s with the init process, which is cheaper than
47    /// calling [`init_proc`] or testing if [`Process::parent`] is `None`.
48    pub fn is_init(self: &Arc<Self>) -> bool {
49        Arc::ptr_eq(self, INIT_PROC.get().unwrap())
50    }
51}
52
53/// Parent & children
54impl Process {
55    /// The parent [`Process`].
56    pub fn parent(&self) -> Option<Arc<Process>> {
57        self.parent.lock().upgrade()
58    }
59
60    /// The child [`Process`]es.
61    pub fn children(&self) -> Vec<Arc<Process>> {
62        self.children.lock().values().cloned().collect()
63    }
64}
65
66/// [`ProcessGroup`] & [`Session`]
67impl Process {
68    /// The [`ProcessGroup`] that the [`Process`] belongs to.
69    pub fn group(&self) -> Arc<ProcessGroup> {
70        self.group.lock().clone()
71    }
72
73    fn set_group(self: &Arc<Self>, group: &Arc<ProcessGroup>) {
74        let mut self_group = self.group.lock();
75
76        self_group.processes.lock().remove(&self.pid);
77
78        group.processes.lock().insert(self.pid, self);
79
80        *self_group = group.clone();
81    }
82
83    /// Creates a new [`Session`] and new [`ProcessGroup`] and moves the
84    /// [`Process`] to it.
85    ///
86    /// If the [`Process`] is already a session leader, this method does
87    /// nothing and returns `None`.
88    ///
89    /// Otherwise, it returns the new [`Session`] and [`ProcessGroup`].
90    ///
91    /// The caller has to ensure that the new [`ProcessGroup`] does not conflict
92    /// with any existing [`ProcessGroup`]. Thus, the [`Process`] must not
93    /// be a [`ProcessGroup`] leader.
94    ///
95    /// Checking [`Session`] conflicts is unnecessary.
96    pub fn create_session(self: &Arc<Self>) -> Option<(Arc<Session>, Arc<ProcessGroup>)> {
97        if self.group.lock().session.sid() == self.pid {
98            return None;
99        }
100
101        let new_session = Session::new(self.pid);
102        let new_group = ProcessGroup::new(self.pid, &new_session);
103        self.set_group(&new_group);
104
105        Some((new_session, new_group))
106    }
107
108    /// Creates a new [`ProcessGroup`] and moves the [`Process`] to it.
109    ///
110    /// If the [`Process`] is already a group leader, this method does nothing
111    /// and returns `None`.
112    ///
113    /// Otherwise, it returns the new [`ProcessGroup`].
114    ///
115    /// The caller has to ensure that the new [`ProcessGroup`] does not conflict
116    /// with any existing [`ProcessGroup`].
117    pub fn create_group(self: &Arc<Self>) -> Option<Arc<ProcessGroup>> {
118        if self.group.lock().pgid() == self.pid {
119            return None;
120        }
121
122        let new_group = ProcessGroup::new(self.pid, &self.group.lock().session);
123        self.set_group(&new_group);
124
125        Some(new_group)
126    }
127
128    /// Moves the [`Process`] to a specified [`ProcessGroup`].
129    ///
130    /// Returns `true` if the move succeeded. The move failed if the
131    /// [`ProcessGroup`] is not in the same [`Session`] as the [`Process`].
132    ///
133    /// If the [`Process`] is already in the specified [`ProcessGroup`], this
134    /// method does nothing and returns `true`.
135    pub fn move_to_group(self: &Arc<Self>, group: &Arc<ProcessGroup>) -> bool {
136        if Arc::ptr_eq(&self.group.lock(), group) {
137            return true;
138        }
139
140        if !Arc::ptr_eq(&self.group.lock().session, &group.session) {
141            return false;
142        }
143
144        self.set_group(group);
145        true
146    }
147}
148
149/// Threads
150impl Process {
151    /// Adds a thread to this [`Process`] with the given thread ID.
152    pub fn add_thread(self: &Arc<Self>, tid: Pid) {
153        self.tg.lock().threads.insert(tid);
154    }
155
156    /// Removes a thread from this [`Process`] and sets the exit code if the
157    /// group has not exited.
158    ///
159    /// Returns `true` if this was the last thread in the process.
160    pub fn exit_thread(self: &Arc<Self>, tid: Pid, exit_code: i32) -> bool {
161        let mut tg = self.tg.lock();
162        if !tg.group_exited {
163            tg.exit_code = exit_code;
164        }
165        tg.threads.remove(&tid);
166        tg.threads.is_empty()
167    }
168
169    /// Get all threads in this [`Process`].
170    pub fn threads(&self) -> Vec<Pid> {
171        self.tg.lock().threads.iter().cloned().collect()
172    }
173
174    /// Returns `true` if the [`Process`] is group exited.
175    pub fn is_group_exited(&self) -> bool {
176        self.tg.lock().group_exited
177    }
178
179    /// Marks the [`Process`] as group exited.
180    pub fn group_exit(&self) {
181        self.tg.lock().group_exited = true;
182    }
183
184    /// The exit code of the [`Process`].
185    pub fn exit_code(&self) -> i32 {
186        self.tg.lock().exit_code
187    }
188}
189
190/// Status & exit
191impl Process {
192    /// Returns `true` if the [`Process`] is a zombie process.
193    pub fn is_zombie(&self) -> bool {
194        self.is_zombie.load(Ordering::Acquire)
195    }
196
197    /// Terminates the [`Process`], marking it as a zombie process.
198    ///
199    /// Child processes are inherited by the init process or by the nearest
200    /// subreaper process.
201    ///
202    /// This method panics if the [`Process`] is the init process.
203    pub fn exit(self: &Arc<Self>) {
204        // TODO: child subreaper
205        let reaper = INIT_PROC.get().unwrap();
206
207        if Arc::ptr_eq(self, reaper) {
208            return;
209        }
210
211        let mut children = self.children.lock(); // Acquire the lock first
212        self.is_zombie.store(true, Ordering::Release);
213
214        let mut reaper_children = reaper.children.lock();
215        let reaper = Arc::downgrade(reaper);
216
217        for (pid, child) in core::mem::take(&mut *children) {
218            *child.parent.lock() = reaper.clone();
219            reaper_children.insert(pid, child);
220        }
221    }
222
223    /// Frees a zombie [`Process`]. Removes it from the parent.
224    ///
225    /// This method panics if the [`Process`] is not a zombie.
226    pub fn free(&self) {
227        assert!(self.is_zombie(), "only zombie process can be freed");
228
229        if let Some(parent) = self.parent() {
230            parent.children.lock().remove(&self.pid);
231        }
232    }
233}
234
235impl fmt::Debug for Process {
236    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237        let mut builder = f.debug_struct("Process");
238        builder.field("pid", &self.pid);
239
240        let tg = self.tg.lock();
241        if tg.group_exited {
242            builder.field("group_exited", &tg.group_exited);
243        }
244        if self.is_zombie() {
245            builder.field("exit_code", &tg.exit_code);
246        }
247
248        if let Some(parent) = self.parent() {
249            builder.field("parent", &parent.pid());
250        }
251        builder.field("group", &self.group());
252        builder.finish()
253    }
254}
255
256/// Builder
257impl Process {
258    fn new(pid: Pid, parent: Option<Arc<Process>>) -> Arc<Process> {
259        let group = parent.as_ref().map_or_else(
260            || {
261                let session = Session::new(pid);
262                ProcessGroup::new(pid, &session)
263            },
264            |p| p.group(),
265        );
266
267        let process = Arc::new(Process {
268            pid,
269            is_zombie: AtomicBool::new(false),
270            tg: SpinNoIrq::new(ThreadGroup::default()),
271            children: SpinNoIrq::new(StrongMap::new()),
272            parent: SpinNoIrq::new(parent.as_ref().map(Arc::downgrade).unwrap_or_default()),
273            group: SpinNoIrq::new(group.clone()),
274        });
275
276        group.processes.lock().insert(pid, &process);
277
278        if let Some(parent) = parent {
279            parent.children.lock().insert(pid, process.clone());
280        } else {
281            INIT_PROC.init_once(process.clone());
282        }
283
284        process
285    }
286
287    /// Creates a init [`Process`].
288    ///
289    /// This function can be called multiple times, but
290    /// [`ProcessBuilder::build`] on the the result must be called only once.
291    pub fn new_init(pid: Pid) -> Arc<Process> {
292        Self::new(pid, None)
293    }
294
295    /// Creates a child [`Process`].
296    pub fn fork(self: &Arc<Process>, pid: Pid) -> Arc<Process> {
297        Self::new(pid, Some(self.clone()))
298    }
299}
300
301static INIT_PROC: LazyInit<Arc<Process>> = LazyInit::new();
302
303/// Gets the init process.
304///
305/// This function panics if the init process has not been initialized yet.
306pub fn init_proc() -> Arc<Process> {
307    INIT_PROC.get().unwrap().clone()
308}