1use 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#[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 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 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 pub fn is_process_group(&self, scope: &RcuReadScope) -> bool {
97 !self.pgid_thread_groups.is_empty(scope)
98 }
99
100 pub fn is_session(&self, scope: &RcuReadScope) -> bool {
102 !self.sid_thread_groups.is_empty(scope)
103 }
104
105 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 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 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 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 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 if ignored_tg.is_some_and(|ignored| std::ptr::eq(parent_tg.as_ref(), ignored)) {
183 continue;
184 }
185
186 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 parent_pgid != self.id && parent_sid == my_sid {
202 return false;
203 }
204 }
205
206 true
207 }
208
209 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 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 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 unsafe {
257 list.push_back(&scope, RcuPtrRef::new(&scope, node_ptr));
258 }
259 }
260
261 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 unsafe {
277 list.remove(&scope, node_ptr);
278 }
279 rcu_drop(unsafe { Box::from_raw(node_ptr.as_mut_ptr()) });
282 }
283 list.is_empty(&scope)
284 }
285 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 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 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 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#[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
415pub const RESERVED_PIDS: u32 = 300;
417
418pub const PID_MAX_DEFAULT: pid_t = 1 << 15;
420
421pub const PID_MAX_LIMIT: pid_t = 1 << 22;
423
424pub const PIDS_PER_CPU_DEFAULT: pid_t = 1024;
426
427fn actual_pid_limit(limit: pid_t) -> pid_t {
429 actual_pid_limit_with_cpus(limit, zx::system_get_num_cpus())
430}
431
432fn 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 last_pid: AtomicI32,
441
442 idr: Idr<PidEntry, PidTableLock>,
444
445 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
466pub 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 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 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 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 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 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 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 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 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 pub fn max(&self) -> pid_t {
650 self.idr.max() as pid_t
651 }
652
653 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 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 assert_eq!(table.lock().allocate_pid().unwrap_err(), errno!(EAGAIN));
717
718 table.idr.lock().remove(2);
720 table.idr.lock().remove(302);
721
722 let pid = table.lock().allocate_pid().unwrap();
725 assert_eq!(pid.id, 302);
726
727 assert_eq!(table.lock().allocate_pid().unwrap_err(), errno!(EAGAIN));
729
730 table.idr.lock().remove(300);
732 let pid = table.lock().allocate_pid().unwrap();
733 assert_eq!(pid.id, 300);
734
735 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 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 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 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 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 let _allocated = kernel.pids.lock().allocate_pid().unwrap();
778 assert_eq!(kernel.pids.last_pid(), initial_last_pid);
779
780 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 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}