Skip to main content

starnix_core/task/
pid_table.rs

1// Copyright 2021 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::device::terminal::{Terminal, TerminalSide};
6use crate::ptrace::StopState;
7use crate::signals::SignalInfo;
8use crate::task::idr::{Idr, IdrGuard};
9use crate::task::memory_attribution::MemoryAttributionLifecycleEvent;
10use crate::task::{Task, ThreadGroup, ThreadGroupLinkAdapter, ThreadGroupLinkNode};
11use fuchsia_rcu::subtle::{RcuPtr, RcuPtrRef};
12use fuchsia_rcu::{RcuDroppable, RcuOptionBox, RcuReadScope, RcuWeak, rcu_drop};
13use fuchsia_rcu_collections::rcu_intrusive_list::RcuIntrusiveList;
14use starnix_sync::PidTableLock;
15use starnix_uapi::errors::Errno;
16use starnix_uapi::signals::Signal;
17use starnix_uapi::{errno, error, pid_t};
18use std::sync::atomic::{AtomicI32, Ordering};
19use std::sync::{Arc, Weak};
20
21#[derive(Debug, RcuDroppable)]
22enum ProcessEntry {
23    ThreadGroup(Weak<ThreadGroup>),
24    Zombie(Weak<Task>),
25}
26
27impl ProcessEntry {
28    fn thread_group(&self) -> Option<&Weak<ThreadGroup>> {
29        match self {
30            Self::ThreadGroup(group) => Some(group),
31            _ => None,
32        }
33    }
34}
35
36/// Entities identified by a pid.
37#[derive(Debug, RcuDroppable)]
38pub struct PidEntry {
39    pub id: pid_t,
40    task: RcuWeak<Task>,
41    process: RcuOptionBox<ProcessEntry>,
42    pgid_thread_groups: RcuIntrusiveList<ThreadGroupLinkNode, ThreadGroupLinkAdapter>,
43    sid_thread_groups: RcuIntrusiveList<ThreadGroupLinkNode, ThreadGroupLinkAdapter>,
44}
45
46impl PidEntry {
47    pub fn get_task(&self) -> Result<Arc<Task>, Errno> {
48        self.task.upgrade().ok_or_else(|| errno!(ESRCH))
49    }
50
51    /// Returns a representative [`Task`] for this process, including when the thread-group
52    /// leader has exited or the process is an unreaped zombie.
53    pub fn get_process_task(&self) -> Option<Arc<Task>> {
54        if let Ok(task) = self.get_task() {
55            return Some(task);
56        }
57        let process = self.process.read()?;
58        match &*process {
59            ProcessEntry::ThreadGroup(thread_group) => {
60                if let Some(task) = thread_group.upgrade().and_then(|tg| tg.read().first_task()) {
61                    return Some(task);
62                }
63                match &*self.process.read()? {
64                    ProcessEntry::Zombie(task) => task.upgrade(),
65                    ProcessEntry::ThreadGroup(_) => None,
66                }
67            }
68            ProcessEntry::Zombie(task) => task.upgrade(),
69        }
70    }
71
72    pub fn get_process(&self) -> Option<ProcessEntryRef> {
73        let process = self.process.read()?;
74        match &*process {
75            ProcessEntry::ThreadGroup(thread_group) => {
76                // Because `self.process` is read lock-free under RCU, the process may concurrently
77                // transition to a zombie in `ThreadGroup::remove()` and drop its last strong
78                // `Arc<ThreadGroup>` reference before `upgrade()` is called here.
79                Some(match thread_group.upgrade() {
80                    Some(thread_group) => ProcessEntryRef::Process(thread_group),
81                    None => ProcessEntryRef::Zombie,
82                })
83            }
84            ProcessEntry::Zombie(_) => Some(ProcessEntryRef::Zombie),
85        }
86    }
87
88    pub fn get_thread_group(&self) -> Result<Arc<ThreadGroup>, Errno> {
89        match self.get_process() {
90            Some(ProcessEntryRef::Process(tg)) => Ok(tg),
91            _ => error!(ESRCH),
92        }
93    }
94
95    /// Returns whether any member thread groups belong to this process group (PGID).
96    pub fn is_process_group(&self, scope: &RcuReadScope) -> bool {
97        !self.pgid_thread_groups.is_empty(scope)
98    }
99
100    /// Returns whether any member thread groups belong to this session (SID).
101    pub fn is_session(&self, scope: &RcuReadScope) -> bool {
102        !self.sid_thread_groups.is_empty(scope)
103    }
104
105    /// Returns an iterator over the member thread groups in this process group (PGID).
106    pub fn pgid_thread_groups<'a>(
107        &'a self,
108        scope: &'a RcuReadScope,
109    ) -> impl Iterator<Item = Arc<ThreadGroup>> + 'a {
110        self.pgid_thread_groups.iter(scope).filter_map(|n| n.thread_group.upgrade())
111    }
112
113    /// Returns an iterator over the member thread groups in this session (SID).
114    pub fn sid_thread_groups<'a>(
115        &'a self,
116        scope: &'a RcuReadScope,
117    ) -> impl Iterator<Item = Arc<ThreadGroup>> + 'a {
118        self.sid_thread_groups.iter(scope).filter_map(|n| n.thread_group.upgrade())
119    }
120
121    /// Returns whether this process group (PGID) is non-empty and belongs to `session` (SID).
122    pub fn is_process_group_in_session(&self, session: &PidEntry, scope: &RcuReadScope) -> bool {
123        self.pgid_thread_groups(scope).next().is_some_and(|member_tg| {
124            session.sid_thread_groups(scope).any(|tg| Arc::ptr_eq(&tg, &member_tg))
125        })
126    }
127
128    /// Clears the controlling terminal on all member thread groups in this session (SID).
129    ///
130    /// If `matching` is `Some((terminal, maybe_side))`, only clears `controlling_terminal`
131    /// if it refers to `terminal` (and matches `side` if specified).
132    pub fn clear_controlling_terminal(&self, matching: Option<(&Terminal, Option<TerminalSide>)>) {
133        let scope = RcuReadScope::new();
134        for tg in self.sid_thread_groups(&scope) {
135            let mut state = tg.write();
136            if *state.session != *self {
137                continue;
138            }
139            let should_clear = state.controlling_terminal.as_ref().is_some_and(|ct| {
140                let matches = match matching {
141                    Some((terminal, Some(side))) => ct.matches(terminal, side),
142                    Some((terminal, None)) => std::ptr::eq(terminal, Arc::as_ptr(&ct.terminal)),
143                    None => true,
144                };
145                matches && ct.terminal.read().controlling_session().map(Arc::as_ref) != Some(self)
146            });
147            if should_clear {
148                state.controlling_terminal = None;
149            }
150        }
151    }
152
153    /// Returns whether the process group represented by this PidEntry is orphaned.
154    ///
155    /// An orphaned process group is one where every member's parent is either in the
156    /// same process group or outside the group's session.
157    ///
158    /// If `ignored_tg` is supplied, that thread group is ignored during traversal.
159    pub fn will_become_orphaned_pgrp(
160        &self,
161        scope: &RcuReadScope,
162        ignored_tg: Option<&ThreadGroup>,
163    ) -> bool {
164        for tg in self.pgid_thread_groups(scope) {
165            if ignored_tg.is_some_and(|ignored| std::ptr::eq(tg.as_ref(), ignored)) {
166                continue;
167            }
168
169            let (parent_tg, my_sid) = {
170                let tg_state = tg.read();
171                if !tg_state.is_running() {
172                    continue;
173                }
174                (tg_state.parent.as_ref().map(|p| p.upgrade()), tg_state.session.id)
175            };
176
177            let Some(parent_tg) = parent_tg else {
178                continue;
179            };
180
181            // Skip the parent if it matches ignored_tg.
182            if ignored_tg.is_some_and(|ignored| std::ptr::eq(parent_tg.as_ref(), ignored)) {
183                continue;
184            }
185
186            // Init (PID 1) does not act as a job-controlling parent for reparented tasks.
187            if parent_tg.leader.id == 1 {
188                continue;
189            }
190
191            let (parent_pgid, parent_sid) = {
192                let parent_state = parent_tg.read();
193                if !parent_state.is_running() {
194                    continue;
195                }
196                (parent_state.process_group.id, parent_state.session.id)
197            };
198
199            // If a member has a parent in the same session but outside this process group,
200            // that parent handles job control. The group is NOT orphaned.
201            if parent_pgid != self.id && parent_sid == my_sid {
202                return false;
203            }
204        }
205
206        true
207    }
208
209    /// Returns whether any member in this process group is stopped by job control.
210    pub fn has_stopped_jobs(&self, scope: &RcuReadScope) -> bool {
211        for tg in self.pgid_thread_groups(scope) {
212            let stop_state = tg.load_stopped();
213            if matches!(stop_state, StopState::GroupStopping | StopState::GroupStopped)
214                && tg.read().is_running()
215            {
216                return true;
217            }
218        }
219        false
220    }
221
222    /// Dispatches signals to all member thread groups in this process group.
223    ///
224    /// Signals are delivered in order: each signal in `signals` is delivered to all
225    /// group members before the subsequent signal is dispatched.
226    pub fn send_signals_to_pgid(&self, signals: &[Signal]) {
227        let scope = RcuReadScope::new();
228        for &signal in signals {
229            for tg in self.pgid_thread_groups(&scope) {
230                let state = tg.write();
231                if !state.is_exited() {
232                    state.send_signal(SignalInfo::kernel(signal));
233                }
234            }
235        }
236    }
237
238    /// Attaches `thread_group` to `list` using `node_slot` to store the allocated link node.
239    ///
240    /// Allocates a fresh [`ThreadGroupLinkNode`] for this list attachment so moving a process
241    /// across process groups or sessions never mutates a node still visible to in-flight RCU
242    /// readers.
243    fn attach_thread_group_node(
244        _guard: &PidTableGuard<'_>,
245        list: &RcuIntrusiveList<ThreadGroupLinkNode, ThreadGroupLinkAdapter>,
246        node_slot: &RcuPtr<ThreadGroupLinkNode>,
247        thread_group: &Arc<ThreadGroup>,
248    ) {
249        let scope = RcuReadScope::new();
250        debug_assert!(node_slot.read(&scope).is_null());
251        let node_ptr =
252            Box::into_raw(Box::new(ThreadGroupLinkNode::new(Arc::downgrade(thread_group))));
253        node_slot.assign(node_ptr);
254        // SAFETY: Single-writer exclusion is guaranteed by `_guard`, and `node_ptr` is newly
255        // allocated for this list attachment and retired via `rcu_drop` upon detach.
256        unsafe {
257            list.push_back(&scope, RcuPtrRef::new(&scope, node_ptr));
258        }
259    }
260
261    /// Detaches the node stored in `node_slot` from `list`, returning whether `list` is now empty.
262    ///
263    /// Unlinks the node (leaving `link.next` intact for concurrent RCU readers) and schedules
264    /// deferred reclamation via [`rcu_drop`].
265    fn detach_thread_group_node(
266        _guard: &PidTableGuard<'_>,
267        list: &RcuIntrusiveList<ThreadGroupLinkNode, ThreadGroupLinkAdapter>,
268        node_slot: &RcuPtr<ThreadGroupLinkNode>,
269    ) -> bool {
270        let scope = RcuReadScope::new();
271        let node_ptr = node_slot.read(&scope);
272        if !node_ptr.is_null() {
273            node_slot.assign(std::ptr::null_mut());
274            // SAFETY: Single-writer exclusion is guaranteed by `_guard`. `remove` leaves
275            // `node.link.next` intact for concurrent RCU readers, and `node_ptr` is never reused.
276            unsafe {
277                list.remove(&scope, node_ptr);
278            }
279            // SAFETY: `node_ptr` was allocated via `Box::into_raw` in `attach_thread_group_node`
280            // and is detached at most once under `PidTableLock`.
281            rcu_drop(unsafe { Box::from_raw(node_ptr.as_mut_ptr()) });
282        }
283        list.is_empty(&scope)
284    }
285    /// Attaches a member thread group to this process group (PGID).
286    fn attach_pgid(&self, guard: &PidTableGuard<'_>, thread_group: &Arc<ThreadGroup>) {
287        Self::attach_thread_group_node(
288            guard,
289            &self.pgid_thread_groups,
290            &thread_group.pgrp_node,
291            thread_group,
292        );
293    }
294
295    /// Detaches a member thread group from this process group (PGID).
296    fn detach_pgid(&self, guard: &PidTableGuard<'_>, thread_group: &ThreadGroup) -> bool {
297        Self::detach_thread_group_node(guard, &self.pgid_thread_groups, &thread_group.pgrp_node)
298    }
299
300    /// Attaches a member thread group to this session (SID).
301    fn attach_sid(&self, guard: &PidTableGuard<'_>, thread_group: &Arc<ThreadGroup>) {
302        Self::attach_thread_group_node(
303            guard,
304            &self.sid_thread_groups,
305            &thread_group.session_node,
306            thread_group,
307        );
308    }
309
310    /// Detaches a member thread group from this session (SID).
311    fn detach_sid(&self, guard: &PidTableGuard<'_>, thread_group: &ThreadGroup) -> bool {
312        Self::detach_thread_group_node(guard, &self.sid_thread_groups, &thread_group.session_node)
313    }
314
315    #[cfg(test)]
316    pub fn new_for_test(id: pid_t) -> Pid {
317        Arc::new(Self::new(id))
318    }
319
320    fn new(id: pid_t) -> Self {
321        Self {
322            id,
323            task: Default::default(),
324            process: Default::default(),
325            pgid_thread_groups: Default::default(),
326            sid_thread_groups: Default::default(),
327        }
328    }
329
330    fn is_empty(&self, scope: &RcuReadScope) -> bool {
331        self.task.strong_count(scope) == 0
332            && self.process.is_none(scope)
333            && self.pgid_thread_groups.is_empty(scope)
334            && self.sid_thread_groups.is_empty(scope)
335    }
336}
337
338impl std::fmt::Display for PidEntry {
339    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340        write!(f, "{}", self.id)
341    }
342}
343
344impl PartialEq for PidEntry {
345    fn eq(&self, other: &Self) -> bool {
346        std::ptr::eq(self, other)
347    }
348}
349
350impl Eq for PidEntry {}
351
352impl PartialOrd for PidEntry {
353    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
354        Some(self.cmp(other))
355    }
356}
357
358impl Ord for PidEntry {
359    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
360        (self as *const Self).cmp(&(other as *const Self))
361    }
362}
363
364impl std::hash::Hash for PidEntry {
365    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
366        (self as *const Self).hash(state);
367    }
368}
369
370pub enum ProcessEntryRef {
371    Process(Arc<ThreadGroup>),
372    Zombie,
373}
374
375pub type Pid = Arc<PidEntry>;
376
377#[derive(Copy, Clone, Debug, Eq, PartialEq)]
378pub enum TaskEntryScope {
379    Task,
380    ThreadGroup,
381}
382
383/// Identifies the target process or thread for a `/proc/<pid>` or `/proc/<pid>/task/<tid>` node.
384#[derive(Clone, Debug)]
385pub struct TaskContainer {
386    pub pid: Pid,
387    pub tid: Pid,
388    pub scope: TaskEntryScope,
389}
390
391impl TaskContainer {
392    pub fn from_task(task: &Task) -> Self {
393        Self { pid: task.pid.clone(), tid: task.tid.clone(), scope: TaskEntryScope::Task }
394    }
395
396    pub fn from_pid(pid: Pid) -> Self {
397        Self { tid: pid.clone(), pid, scope: TaskEntryScope::ThreadGroup }
398    }
399
400    pub fn from_thread_group(pid: Pid, tid: Pid) -> Self {
401        Self { pid, tid, scope: TaskEntryScope::ThreadGroup }
402    }
403
404    pub fn get_task(&self) -> Result<Arc<Task>, Errno> {
405        if let Ok(task) = self.tid.get_task() {
406            return Ok(task);
407        }
408        if self.scope == TaskEntryScope::ThreadGroup && self.tid == self.pid {
409            return self.pid.get_process_task().ok_or_else(|| errno!(ESRCH));
410        }
411        error!(ESRCH)
412    }
413}
414
415/// The number of reserved PIDs in Linux. When wrapping around, PID allocation restarts at this value.
416pub const RESERVED_PIDS: u32 = 300;
417
418/// The default maximal PID considered in Linux.
419pub const PID_MAX_DEFAULT: pid_t = 1 << 15;
420
421/// The maximal PID considered in Linux.
422pub const PID_MAX_LIMIT: pid_t = 1 << 22;
423
424/// The default number of PIDs per CPU used to scale pid_max.
425pub const PIDS_PER_CPU_DEFAULT: pid_t = 1024;
426
427/// Returns the actual PID limit given a requested limit, scaled by the system's CPU count.
428fn actual_pid_limit(limit: pid_t) -> pid_t {
429    actual_pid_limit_with_cpus(limit, zx::system_get_num_cpus())
430}
431
432/// Returns the actual PID limit given a requested limit and the number of CPUs.
433fn actual_pid_limit_with_cpus(limit: pid_t, num_cpus: u32) -> pid_t {
434    let cpu_limit = (num_cpus as pid_t).saturating_mul(PIDS_PER_CPU_DEFAULT);
435    limit.max(cpu_limit).min(PID_MAX_LIMIT)
436}
437
438pub struct PidTable {
439    /// The most-recently allocated pid in this table.
440    last_pid: AtomicI32,
441
442    /// The tasks in this table, organized by pid_t using an IDR radix tree.
443    idr: Idr<PidEntry, PidTableLock>,
444
445    /// Used to notify thread group changes.
446    thread_group_notifier: RcuOptionBox<std::sync::mpsc::Sender<MemoryAttributionLifecycleEvent>>,
447}
448
449impl Default for PidTable {
450    fn default() -> Self {
451        let idr = Idr::new_cyclic(Some(RESERVED_PIDS));
452        idr.set_max(actual_pid_limit(PID_MAX_DEFAULT) as u32);
453        idr.lock().reserve_id(0);
454        Self { last_pid: AtomicI32::new(0), idr, thread_group_notifier: Default::default() }
455    }
456}
457
458impl std::fmt::Debug for PidTable {
459    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
460        f.debug_struct("PidTable")
461            .field("last_pid", &self.last_pid.load(Ordering::Relaxed))
462            .finish()
463    }
464}
465
466/// An RAII guard representing exclusive writer access to a [`PidTable`].
467///
468/// Holding this guard serializes mutations (allocations, reservations, removals)
469/// to the PID table while allowing concurrent lock-free reads.
470pub struct PidTableGuard<'a> {
471    table: &'a PidTable,
472    idr: IdrGuard<'a, PidEntry, PidTableLock>,
473}
474
475impl<'a> std::ops::Deref for PidTableGuard<'a> {
476    type Target = PidTable;
477
478    fn deref(&self) -> &Self::Target {
479        self.table
480    }
481}
482
483impl<'a> PidTableGuard<'a> {
484    /// Allocates a new PID, returning an error if the PID table is full.
485    pub fn allocate_pid(&mut self) -> Result<Pid, Errno> {
486        self.idr
487            .alloc(|id| Arc::new(PidEntry::new(id as pid_t)))
488            .map(|(_, pid)| pid)
489            .ok_or_else(|| errno!(EAGAIN))
490    }
491
492    pub fn add_task(&mut self, task: Arc<Task>) {
493        let scope = RcuReadScope::new();
494        let entry =
495            self.idr.lookup(task.tid.id as u32, &scope).expect("task.tid should be in pid table");
496        assert_eq!(entry.task.strong_count(&scope), 0);
497        if task.is_leader() {
498            assert!(entry.process.is_none(&scope));
499            self.table.last_pid.store(task.tid.id, Ordering::Relaxed);
500            // Publish process before task so lock-free RCU readers that look up a task
501            // first (e.g. /proc/<pid> lookups) always observe its process entry populated
502            // for a leader. Readers that check process first and fall back to task (such as
503            // new_pidfd) rely on task.is_leader() to handle concurrent initialization/reaping.
504            entry
505                .process
506                .update(Some(ProcessEntry::ThreadGroup(Arc::downgrade(task.thread_group()))));
507        }
508        entry.task.update(Arc::downgrade(&task));
509
510        if task.is_leader() {
511            // Notify thread group changes.
512            if let Some(notifier) = self.table.thread_group_notifier.as_ref(&scope) {
513                let mut tg_state = task.thread_group.write();
514                let _ = notifier.send(MemoryAttributionLifecycleEvent::creation(task.tid.id));
515                tg_state.notifier = Some(notifier.clone());
516            }
517        }
518    }
519
520    fn remove_item<F>(&mut self, pid: &Pid, do_remove: F)
521    where
522        F: FnOnce(&PidEntry),
523    {
524        let scope = RcuReadScope::new();
525        debug_assert_eq!(self.idr.lookup(pid.id as u32, &scope).as_ref(), Some(pid));
526        do_remove(pid);
527        if pid.is_empty(&scope) {
528            self.idr.remove(pid.id as u32);
529        }
530    }
531
532    pub fn remove_task(&mut self, tid: &Pid) {
533        self.remove_item(tid, |entry| {
534            let scope = RcuReadScope::new();
535            assert!(entry.task.strong_count(&scope) > 0);
536            entry.task.update(Weak::new());
537        });
538    }
539
540    /// Replace process with the specified `pid` with a zombie.
541    pub fn kill_process(&mut self, pid: &Pid, zombie_task: Weak<Task>) {
542        let scope = RcuReadScope::new();
543        debug_assert_eq!(self.idr.lookup(pid.id as u32, &scope).as_ref(), Some(pid));
544        assert!(matches!(pid.process.read().as_deref(), Some(ProcessEntry::ThreadGroup(_))));
545
546        pid.process.update(Some(ProcessEntry::Zombie(zombie_task)));
547    }
548
549    pub fn remove_zombie(&mut self, pid: &Pid) {
550        let scope = RcuReadScope::new();
551
552        self.remove_item(pid, |entry| {
553            assert!(matches!(entry.process.read().as_deref(), Some(ProcessEntry::Zombie(_))));
554            entry.process.update(None);
555        });
556
557        // Notify thread group changes.
558        if let Some(notifier) = self.table.thread_group_notifier.as_ref(&scope) {
559            let _ = notifier.send(MemoryAttributionLifecycleEvent::destruction(pid.id));
560        }
561    }
562
563    pub fn attach_pgid(&self, pid: &PidEntry, thread_group: &Arc<ThreadGroup>) {
564        pid.attach_pgid(self, thread_group);
565    }
566
567    pub fn detach_pgid(&mut self, pid: &Pid, thread_group: &ThreadGroup) -> bool {
568        let scope = RcuReadScope::new();
569        let is_empty = pid.detach_pgid(self, thread_group);
570        if pid.is_empty(&scope) && self.idr.lookup(pid.id as u32, &scope).as_ref() == Some(pid) {
571            self.idr.remove(pid.id as u32);
572        }
573        is_empty
574    }
575
576    pub fn attach_sid(&self, pid: &PidEntry, thread_group: &Arc<ThreadGroup>) {
577        pid.attach_sid(self, thread_group);
578    }
579
580    pub fn detach_sid(&mut self, pid: &Pid, thread_group: &ThreadGroup) -> bool {
581        let scope = RcuReadScope::new();
582        let is_empty = pid.detach_sid(self, thread_group);
583        if pid.is_empty(&scope) && self.idr.lookup(pid.id as u32, &scope).as_ref() == Some(pid) {
584            self.idr.remove(pid.id as u32);
585        }
586        is_empty
587    }
588}
589
590impl PidTable {
591    /// Acquires the lock for mutations, returning a [`PidTableGuard`].
592    pub fn lock(&self) -> PidTableGuard<'_> {
593        PidTableGuard { table: self, idr: self.idr.lock() }
594    }
595
596    pub fn get(&self, pid: pid_t) -> Result<Pid, Errno> {
597        if pid <= 0 {
598            return error!(ESRCH);
599        }
600        let scope = RcuReadScope::new();
601        self.idr.lookup(pid as u32, &scope).ok_or_else(|| errno!(ESRCH))
602    }
603
604    pub fn set_thread_group_notifier(
605        &self,
606        notifier: std::sync::mpsc::Sender<MemoryAttributionLifecycleEvent>,
607    ) {
608        self.thread_group_notifier.update(Some(notifier));
609    }
610
611    pub fn get_thread_groups<'a>(
612        &'a self,
613        scope: &'a RcuReadScope,
614    ) -> impl Iterator<Item = Arc<ThreadGroup>> + 'a {
615        self.idr.iter(&scope).flat_map(move |(_, entry)| {
616            entry
617                .process
618                .as_ref(&scope)
619                .and_then(ProcessEntry::thread_group)
620                .and_then(|g| g.upgrade())
621        })
622    }
623
624    /// Returns the process ids for all processes, including zombies.
625    pub fn process_ids(&self) -> Vec<pid_t> {
626        let scope = RcuReadScope::new();
627        self.idr
628            .iter(&scope)
629            .flat_map(|(_, entry)| entry.process.is_some(&scope).then_some(entry.id))
630            .collect()
631    }
632
633    /// Returns an iterator over the [`Pid`]s for all the currently running tasks.
634    pub fn running_task_ids<'a>(
635        &'a self,
636        scope: &'a RcuReadScope,
637    ) -> impl Iterator<Item = &'a Pid> {
638        self.idr
639            .iter(scope)
640            .map(|(_, entry)| entry)
641            .filter(|entry| entry.task.strong_count(scope) > 0)
642    }
643
644    pub fn last_pid(&self) -> pid_t {
645        self.last_pid.load(Ordering::Relaxed)
646    }
647
648    /// Returns the maximal PID value allowed for allocation.
649    pub fn max(&self) -> pid_t {
650        self.idr.max() as pid_t
651    }
652
653    /// Sets the maximal PID value allowed for allocation.
654    pub fn set_max(&self, max: pid_t) {
655        self.idr.set_max(actual_pid_limit(max) as u32);
656    }
657}
658
659#[cfg(test)]
660mod tests {
661    use super::*;
662    use crate::testing::spawn_kernel_and_run;
663    use starnix_uapi::signals::SIGCHLD;
664    use starnix_uapi::{CLONE_SIGHAND, CLONE_THREAD, CLONE_VM};
665
666    #[test]
667    fn test_pid_table_allocation() {
668        let table = PidTable::default();
669        let pid1 = table.lock().allocate_pid().unwrap();
670        assert_eq!(pid1.id, 1);
671
672        let pid2 = table.lock().allocate_pid().unwrap();
673        assert_eq!(pid2.id, 2);
674
675        assert_eq!(table.get(1).unwrap().id, 1);
676        assert_eq!(table.get(2).unwrap().id, 2);
677        assert!(table.get(0).is_err());
678        assert!(table.get(-1).is_err());
679        assert!(table.get(3).is_err());
680    }
681
682    #[test]
683    fn test_pid_table_lock_guard() {
684        let table = PidTable::default();
685        let pid1 = {
686            let mut guard = table.lock();
687            let pid = guard.allocate_pid().unwrap();
688            assert_eq!(pid.id, 1);
689            pid
690        };
691        assert_eq!(table.get(1).unwrap(), pid1);
692    }
693
694    #[test]
695    fn test_pid_table_empty_state() {
696        let table = PidTable::default();
697        assert_eq!(table.last_pid(), 0);
698        assert_eq!(table.process_ids().len(), 0);
699        let scope = RcuReadScope::new();
700        assert_eq!(table.running_task_ids(&scope).count(), 0);
701    }
702
703    #[test]
704    fn test_pid_table_max_and_wrap() {
705        let table = PidTable::default();
706        assert_eq!(table.max(), actual_pid_limit(PID_MAX_DEFAULT));
707
708        // Allocate up to the max value (1..=max).
709        let max = table.max();
710        for expected in 1..=max {
711            let pid = table.lock().allocate_pid().unwrap();
712            assert_eq!(pid.id, expected);
713        }
714
715        // The table is full up to max. Allocation fails.
716        assert_eq!(table.lock().allocate_pid().unwrap_err(), errno!(EAGAIN));
717
718        // Free PID 2 (below RESERVED_PIDS) and PID 302 (at or above RESERVED_PIDS).
719        table.idr.lock().remove(2);
720        table.idr.lock().remove(302);
721
722        // Next allocation wraps to RESERVED_PIDS (300) and allocates slot 302.
723        // Slot 2 is skipped because wrapping only allocates IDs >= RESERVED_PIDS.
724        let pid = table.lock().allocate_pid().unwrap();
725        assert_eq!(pid.id, 302);
726
727        // Now all slots >= RESERVED_PIDS are occupied, so allocation fails even though slot 2 is free.
728        assert_eq!(table.lock().allocate_pid().unwrap_err(), errno!(EAGAIN));
729
730        // Free PID 300. Allocation should reuse it.
731        table.idr.lock().remove(300);
732        let pid = table.lock().allocate_pid().unwrap();
733        assert_eq!(pid.id, 300);
734
735        // Increase max and verify new PIDs can be allocated.
736        let new_max = max + 10;
737        table.set_max(new_max);
738        assert_eq!(table.max(), new_max);
739        let pid = table.lock().allocate_pid().unwrap();
740        assert_eq!(pid.id, max + 1);
741    }
742
743    #[test]
744    fn test_actual_pid_limit() {
745        // With 1 CPU, limit scales to at least 1024, but PID_MAX_DEFAULT is 32768.
746        assert_eq!(actual_pid_limit_with_cpus(PID_MAX_DEFAULT, 1), PID_MAX_DEFAULT);
747        assert_eq!(actual_pid_limit_with_cpus(500, 1), 1024);
748
749        // With 64 CPUs, cpu_limit is 65536 > PID_MAX_DEFAULT.
750        assert_eq!(actual_pid_limit_with_cpus(PID_MAX_DEFAULT, 64), 65536);
751        assert_eq!(actual_pid_limit_with_cpus(100_000, 64), 100_000);
752
753        // Capped at PID_MAX_LIMIT.
754        assert_eq!(actual_pid_limit_with_cpus(PID_MAX_LIMIT + 1000, 1), PID_MAX_LIMIT);
755        assert_eq!(actual_pid_limit_with_cpus(PID_MAX_DEFAULT, 10_000), PID_MAX_LIMIT);
756    }
757
758    #[test]
759    fn test_get_process_after_thread_group_release() {
760        // `get_process()` reads `process` lock-free under RCU, so it can observe a
761        // `ThreadGroup` entry that `kill_process()` is concurrently replacing with `Zombie`
762        // and upgrade the `Weak` only after `CurrentTask::exit()` has dropped the last
763        // strong reference. `Weak::new()` never upgrades, modelling that released state.
764        let pid = PidEntry::new_for_test(1);
765        pid.process.update(Some(ProcessEntry::ThreadGroup(Weak::new())));
766
767        assert!(matches!(pid.get_process(), Some(ProcessEntryRef::Zombie)));
768    }
769
770    #[::fuchsia::test]
771    async fn test_pid_table_last_pid_on_thread_group() {
772        spawn_kernel_and_run(async |current_task| {
773            let kernel = current_task.kernel();
774            let initial_last_pid = kernel.pids.last_pid();
775
776            // Allocating a PID directly must not update last_pid.
777            let _allocated = kernel.pids.lock().allocate_pid().unwrap();
778            assert_eq!(kernel.pids.last_pid(), initial_last_pid);
779
780            // Cloning a thread in the same thread group must not update last_pid.
781            let _thread = current_task.clone_task_for_test(
782                (CLONE_THREAD | CLONE_VM | CLONE_SIGHAND) as u64,
783                Some(SIGCHLD),
784            );
785            assert_eq!(kernel.pids.last_pid(), initial_last_pid);
786
787            // Cloning a new process (thread group leader) must update last_pid.
788            let child = current_task.clone_task_for_test(0, Some(SIGCHLD));
789            assert_eq!(kernel.pids.last_pid(), child.get_pid());
790        })
791        .await;
792    }
793}