Skip to main content

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 ax_kspin::SpinNoIrq;
12use ax_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    is_child_subreaper: AtomicBool,
29    pub(crate) tg: SpinNoIrq<ThreadGroup>,
30
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    /// Returns `true` if this process acts as a child subreaper.
53    ///
54    /// Linux keeps this flag per process: it is preserved across `execve`,
55    /// applies to all threads in the thread group, and is not inherited by
56    /// newly forked child processes.
57    pub fn is_child_subreaper(&self) -> bool {
58        self.is_child_subreaper.load(Ordering::Acquire)
59    }
60
61    /// Enables or disables child subreaper behavior for this process.
62    pub fn set_child_subreaper(&self, enabled: bool) {
63        self.is_child_subreaper.store(enabled, Ordering::Release);
64    }
65}
66
67/// Parent & children
68impl Process {
69    /// The parent [`Process`].
70    pub fn parent(&self) -> Option<Arc<Process>> {
71        self.parent.lock().upgrade()
72    }
73
74    /// The child [`Process`]es.
75    pub fn children(&self) -> Vec<Arc<Process>> {
76        self.children.lock().values().cloned().collect()
77    }
78}
79
80/// [`ProcessGroup`] & [`Session`]
81impl Process {
82    /// The [`ProcessGroup`] that the [`Process`] belongs to.
83    pub fn group(&self) -> Arc<ProcessGroup> {
84        self.group.lock().clone()
85    }
86
87    fn set_group(self: &Arc<Self>, group: &Arc<ProcessGroup>) {
88        let mut self_group = self.group.lock();
89
90        self_group.processes.lock().remove(&self.pid);
91
92        group.processes.lock().insert(self.pid, self);
93
94        *self_group = group.clone();
95    }
96
97    /// Creates a new [`Session`] and new [`ProcessGroup`] and moves the
98    /// [`Process`] to it.
99    ///
100    /// If the [`Process`] is already a session leader, this method does
101    /// nothing and returns `None`.
102    ///
103    /// Otherwise, it returns the new [`Session`] and [`ProcessGroup`].
104    ///
105    /// The caller has to ensure that the new [`ProcessGroup`] does not conflict
106    /// with any existing [`ProcessGroup`]. Thus, the [`Process`] must not
107    /// be a [`ProcessGroup`] leader.
108    ///
109    /// Checking [`Session`] conflicts is unnecessary.
110    pub fn create_session(self: &Arc<Self>) -> Option<(Arc<Session>, Arc<ProcessGroup>)> {
111        if self.group.lock().session.sid() == self.pid {
112            return None;
113        }
114
115        let new_session = Session::new(self.pid);
116        let new_group = ProcessGroup::new(self.pid, &new_session);
117        self.set_group(&new_group);
118
119        Some((new_session, new_group))
120    }
121
122    /// Creates a new [`ProcessGroup`] and moves the [`Process`] to it.
123    ///
124    /// If the [`Process`] is already a group leader, this method does nothing
125    /// and returns `None`.
126    ///
127    /// Otherwise, it returns the new [`ProcessGroup`].
128    ///
129    /// The caller has to ensure that the new [`ProcessGroup`] does not conflict
130    /// with any existing [`ProcessGroup`].
131    pub fn create_group(self: &Arc<Self>) -> Option<Arc<ProcessGroup>> {
132        if self.group.lock().pgid() == self.pid {
133            return None;
134        }
135
136        let new_group = ProcessGroup::new(self.pid, &self.group.lock().session);
137        self.set_group(&new_group);
138
139        Some(new_group)
140    }
141
142    /// Moves the [`Process`] to a specified [`ProcessGroup`].
143    ///
144    /// Returns `true` if the move succeeded. The move failed if the
145    /// [`ProcessGroup`] is not in the same [`Session`] as the [`Process`].
146    ///
147    /// If the [`Process`] is already in the specified [`ProcessGroup`], this
148    /// method does nothing and returns `true`.
149    pub fn move_to_group(self: &Arc<Self>, group: &Arc<ProcessGroup>) -> bool {
150        if Arc::ptr_eq(&self.group.lock(), group) {
151            return true;
152        }
153
154        if !Arc::ptr_eq(&self.group.lock().session, &group.session) {
155            return false;
156        }
157
158        self.set_group(group);
159        true
160    }
161}
162
163/// Threads
164impl Process {
165    /// Adds a thread to this [`Process`] with the given thread ID.
166    pub fn add_thread(self: &Arc<Self>, tid: Pid) {
167        self.tg.lock().threads.insert(tid);
168    }
169
170    /// Removes a thread from this [`Process`] and sets the exit code if the
171    /// group has not exited.
172    ///
173    /// Returns `true` if this was the last thread in the process.
174    pub fn exit_thread(self: &Arc<Self>, tid: Pid, exit_code: i32) -> bool {
175        let mut tg = self.tg.lock();
176        if !tg.group_exited {
177            tg.exit_code = exit_code;
178        }
179        tg.threads.remove(&tid);
180        tg.threads.is_empty()
181    }
182
183    /// Get all threads in this [`Process`].
184    pub fn threads(&self) -> Vec<Pid> {
185        self.tg.lock().threads.iter().cloned().collect()
186    }
187
188    /// Renames a thread in the thread group.
189    ///
190    /// Used by `execve`'s de_thread step when a non-leader thread successfully
191    /// `execve`s: the calling thread inherits the leader's TID so that
192    /// `gettid() == getpid()` holds in the new image. We swap `old_tid` for
193    /// `new_tid` atomically inside the thread-group lock so there is no
194    /// instant in which the caller is unrepresented in the group.
195    pub fn rename_thread(self: &Arc<Self>, old_tid: Pid, new_tid: Pid) {
196        let mut tg = self.tg.lock();
197        tg.threads.remove(&old_tid);
198        tg.threads.insert(new_tid);
199    }
200
201    /// Returns `true` if the [`Process`] is group exited.
202    pub fn is_group_exited(&self) -> bool {
203        self.tg.lock().group_exited
204    }
205
206    /// Starts a process-wide exit if one is not already in progress.
207    ///
208    /// Returns a snapshot of the thread group at the point where the group-exit
209    /// state was first published. Later exiting threads must not overwrite the
210    /// recorded process exit code.
211    pub fn start_group_exit(&self, exit_code: i32) -> Option<Vec<Pid>> {
212        let mut tg = self.tg.lock();
213        if tg.group_exited {
214            return None;
215        }
216        tg.group_exited = true;
217        tg.exit_code = exit_code;
218        Some(tg.threads.iter().cloned().collect())
219    }
220
221    /// Marks the [`Process`] as group exited.
222    pub fn group_exit(&self) {
223        self.tg.lock().group_exited = true;
224    }
225
226    /// The exit code of the [`Process`].
227    pub fn exit_code(&self) -> i32 {
228        self.tg.lock().exit_code
229    }
230}
231
232/// Status & exit
233impl Process {
234    fn orphan_reaper(self: &Arc<Self>) -> Arc<Process> {
235        let init_proc = INIT_PROC.get().unwrap();
236        let mut cursor = self.parent();
237
238        while let Some(proc) = cursor {
239            if Arc::ptr_eq(&proc, init_proc) {
240                break;
241            }
242            if proc.is_child_subreaper() && !proc.is_zombie() {
243                return proc;
244            }
245            cursor = proc.parent();
246        }
247
248        init_proc.clone()
249    }
250
251    /// Returns `true` if the [`Process`] is a zombie process.
252    pub fn is_zombie(&self) -> bool {
253        self.is_zombie.load(Ordering::Acquire)
254    }
255
256    /// Terminates the [`Process`], marking it as a zombie process.
257    ///
258    /// Child processes are inherited by the init process or by the nearest
259    /// subreaper process.
260    ///
261    /// This method does nothing if the [`Process`] is the init process.
262    pub fn exit(self: &Arc<Self>) {
263        if self.is_init() {
264            return;
265        }
266
267        let reaper_proc = self.orphan_reaper();
268        let reaper_parent = Arc::downgrade(&reaper_proc);
269
270        let mut reaper_children = reaper_proc.children.lock();
271        let mut children = self.children.lock();
272        self.is_zombie.store(true, Ordering::Release);
273        for (pid, child) in core::mem::take(&mut *children) {
274            *child.parent.lock() = reaper_parent.clone();
275            reaper_children.insert(pid, child);
276        }
277    }
278
279    /// Frees a zombie [`Process`]. Removes it from the parent.
280    ///
281    /// This method panics if the [`Process`] is not a zombie.
282    pub fn free(&self) {
283        assert!(self.is_zombie(), "only zombie process can be freed");
284
285        if let Some(parent) = self.parent() {
286            parent.children.lock().remove(&self.pid);
287        }
288    }
289}
290
291impl fmt::Debug for Process {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        let mut builder = f.debug_struct("Process");
294        builder.field("pid", &self.pid);
295
296        let tg = self.tg.lock();
297        if tg.group_exited {
298            builder.field("group_exited", &tg.group_exited);
299        }
300        if self.is_zombie() {
301            builder.field("exit_code", &tg.exit_code);
302        }
303
304        if let Some(parent) = self.parent() {
305            builder.field("parent", &parent.pid());
306        }
307        builder.field("group", &self.group());
308        builder.finish()
309    }
310}
311
312/// Builder
313impl Process {
314    fn new(pid: Pid, parent: Option<Arc<Process>>) -> Arc<Process> {
315        let group = parent.as_ref().map_or_else(
316            || {
317                let session = Session::new(pid);
318                ProcessGroup::new(pid, &session)
319            },
320            |p| p.group(),
321        );
322
323        let process = Arc::new(Process {
324            pid,
325            is_zombie: AtomicBool::new(false),
326            is_child_subreaper: AtomicBool::new(false),
327            tg: SpinNoIrq::new(ThreadGroup::default()),
328            children: SpinNoIrq::new(StrongMap::new()),
329            parent: SpinNoIrq::new(parent.as_ref().map(Arc::downgrade).unwrap_or_default()),
330            group: SpinNoIrq::new(group.clone()),
331        });
332
333        group.processes.lock().insert(pid, &process);
334
335        if let Some(parent) = parent {
336            parent.children.lock().insert(pid, process.clone());
337        } else {
338            INIT_PROC.init_once(process.clone());
339        }
340
341        process
342    }
343
344    /// Creates a init [`Process`].
345    ///
346    /// This function can be called multiple times, but
347    /// [`ProcessBuilder::build`] on the the result must be called only once.
348    pub fn new_init(pid: Pid) -> Arc<Process> {
349        Self::new(pid, None)
350    }
351
352    /// Creates a child [`Process`].
353    pub fn fork(self: &Arc<Process>, pid: Pid) -> Arc<Process> {
354        Self::new(pid, Some(self.clone()))
355    }
356}
357
358static INIT_PROC: LazyInit<Arc<Process>> = LazyInit::new();
359
360/// Gets the init process.
361///
362/// This function panics if the init process has not been initialized yet.
363pub fn init_proc() -> Arc<Process> {
364    INIT_PROC.get().unwrap().clone()
365}
366
367#[cfg(test)]
368mod tests {
369    extern crate std;
370
371    use alloc::sync::Arc;
372    use core::time::Duration;
373    use std::{
374        sync::{Arc as StdArc, Barrier},
375        thread,
376        time::Instant,
377    };
378
379    use super::Process;
380
381    #[test]
382    fn orphan_never_becomes_invisible_while_reparenting() {
383        let init = Process::new_init(1);
384        let reaper = init.fork(2);
385        reaper.set_child_subreaper(true);
386        let parent = reaper.fork(3);
387        let child = parent.fork(4);
388        let child_pid = child.pid();
389
390        let reaper_children = reaper.children.lock();
391        let start_exit = StdArc::new(Barrier::new(2));
392        let exit_parent = parent.clone();
393        let exit_start = start_exit.clone();
394        let exit_thread = thread::spawn(move || {
395            exit_start.wait();
396            exit_parent.exit();
397        });
398
399        start_exit.wait();
400        let deadline = Instant::now() + Duration::from_millis(500);
401        let mut observed_invisible = false;
402        while Instant::now() < deadline {
403            let parent_has_child = parent.children.lock().contains_key(&child_pid);
404            let reaper_has_child = reaper_children.contains_key(&child_pid);
405            if !parent_has_child && !reaper_has_child {
406                observed_invisible = true;
407                break;
408            }
409            thread::yield_now();
410        }
411
412        drop(reaper_children);
413        exit_thread.join().unwrap();
414
415        assert!(
416            !observed_invisible,
417            "orphan was removed from its old parent before it became visible to the reaper"
418        );
419        assert!(Arc::ptr_eq(&reaper, &child.parent().unwrap()));
420        assert!(reaper.children.lock().contains_key(&child_pid));
421    }
422}