Skip to main content

starnix_core/task/
process_group.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::mutable_state::{ordered_state_accessor, state_implementation};
6use crate::signals::SignalInfo;
7use crate::task::{PidTable, Session, ThreadGroup};
8use macro_rules_attribute::apply;
9use starnix_sync::{LockBefore, Locked, OrderedRwLock, ProcessGroupState};
10use starnix_uapi::pid_t;
11use starnix_uapi::signals::{SIGCONT, SIGHUP, Signal};
12use std::collections::BTreeMap;
13use std::sync::{Arc, Weak};
14
15#[derive(Debug)]
16pub struct ProcessGroupMutableState {
17    /// The thread_groups in the process group.
18    ///
19    /// The references to ThreadGroup is weak to prevent cycles as ThreadGroup have a Arc reference to their process group.
20    /// It is still expected that these weak references are always valid, as thread groups must unregister
21    /// themselves before they are deleted.
22    thread_groups: BTreeMap<pid_t, Weak<ThreadGroup>>,
23
24    /// Whether this process group is orphaned and already notified its members.
25    orphaned: bool,
26}
27
28#[derive(Debug)]
29pub struct ProcessGroup {
30    /// The session of the process group.
31    pub session: Arc<Session>,
32
33    /// The leader of the process group.
34    pub leader: pid_t,
35
36    /// The mutable state of the ProcessGroup.
37    mutable_state: OrderedRwLock<ProcessGroupMutableState, ProcessGroupState>,
38}
39
40impl PartialEq for ProcessGroup {
41    fn eq(&self, other: &Self) -> bool {
42        self.leader == other.leader
43    }
44}
45
46impl Eq for ProcessGroup {}
47
48impl std::hash::Hash for ProcessGroup {
49    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
50        self.leader.hash(state);
51    }
52}
53
54/// A process group is a set of processes that are considered to be a unit for the purposes of job
55/// control and signal delivery. Each process in a process group has the same process group
56/// ID (PGID). The process with the same PID as the PGID is called the process group leader.
57///
58/// When a signal is sent to a process group, it is delivered to all processes in the group,
59/// including the process group leader. This allows a single signal to be used to control all
60/// processes in a group, such as stopping or resuming them all.
61///
62/// Process groups are also used for job control. The foreground and background process groups of a
63/// terminal are used to determine which processes can read from and write to the terminal. The
64/// foreground process group is the only process group that can read from and write to the terminal
65/// at any given time.
66///
67/// When a process forks from its parent, the child process inherits the parent's PGID. A process
68/// can also explicitly change its own PGID using the setpgid() system call.
69///
70/// Process groups are destroyed when the last process in the group exits.
71impl ProcessGroup {
72    pub fn new(leader: pid_t, session: Option<Arc<Session>>) -> Arc<ProcessGroup> {
73        let session = session.unwrap_or_else(|| Session::new(leader));
74        let process_group = Arc::new(ProcessGroup {
75            session: session.clone(),
76            leader,
77            mutable_state: OrderedRwLock::new(ProcessGroupMutableState {
78                thread_groups: BTreeMap::new(),
79                orphaned: false,
80            }),
81        });
82        session.write().insert(&process_group);
83        process_group
84    }
85
86    ordered_state_accessor!(
87        ProcessGroup,
88        mutable_state,
89        ProcessGroupState,
90        ProcessGroupMutableState
91    );
92
93    pub fn insert<L>(&self, locked: &mut Locked<L>, thread_group: &ThreadGroup)
94    where
95        L: LockBefore<ProcessGroupState>,
96    {
97        self.write(locked)
98            .thread_groups
99            .insert(thread_group.leader, thread_group.weak_self.clone());
100    }
101
102    /// Removes the thread group from the process group. Returns whether the process group is empty.
103    pub fn remove<L>(&self, locked: &mut Locked<L>, thread_group: &ThreadGroup) -> bool
104    where
105        L: LockBefore<ProcessGroupState>,
106    {
107        let is_session_leader = self.session.leader == thread_group.leader;
108        let is_empty = self.write(locked).remove(thread_group);
109        if is_session_leader {
110            // If the exiting thread group is the session leader, disassociate the controlling
111            // terminal. This must be called after `remove` drops the ProcessGroup write lock to
112            // prevent deadlock. Calling it after `remove` also ensures that the exiting thread
113            // group is no longer in the process group when we attempt to send SIGHUP/SIGCONT to the
114            // foreground process group, avoiding a self-deadlock where the exiting thread group
115            // attempts to write-lock itself.
116            self.session.disassociate_controlling_terminal(locked);
117        }
118        is_empty
119    }
120
121    pub fn send_signals<L>(&self, locked: &mut Locked<L>, signals: &[Signal])
122    where
123        L: LockBefore<ProcessGroupState>,
124    {
125        let thread_groups = self.read(locked).thread_groups().collect::<Vec<_>>();
126        Self::send_signals_to_thread_groups(signals, thread_groups);
127    }
128
129    /// Check whether the process group became orphaned. If this is the case, send signals to its
130    /// members if at least one is stopped.
131    ///
132    /// Takes a read lock on the PidTable to ensure the object cannot be removed while this method
133    /// is running.
134    pub fn check_orphaned<L>(&self, locked: &mut Locked<L>, _pids: &PidTable)
135    where
136        L: LockBefore<ProcessGroupState>,
137    {
138        let thread_groups = {
139            let state = self.read(locked);
140            if state.orphaned {
141                return;
142            }
143            state.thread_groups().collect::<Vec<_>>()
144        };
145        for tg in thread_groups {
146            let Some(parent) = tg.read().parent.clone() else {
147                return;
148            };
149            let parent = parent.upgrade();
150            let parent_state = parent.read();
151            if parent_state.process_group.as_ref() != self
152                && parent_state.process_group.session == self.session
153            {
154                return;
155            }
156        }
157        let thread_groups = {
158            let mut state = self.write(locked);
159            if state.orphaned {
160                return;
161            }
162            state.orphaned = true;
163            state.thread_groups().collect::<Vec<_>>()
164        };
165        if thread_groups.iter().any(|tg| tg.load_stopped().is_stopping_or_stopped()) {
166            Self::send_signals_to_thread_groups(&[SIGHUP, SIGCONT], thread_groups);
167        }
168    }
169
170    fn send_signals_to_thread_groups(
171        signals: &[Signal],
172        thread_groups: impl IntoIterator<Item = impl AsRef<ThreadGroup>>,
173    ) {
174        for thread_group in thread_groups.into_iter() {
175            for &signal in signals {
176                thread_group.as_ref().write().send_signal(SignalInfo::kernel(signal));
177            }
178        }
179    }
180}
181
182#[apply(state_implementation!)]
183impl ProcessGroupMutableState<Base = ProcessGroup> {
184    pub fn thread_groups(&self) -> Box<dyn Iterator<Item = Arc<ThreadGroup>> + '_> {
185        Box::new(self.thread_groups.values().map(|t| {
186            t.upgrade()
187                .expect("Weak references to thread_groups in ProcessGroup must always be valid")
188        }))
189    }
190
191    /// Removes the thread group from the process group. Returns whether the process group is empty.
192    fn remove(&mut self, thread_group: &ThreadGroup) -> bool {
193        self.thread_groups.remove(&thread_group.leader);
194
195        self.thread_groups.is_empty()
196    }
197}