⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/process/
exit.rs

1//! Process exit, cleanup, signals, and wait
2//!
3//! Handles process termination, resource cleanup, signal delivery,
4//! zombie reaping, and parent-child wait semantics. Also provides
5//! system-wide process statistics.
6
7#[cfg(feature = "alloc")]
8extern crate alloc;
9
10#[cfg(feature = "alloc")]
11use alloc::vec::Vec;
12use core::sync::atomic::Ordering;
13
14use super::{
15    pcb::{Process, ProcessState},
16    table,
17    thread::ThreadId,
18    ProcessId,
19};
20#[allow(unused_imports)]
21use crate::{error::KernelError, println, sched};
22
23/// Exit current process
24pub fn exit_process(exit_code: i32) {
25    if let Some(process) = super::current_process() {
26        // Audit log: process exit
27        crate::security::audit::log_process_exit(process.pid.0, exit_code);
28
29        println!(
30            "[PROCESS] Process {} exiting with code {}",
31            process.pid.0, exit_code
32        );
33
34        // Set exit code
35        process.set_exit_code(exit_code);
36
37        // Mark all threads as exited
38        #[cfg(feature = "alloc")]
39        {
40            let threads = process.threads.lock();
41            for (_, thread) in threads.iter() {
42                thread.set_state(super::thread::ThreadState::Zombie);
43            }
44        }
45
46        // Clean up resources
47        cleanup_process(process);
48
49        // Mark process as zombie (parent needs to reap)
50        process.set_state(ProcessState::Zombie);
51
52        // Notify parent: send SIGCHLD and wake if blocked
53        if let Some(parent_pid) = process.parent {
54            if let Some(parent) = table::get_process(parent_pid) {
55                // Send SIGCHLD to parent (POSIX: delivered on child exit)
56                if let Err(_e) = parent.send_signal(signals::SIGCHLD as usize) {
57                    println!(
58                        "[PROCESS] Warning: Failed to send SIGCHLD to parent {}: {:?}",
59                        parent_pid.0, _e
60                    );
61                }
62
63                // Wake parent if it is blocked (e.g. in waitpid)
64                let parent_state = parent.get_state();
65                if parent_state == ProcessState::Blocked {
66                    parent.set_state(ProcessState::Ready);
67                    sched::wake_up_process(parent_pid);
68                }
69            }
70        }
71
72        // Schedule another process
73        sched::exit_task(exit_code);
74    }
75}
76
77/// Wait for child process to exit
78#[cfg(feature = "alloc")]
79pub fn wait_process(pid: Option<ProcessId>) -> Result<(ProcessId, i32), KernelError> {
80    wait_process_with_options(pid, WaitOptions::default())
81}
82
83/// Wait options for wait_process_with_options
84#[derive(Debug, Clone, Copy, Default)]
85pub struct WaitOptions {
86    /// Don't block if no child has exited (WNOHANG)
87    pub no_hang: bool,
88    /// Also return if a child has stopped (WUNTRACED)
89    pub untraced: bool,
90    /// Also return if a stopped child has been resumed (WCONTINUED)
91    pub continued: bool,
92}
93
94impl WaitOptions {
95    /// Non-blocking wait
96    pub fn no_hang() -> Self {
97        Self {
98            no_hang: true,
99            untraced: false,
100            continued: false,
101        }
102    }
103}
104
105/// Wait for child process with options
106#[cfg(feature = "alloc")]
107pub fn wait_process_with_options(
108    pid: Option<ProcessId>,
109    options: WaitOptions,
110) -> Result<(ProcessId, i32), KernelError> {
111    let current = super::current_process().ok_or(KernelError::NotInitialized {
112        subsystem: "current process",
113    })?;
114    let current_pid = current.pid;
115
116    loop {
117        // Check for zombie children
118        let children = table::PROCESS_TABLE.find_children(current_pid);
119
120        // No children at all
121        if children.is_empty() {
122            return Err(KernelError::NotFound {
123                resource: "child process",
124                id: 0,
125            });
126        }
127
128        // Check if any matching child exists
129        let mut matching_child_exists = false;
130
131        for child_pid in &children {
132            // Check if this child matches our pid filter
133            let matches_filter = pid.is_none() || pid == Some(*child_pid);
134            if !matches_filter {
135                continue;
136            }
137
138            matching_child_exists = true;
139
140            if let Some(child) = table::get_process(*child_pid) {
141                let child_state = child.get_state();
142
143                // Check for zombie (exited)
144                if child_state == ProcessState::Zombie {
145                    // Reap the zombie
146                    let exit_code = child.get_exit_code();
147
148                    // Remove from children list
149                    current.children.lock().retain(|&p| p != *child_pid);
150
151                    // Remove from process table
152                    table::remove_process(*child_pid);
153
154                    println!(
155                        "[PROCESS] Process {} reaped child {} (exit code {})",
156                        current_pid.0, child_pid.0, exit_code
157                    );
158
159                    // Encode as POSIX wait status:
160                    // Normal exit: low 7 bits = 0, bits 8-15 = exit code
161                    // WIFEXITED(s) = (s & 0x7f) == 0
162                    // WEXITSTATUS(s) = (s >> 8) & 0xff
163                    let wait_status = (exit_code & 0xff) << 8;
164                    return Ok((*child_pid, wait_status));
165                }
166
167                // Check for stopped child if WUNTRACED is set
168                if options.untraced && child_state == ProcessState::Blocked {
169                    // Return status indicating stopped (signal number in bits 8-15)
170                    // Use 0x7f as the stopped indicator with SIGSTOP (19)
171                    let status = 0x7f | (19 << 8);
172                    return Ok((*child_pid, status));
173                }
174
175                // Check for continued child if WCONTINUED is set
176                if options.continued && child_state == ProcessState::Running {
177                    // Return status indicating continued
178                    let status = 0xffff; // WIFCONTINUED indicator
179                    return Ok((*child_pid, status));
180                }
181            }
182        }
183
184        // No matching child found
185        if pid.is_some() && !matching_child_exists {
186            return Err(KernelError::ProcessNotFound {
187                pid: pid.unwrap_or(ProcessId(0)).0,
188            });
189        }
190
191        // No zombie children found
192        if options.no_hang {
193            // WNOHANG: return immediately with (0, 0) to indicate no child changed state
194            return Ok((ProcessId(0), 0));
195        }
196
197        // Boot execution model: no preemptive scheduler to context-switch
198        // to the child. If we find a Ready child (just forked, never run),
199        // run it inline to completion. After it exits, loop back to reap
200        // the zombie.
201        #[cfg(target_arch = "x86_64")]
202        {
203            let mut ran_child = false;
204            for child_pid in &children {
205                let matches_filter = pid.is_none() || pid == Some(*child_pid);
206                if !matches_filter {
207                    continue;
208                }
209                if let Some(child) = table::get_process(*child_pid) {
210                    if child.get_state() == ProcessState::Ready {
211                        // Get parent's thread ID for BOOT_CURRENT restore
212                        let parent_tid = current
213                            .threads
214                            .lock()
215                            .values()
216                            .next()
217                            .map(|t| t.tid)
218                            .unwrap_or(super::thread::ThreadId(current_pid.0));
219                        ran_child = crate::bootstrap::boot_run_forked_child(
220                            *child_pid,
221                            current_pid,
222                            parent_tid,
223                        );
224                        break;
225                    }
226                }
227            }
228            if ran_child {
229                // Child ran to completion, loop back to reap
230                continue;
231            }
232        }
233
234        // Normal scheduler path: block and wait for child to wake us.
235        println!(
236            "[PROCESS] Process {} blocking in wait() for child {}",
237            current_pid.0,
238            pid.map_or(-1, |p| p.0 as i64)
239        );
240
241        sched::block_process(current_pid);
242
243        // When we wake up, loop back to check for zombie children.
244        current.set_state(ProcessState::Running);
245
246        // Check if we were interrupted by a signal
247        if let Some(signum) = current.get_next_pending_signal() {
248            // Clear the signal and return EINTR
249            current.clear_pending_signal(signum);
250            return Err(KernelError::WouldBlock);
251        }
252    }
253}
254
255// ============================================================================
256// Signals
257// ============================================================================
258
259/// Standard signal numbers (POSIX)
260pub mod signals {
261    pub const SIGHUP: i32 = 1; // Hangup
262    pub const SIGINT: i32 = 2; // Interrupt
263    pub const SIGQUIT: i32 = 3; // Quit
264    pub const SIGILL: i32 = 4; // Illegal instruction
265    pub const SIGTRAP: i32 = 5; // Trace trap
266    pub const SIGABRT: i32 = 6; // Abort
267    pub const SIGBUS: i32 = 7; // Bus error
268    pub const SIGFPE: i32 = 8; // Floating point exception
269    pub const SIGKILL: i32 = 9; // Kill (cannot be caught)
270    pub const SIGUSR1: i32 = 10; // User signal 1
271    pub const SIGSEGV: i32 = 11; // Segmentation violation
272    pub const SIGUSR2: i32 = 12; // User signal 2
273    pub const SIGPIPE: i32 = 13; // Broken pipe
274    pub const SIGALRM: i32 = 14; // Alarm clock
275    pub const SIGTERM: i32 = 15; // Termination
276    pub const SIGSTKFLT: i32 = 16; // Stack fault
277    pub const SIGCHLD: i32 = 17; // Child status changed
278    pub const SIGCONT: i32 = 18; // Continue
279    pub const SIGSTOP: i32 = 19; // Stop (cannot be caught)
280    pub const SIGTSTP: i32 = 20; // Terminal stop
281    pub const SIGTTIN: i32 = 21; // Background read from tty
282    pub const SIGTTOU: i32 = 22; // Background write to tty
283    pub const SIGURG: i32 = 23; // Urgent data on socket
284    pub const SIGXCPU: i32 = 24; // CPU time limit exceeded
285    pub const SIGXFSZ: i32 = 25; // File size limit exceeded
286    pub const SIGVTALRM: i32 = 26; // Virtual timer expired
287    pub const SIGPROF: i32 = 27; // Profiling timer expired
288    pub const SIGWINCH: i32 = 28; // Window size changed
289    pub const SIGIO: i32 = 29; // I/O possible
290    pub const SIGPWR: i32 = 30; // Power failure
291    pub const SIGSYS: i32 = 31; // Bad system call
292}
293
294/// Signal action types
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub enum SignalAction {
297    /// Default action for signal
298    Default,
299    /// Ignore signal
300    Ignore,
301    /// Terminate process
302    Terminate,
303    /// Terminate and dump core
304    CoreDump,
305    /// Stop process
306    Stop,
307    /// Continue stopped process
308    Continue,
309    /// Call user handler at given address
310    Handler(usize),
311}
312
313/// Get default action for a signal
314pub fn default_signal_action(signal: i32) -> SignalAction {
315    use signals::*;
316    match signal {
317        SIGHUP | SIGINT | SIGKILL | SIGPIPE | SIGALRM | SIGTERM | SIGUSR1 | SIGUSR2 => {
318            SignalAction::Terminate
319        }
320        SIGQUIT | SIGILL | SIGABRT | SIGFPE | SIGSEGV | SIGBUS | SIGSYS | SIGTRAP | SIGXCPU
321        | SIGXFSZ => SignalAction::CoreDump,
322        SIGSTOP | SIGTSTP | SIGTTIN | SIGTTOU => SignalAction::Stop,
323        SIGCONT => SignalAction::Continue,
324        SIGCHLD | SIGURG | SIGWINCH | SIGIO => SignalAction::Ignore,
325        _ => SignalAction::Terminate, // Unknown signals terminate by default
326    }
327}
328
329/// Send a signal to a process (kill syscall)
330pub fn kill_process(pid: ProcessId, signal: i32) -> Result<(), KernelError> {
331    // Validate signal number
332    if !(0..=31).contains(&signal) {
333        return Err(KernelError::InvalidArgument {
334            name: "signal",
335            value: "signal number out of range (0-31)",
336        });
337    }
338
339    // Special case: signal 0 is used to check if process exists
340    if signal == 0 {
341        if table::get_process(pid).is_some() {
342            return Ok(());
343        } else {
344            return Err(KernelError::ProcessNotFound { pid: pid.0 });
345        }
346    }
347
348    let process = table::get_process(pid).ok_or(KernelError::ProcessNotFound { pid: pid.0 })?;
349
350    if !process.is_alive() {
351        return Err(KernelError::InvalidState {
352            expected: "alive",
353            actual: "dead",
354        });
355    }
356
357    println!("[PROCESS] Sending signal {} to process {}", signal, pid.0);
358
359    // Queue the signal to the process
360    process.send_signal(signal as usize)?;
361
362    // Determine the action to take
363    let handler = process.get_signal_handler(signal as usize).unwrap_or(0);
364    let action = if handler == 0 {
365        default_signal_action(signal)
366    } else if handler == 1 {
367        SignalAction::Ignore
368    } else {
369        SignalAction::Handler(handler as usize)
370    };
371
372    // Handle uncatchable signals immediately
373    match signal {
374        signals::SIGKILL => {
375            // SIGKILL always terminates immediately
376            force_terminate_process(process)?;
377        }
378        signals::SIGSTOP => {
379            // SIGSTOP always stops immediately
380            process.set_state(ProcessState::Blocked);
381            sched::block_process(pid);
382            println!("[PROCESS] Process {} stopped by SIGSTOP", pid.0);
383
384            // Notify parent with SIGCHLD (POSIX: child stopped)
385            notify_parent_sigchld(process);
386        }
387        _ => {
388            // Handle based on action
389            match action {
390                SignalAction::Ignore => {
391                    // Clear the pending signal since we're ignoring it
392                    process.clear_pending_signal(signal as usize);
393                }
394                SignalAction::Terminate | SignalAction::CoreDump => {
395                    // For default terminate/core dump actions, do it now
396                    force_terminate_process(process)?;
397                }
398                SignalAction::Stop => {
399                    process.set_state(ProcessState::Blocked);
400                    sched::block_process(pid);
401                    println!("[PROCESS] Process {} stopped by signal {}", pid.0, signal);
402
403                    // Notify parent with SIGCHLD (POSIX: child stopped)
404                    notify_parent_sigchld(process);
405                }
406                SignalAction::Continue => {
407                    if process.get_state() == ProcessState::Blocked {
408                        process.set_state(ProcessState::Ready);
409                        sched::wake_up_process(pid);
410                        println!("[PROCESS] Process {} continued by signal {}", pid.0, signal);
411
412                        // Notify parent with SIGCHLD (POSIX: child continued)
413                        notify_parent_sigchld(process);
414                    }
415                    process.clear_pending_signal(signal as usize);
416                }
417                SignalAction::Handler(_addr) => {
418                    // Signal will be delivered when process returns to user mode
419                    // The signal handling is done in the syscall return path
420                    println!(
421                        "[PROCESS] Signal {} queued for process {}, handler at {:#x}",
422                        signal, pid.0, _addr
423                    );
424
425                    // Wake up process if blocked so it can handle the signal
426                    if process.get_state() == ProcessState::Blocked {
427                        process.set_state(ProcessState::Ready);
428                        sched::wake_up_process(pid);
429                    }
430                }
431                SignalAction::Default => {
432                    // Should not reach here, but handle it as terminate
433                    force_terminate_process(process)?;
434                }
435            }
436        }
437    }
438
439    Ok(())
440}
441
442// ============================================================================
443// SIGCHLD notification helper
444// ============================================================================
445
446/// Send SIGCHLD to the parent of `process` and wake it if blocked.
447///
448/// Called when a child process exits, stops, or continues. This is the
449/// unified notification path: `exit_process` and `kill_process` (for Stop
450/// and Continue actions) both funnel through here.
451fn notify_parent_sigchld(process: &Process) {
452    if let Some(parent_pid) = process.parent {
453        if let Some(parent) = table::get_process(parent_pid) {
454            // Send SIGCHLD to parent (POSIX: delivered on child state change)
455            if let Err(_e) = parent.send_signal(signals::SIGCHLD as usize) {
456                println!(
457                    "[PROCESS] Warning: Failed to send SIGCHLD to parent {}: {:?}",
458                    parent_pid.0, _e
459                );
460            }
461
462            // Wake parent if it is blocked (e.g. in waitpid)
463            if parent.get_state() == ProcessState::Blocked {
464                parent.set_state(ProcessState::Ready);
465                sched::wake_up_process(parent_pid);
466            }
467        }
468    }
469}
470
471// ============================================================================
472// Process Cleanup
473// ============================================================================
474
475/// Force terminate a process (used by SIGKILL and unhandled fatal signals)
476fn force_terminate_process(process: &Process) -> Result<(), KernelError> {
477    let _pid = process.pid;
478    println!("[PROCESS] Force terminating process {}", _pid.0);
479
480    // Mark all threads as exited
481    #[cfg(feature = "alloc")]
482    {
483        let threads = process.threads.lock();
484        for (_, thread) in threads.iter() {
485            thread.set_state(super::thread::ThreadState::Zombie);
486
487            // Remove from scheduler if scheduled
488            if let Some(task_ptr) = thread.get_task_ptr() {
489                // SAFETY: task_ptr is a NonNull<Task> stored in the thread.
490                // We mark the task as Dead so the scheduler will not run it
491                // again. The threads lock is held, preventing concurrent
492                // modification of the thread's task pointer.
493                unsafe {
494                    let task = task_ptr.as_ptr();
495                    (*task).state = ProcessState::Dead;
496                    // Note: The scheduler will clean up dead tasks
497                }
498            }
499        }
500    }
501
502    // Clean up and mark as zombie
503    cleanup_process(process);
504    process.set_state(ProcessState::Zombie);
505
506    // Wake up parent if waiting
507    if let Some(parent_pid) = process.parent {
508        if let Some(parent) = table::get_process(parent_pid) {
509            // Send SIGCHLD to parent
510            if let Err(_e) = parent.send_signal(signals::SIGCHLD as usize) {
511                println!(
512                    "[PROCESS] Warning: Failed to send SIGCHLD to parent {}: {:?}",
513                    parent_pid.0, _e
514                );
515            }
516
517            if parent.get_state() == ProcessState::Blocked {
518                parent.set_state(ProcessState::Ready);
519                sched::wake_up_process(parent_pid);
520            }
521        }
522    }
523
524    Ok(())
525}
526
527/// Clean up process resources
528pub fn cleanup_process(process: &Process) {
529    println!(
530        "[PROCESS] Cleaning up resources for process {}",
531        process.pid.0
532    );
533
534    // Release memory (VAS-tracked data frames + page table subtrees)
535    {
536        let mut memory_space = process.memory_space.lock();
537        // Clear all mappings
538        memory_space.clear();
539    }
540
541    // Free kernel stack frames for all threads.
542    //
543    // Kernel stacks are allocated by ThreadBuilder::build() from the frame
544    // allocator but are NOT tracked in the VAS (they live in kernel space at
545    // KERNEL_STACK_REGION_BASE). Without this, every process exit leaks 16
546    // frames (64 KB) of kernel stack per thread -- 630 processes during
547    // BusyBox compilation would leak ~40 MB.
548    //
549    // User stack frames are managed by the VAS (allocated via map_page() and
550    // freed by clear() above), so they do not need separate cleanup here.
551    #[cfg(feature = "alloc")]
552    {
553        let threads = process.threads.lock();
554        for (_, thread) in threads.iter() {
555            let frame_num = thread.kernel_stack.phys_frame.load(Ordering::Acquire);
556            let page_count = thread.kernel_stack.phys_page_count.load(Ordering::Acquire);
557            if frame_num != 0 && page_count > 0 {
558                let frame = crate::mm::FrameNumber::new(frame_num);
559                if let Err(_e) = crate::mm::FRAME_ALLOCATOR
560                    .lock()
561                    .free_frames(frame, page_count)
562                {
563                    println!(
564                        "[PROCESS] Warning: failed to free kernel stack frames for tid {}: {:?}",
565                        thread.tid.0, _e
566                    );
567                }
568                // Mark as freed to prevent double-free
569                thread.kernel_stack.phys_frame.store(0, Ordering::Release);
570                thread
571                    .kernel_stack
572                    .phys_page_count
573                    .store(0, Ordering::Release);
574            }
575        }
576    }
577
578    // Release capabilities
579    {
580        let cap_space = process.capability_space.lock();
581        // Clear all capabilities
582        cap_space.clear();
583    }
584
585    // Close IPC endpoints
586    #[cfg(feature = "alloc")]
587    {
588        use crate::ipc;
589
590        // Remove all endpoints owned by this process from the global registry
591        match ipc::remove_process_endpoints(process.pid) {
592            Ok(count) => {
593                if count > 0 {
594                    println!(
595                        "[PROCESS] Removed {} IPC endpoints for process {}",
596                        count, process.pid.0
597                    );
598                }
599            }
600            Err(_e) => {
601                println!(
602                    "[PROCESS] Warning: Failed to remove IPC endpoints for process {}: {:?}",
603                    process.pid.0, _e
604                );
605            }
606        }
607
608        // Clear the local endpoint map
609        process.ipc_endpoints.lock().clear();
610    }
611
612    // Close all open file descriptors.
613    // Log a warning if the process has more than 3 fds open (stdin/stdout/stderr).
614    // This helps identify fd leaks during heavy workloads like BusyBox compilation
615    // where 213+ sequential processes must not leak fds.
616    {
617        let file_table = process.file_table.lock();
618        let open_fds = file_table.count_open();
619        if open_fds > 3 {
620            println!(
621                "[PROCESS] Warning: process {} exiting with {} open fds (expected <= 3)",
622                process.pid.0, open_fds
623            );
624        }
625        file_table.close_all();
626    }
627
628    // Reparent children to init if not zombie
629    #[cfg(feature = "alloc")]
630    {
631        let children: Vec<ProcessId> = process.children.lock().clone();
632        if !children.is_empty() && process.get_state() != ProcessState::Zombie {
633            if let Some(init_process) = table::get_process_mut(ProcessId(1)) {
634                for child_pid in children {
635                    if let Some(child) = table::get_process_mut(child_pid) {
636                        child.parent = Some(ProcessId(1));
637                        init_process.children.lock().push(child_pid);
638                        println!("[PROCESS] Reparented process {} to init", child_pid);
639                    }
640                }
641            }
642            process.children.lock().clear();
643        }
644    }
645
646    // Update CPU time statistics
647    let _cpu_time = process.cpu_time.load(Ordering::Relaxed);
648    println!(
649        "[PROCESS] Process {} used {} microseconds of CPU time",
650        process.pid.0, _cpu_time
651    );
652}
653
654// ============================================================================
655// Thread Cleanup
656// ============================================================================
657
658/// Clean up a dead thread
659#[cfg(feature = "alloc")]
660pub fn cleanup_thread(process: &Process, tid: ThreadId) -> Result<(), KernelError> {
661    // Remove thread from process
662    let mut threads = process.threads.lock();
663
664    if let Some(thread) = threads.remove(&tid) {
665        println!("[PROCESS] Cleaning up thread {}", tid.0);
666
667        // Make sure thread is marked as dead
668        thread.set_state(super::thread::ThreadState::Dead);
669
670        // Clean up scheduler task if exists
671        if let Some(task_ptr) = thread.get_task_ptr() {
672            // SAFETY: task_ptr is a NonNull<Task> stored in the thread.
673            // We clear the thread reference and mark the task as Dead
674            // for scheduler cleanup. The thread has already been marked
675            // as Dead above, so no scheduler will attempt to run it.
676            unsafe {
677                let task = task_ptr.as_ptr();
678
679                // Clear thread reference in task
680                (*task).thread_ref = None;
681
682                // Mark task for cleanup
683                (*task).state = ProcessState::Dead;
684
685                // The scheduler will eventually free the task memory
686            }
687        }
688
689        // Free user stack pages from the VAS. User stack frames are managed
690        // by the VAS (allocated via map_page), so unmap them to free the
691        // physical frames. If cleanup_process already called clear(), the
692        // mappings are gone and unmap will fail harmlessly.
693        if thread.user_stack.size > 0 {
694            let stack_base = thread.user_stack.base;
695            let stack_size = thread.user_stack.size;
696
697            let memory_space = process.memory_space.lock();
698            // Try to unmap each page individually since map_page creates
699            // per-page entries in the BTreeMap.
700            let num_pages = stack_size / 0x1000;
701            for i in 0..num_pages {
702                let page_addr = stack_base + i * 0x1000;
703                let _ = memory_space.unmap(page_addr, 0x1000);
704            }
705            println!(
706                "[PROCESS] Freed user stack at {:#x}, size {}",
707                stack_base, stack_size
708            );
709        }
710
711        // Free kernel stack frames using the stored physical frame info.
712        // ThreadBuilder::build() records the frame number and page count
713        // in the Stack struct for exactly this purpose.
714        {
715            let frame_num = thread
716                .kernel_stack
717                .phys_frame
718                .load(core::sync::atomic::Ordering::Acquire);
719            let page_count = thread
720                .kernel_stack
721                .phys_page_count
722                .load(core::sync::atomic::Ordering::Acquire);
723            if frame_num != 0 && page_count > 0 {
724                let frame = crate::mm::FrameNumber::new(frame_num);
725                if let Err(_e) = crate::mm::FRAME_ALLOCATOR
726                    .lock()
727                    .free_frames(frame, page_count)
728                {
729                    println!(
730                        "[PROCESS] Warning: Failed to free kernel stack for tid {}: {:?}",
731                        tid.0, _e
732                    );
733                } else {
734                    println!(
735                        "[PROCESS] Freed kernel stack for tid {} ({} frames)",
736                        tid.0, page_count
737                    );
738                }
739                // Mark as freed to prevent double-free
740                thread
741                    .kernel_stack
742                    .phys_frame
743                    .store(0, core::sync::atomic::Ordering::Release);
744                thread
745                    .kernel_stack
746                    .phys_page_count
747                    .store(0, core::sync::atomic::Ordering::Release);
748            }
749        }
750
751        // Clean up TLS area
752        {
753            let tls = thread.tls.lock();
754            if tls.base != 0 && tls.size > 0 {
755                // Unmap TLS from process's virtual address space
756                let memory_space = process.memory_space.lock();
757                if let Err(_e) = memory_space.unmap(tls.base, tls.size) {
758                    println!(
759                        "[PROCESS] Warning: Failed to unmap TLS at {:#x}: {}",
760                        tls.base, _e
761                    );
762                } else {
763                    println!(
764                        "[PROCESS] Freed TLS area at {:#x}, size {}",
765                        tls.base, tls.size
766                    );
767                }
768            }
769        }
770
771        Ok(())
772    } else {
773        Err(KernelError::ThreadNotFound { tid: tid.0 })
774    }
775}
776
777/// Reap zombie threads in a process
778#[cfg(feature = "alloc")]
779pub fn reap_zombie_threads(process: &Process) -> Vec<(ThreadId, i32)> {
780    let mut reaped = Vec::new();
781    let threads = process.threads.lock();
782
783    // Find all zombie threads
784    let zombies: Vec<ThreadId> = threads
785        .iter()
786        .filter(|(_, thread)| thread.get_state() == super::thread::ThreadState::Zombie)
787        .map(|(tid, _)| *tid)
788        .collect();
789
790    drop(threads);
791
792    // Clean up each zombie thread
793    for tid in zombies {
794        if let Ok(()) = cleanup_thread(process, tid) {
795            // Get exit code before cleanup
796            if let Some(thread) = process.get_thread(tid) {
797                let exit_code = thread.exit_code.load(Ordering::Acquire) as i32;
798                reaped.push((tid, exit_code));
799            }
800        }
801    }
802
803    reaped
804}
805
806// ============================================================================
807// Process Statistics
808// ============================================================================
809
810/// Process statistics
811#[cfg(feature = "alloc")]
812pub struct ProcessStats {
813    pub total_processes: usize,
814    pub running_processes: usize,
815    pub blocked_processes: usize,
816    pub zombie_processes: usize,
817    pub total_threads: usize,
818    pub total_cpu_time: u64,
819    pub total_memory_usage: u64,
820}
821
822/// Get system-wide process statistics
823#[cfg(feature = "alloc")]
824pub fn get_process_stats() -> ProcessStats {
825    let mut stats = ProcessStats {
826        total_processes: 0,
827        running_processes: 0,
828        blocked_processes: 0,
829        zombie_processes: 0,
830        total_threads: 0,
831        total_cpu_time: 0,
832        total_memory_usage: 0,
833    };
834
835    table::PROCESS_TABLE.for_each(|process| {
836        stats.total_processes += 1;
837        stats.total_threads += process.thread_count();
838        stats.total_cpu_time += process.get_cpu_time();
839        stats.total_memory_usage += process
840            .memory_stats
841            .virtual_size
842            .load(core::sync::atomic::Ordering::Relaxed);
843
844        match process.get_state() {
845            ProcessState::Running => stats.running_processes += 1,
846            ProcessState::Blocked => stats.blocked_processes += 1,
847            ProcessState::Zombie => stats.zombie_processes += 1,
848            _ => {}
849        }
850    });
851
852    stats
853}