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, SessionDisassociation, 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!(ProcessGroup, mutable_state, ProcessGroupState);
87
88    pub fn insert<L>(&self, locked: &mut Locked<L>, thread_group: &ThreadGroup)
89    where
90        L: LockBefore<ProcessGroupState>,
91    {
92        self.write(locked)
93            .thread_groups
94            .insert(thread_group.leader, thread_group.weak_self.clone());
95    }
96
97    /// Removes the thread group from the process group.
98    /// Returns whether the process group is empty and a `SessionDisassociation`, which
99    /// the caller must use to explicitly disassociate the controlling terminal if the
100    /// exiting thread group was the session leader.
101    pub fn remove<L>(
102        &self,
103        locked: &mut Locked<L>,
104        thread_group: &ThreadGroup,
105    ) -> (bool, SessionDisassociation)
106    where
107        L: LockBefore<ProcessGroupState>,
108    {
109        let is_session_leader = self.session.leader == thread_group.leader;
110        let is_empty = self.write(locked).remove(thread_group);
111        let disassociation = if is_session_leader {
112            SessionDisassociation::new(Some(self.session.clone()))
113        } else {
114            SessionDisassociation::new(None)
115        };
116        (is_empty, disassociation)
117    }
118
119    pub fn send_signals<L>(&self, locked: &mut Locked<L>, signals: &[Signal])
120    where
121        L: LockBefore<ProcessGroupState>,
122    {
123        let thread_groups = self.read(locked).thread_groups().collect::<Vec<_>>();
124        Self::send_signals_to_thread_groups(signals, thread_groups);
125    }
126
127    /// Check whether the process group became orphaned. If this is the case, send signals to its
128    /// members if at least one is stopped.
129    ///
130    /// Takes a read lock on the PidTable to ensure the object cannot be removed while this method
131    /// is running.
132    pub fn check_orphaned<L>(&self, locked: &mut Locked<L>, _pids: &PidTable)
133    where
134        L: LockBefore<ProcessGroupState>,
135    {
136        let thread_groups = {
137            let state = self.read(locked);
138            if state.orphaned {
139                return;
140            }
141            state.thread_groups().collect::<Vec<_>>()
142        };
143        for tg in thread_groups {
144            let Some(parent) = tg.read().parent.clone() else {
145                return;
146            };
147            let parent = parent.upgrade();
148            let parent_state = parent.read();
149            if parent_state.process_group.as_ref() != self
150                && parent_state.process_group.session == self.session
151            {
152                return;
153            }
154        }
155        let thread_groups = {
156            let mut state = self.write(locked);
157            if state.orphaned {
158                return;
159            }
160            state.orphaned = true;
161            state.thread_groups().collect::<Vec<_>>()
162        };
163        if thread_groups.iter().any(|tg| tg.load_stopped().is_stopping_or_stopped()) {
164            Self::send_signals_to_thread_groups(&[SIGHUP, SIGCONT], thread_groups);
165        }
166    }
167
168    fn send_signals_to_thread_groups(
169        signals: &[Signal],
170        thread_groups: impl IntoIterator<Item = impl AsRef<ThreadGroup>>,
171    ) {
172        for thread_group in thread_groups.into_iter() {
173            for &signal in signals {
174                thread_group.as_ref().write().send_signal(SignalInfo::kernel(signal));
175            }
176        }
177    }
178}
179
180#[apply(state_implementation!)]
181impl ProcessGroupMutableState<Base = ProcessGroup> {
182    pub fn thread_groups(&self) -> Box<dyn Iterator<Item = Arc<ThreadGroup>> + '_> {
183        Box::new(self.thread_groups.values().map(|t| {
184            t.upgrade()
185                .expect("Weak references to thread_groups in ProcessGroup must always be valid")
186        }))
187    }
188
189    /// Removes the thread group from the process group. Returns whether the process group is empty.
190    fn remove(&mut self, thread_group: &ThreadGroup) -> bool {
191        self.thread_groups.remove(&thread_group.leader);
192
193        self.thread_groups.is_empty()
194    }
195}