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

veridian_kernel/process/
table.rs

1//! Global process table implementation
2//!
3//! The process table maintains a global view of all processes in the system
4//! and provides efficient lookup operations.
5
6#[cfg(feature = "alloc")]
7extern crate alloc;
8
9#[cfg(feature = "alloc")]
10use alloc::{boxed::Box, collections::BTreeMap, vec::Vec};
11
12use spin::Mutex;
13
14use super::{pcb::ProcessState, Process, ProcessId};
15#[allow(unused_imports)]
16use crate::{error::KernelError, println};
17
18/// Process table entry
19#[cfg(feature = "alloc")]
20pub struct ProcessEntry {
21    /// The process
22    pub process: Box<Process>,
23    /// Reference count (for safe access)
24    pub ref_count: usize,
25}
26
27/// Global process table
28pub struct ProcessTable {
29    /// Process entries indexed by PID
30    #[cfg(feature = "alloc")]
31    entries: Mutex<BTreeMap<ProcessId, ProcessEntry>>,
32
33    /// Simple array-based storage for no-alloc mode
34    #[cfg(not(feature = "alloc"))]
35    entries: Mutex<ProcessArray>,
36
37    /// Number of active processes
38    pub process_count: core::sync::atomic::AtomicUsize,
39}
40
41/// Fixed-size process array for no-alloc mode
42#[cfg(not(feature = "alloc"))]
43pub struct ProcessArray {
44    processes: [Option<Process>; super::MAX_PROCESSES],
45    count: usize,
46}
47
48#[cfg(not(feature = "alloc"))]
49impl ProcessArray {
50    const fn new() -> Self {
51        Self {
52            processes: [const { None }; super::MAX_PROCESSES],
53            count: 0,
54        }
55    }
56}
57
58impl Default for ProcessTable {
59    fn default() -> Self {
60        Self {
61            #[cfg(feature = "alloc")]
62            entries: Mutex::new(BTreeMap::new()),
63            #[cfg(not(feature = "alloc"))]
64            entries: Mutex::new(ProcessArray::new()),
65            process_count: core::sync::atomic::AtomicUsize::new(0),
66        }
67    }
68}
69
70impl ProcessTable {
71    /// Create a new process table
72    pub const fn new() -> Self {
73        Self {
74            #[cfg(feature = "alloc")]
75            entries: Mutex::new(BTreeMap::new()),
76            #[cfg(not(feature = "alloc"))]
77            entries: Mutex::new(ProcessArray::new()),
78            process_count: core::sync::atomic::AtomicUsize::new(0),
79        }
80    }
81
82    /// Add a process to the table
83    #[cfg(feature = "alloc")]
84    pub fn add_process(&self, process: Process) -> Result<ProcessId, KernelError> {
85        let pid = process.pid;
86        let mut entries = self.entries.lock();
87
88        if entries.contains_key(&pid) {
89            return Err(KernelError::AlreadyExists {
90                resource: "process",
91                id: pid.0,
92            });
93        }
94
95        entries.insert(
96            pid,
97            ProcessEntry {
98                process: Box::new(process),
99                ref_count: 1,
100            },
101        );
102
103        self.process_count
104            .fetch_add(1, core::sync::atomic::Ordering::Relaxed);
105        Ok(pid)
106    }
107
108    /// Add a process to the table (no-alloc version)
109    #[cfg(not(feature = "alloc"))]
110    pub fn add_process(&self, process: Process) -> Result<ProcessId, KernelError> {
111        let pid = process.pid;
112        let mut entries = self.entries.lock();
113
114        if entries.count >= super::MAX_PROCESSES {
115            return Err(KernelError::ResourceExhausted {
116                resource: "process table",
117            });
118        }
119
120        // Find empty slot
121        for i in 0..super::MAX_PROCESSES {
122            if entries.processes[i].is_none() {
123                entries.processes[i] = Some(process);
124                entries.count += 1;
125                self.process_count
126                    .fetch_add(1, core::sync::atomic::Ordering::Relaxed);
127                return Ok(pid);
128            }
129        }
130
131        Err(KernelError::ResourceExhausted {
132            resource: "process table",
133        })
134    }
135
136    /// Remove a process from the table
137    #[cfg(feature = "alloc")]
138    pub fn remove_process(&self, pid: ProcessId) -> Option<Box<Process>> {
139        let mut entries = self.entries.lock();
140
141        if let Some(entry) = entries.remove(&pid) {
142            self.process_count
143                .fetch_sub(1, core::sync::atomic::Ordering::Relaxed);
144            Some(entry.process)
145        } else {
146            None
147        }
148    }
149
150    /// Remove a process from the table (no-alloc version)
151    #[cfg(not(feature = "alloc"))]
152    pub fn remove_process(&self, pid: ProcessId) -> Option<Process> {
153        let mut entries = self.entries.lock();
154
155        for i in 0..super::MAX_PROCESSES {
156            if let Some(ref process) = entries.processes[i] {
157                if process.pid == pid {
158                    let process = entries.processes[i].take();
159                    entries.count -= 1;
160                    self.process_count
161                        .fetch_sub(1, core::sync::atomic::Ordering::Relaxed);
162                    return process;
163                }
164            }
165        }
166
167        None
168    }
169
170    /// Get a process by PID
171    #[cfg(feature = "alloc")]
172    pub fn get_process(&self, pid: ProcessId) -> Option<&'static Process> {
173        let entries = self.entries.lock();
174
175        entries.get(&pid).map(|entry| {
176            // SAFETY: The Process is stored in a BTreeMap behind a Mutex, giving
177            // it a stable heap address. Casting to *const and back to &'static
178            // extends the borrow lifetime beyond the lock. This is sound because
179            // processes are never moved or deallocated while references exist in
180            // the current kernel model.
181            unsafe { &*(entry.process.as_ref() as *const Process) }
182        })
183    }
184
185    /// Get a process by PID (no-alloc version)
186    #[cfg(not(feature = "alloc"))]
187    pub fn get_process(&self, pid: ProcessId) -> Option<&'static Process> {
188        let entries = self.entries.lock();
189
190        for i in 0..super::MAX_PROCESSES {
191            if let Some(ref process) = entries.processes[i] {
192                if process.pid == pid {
193                    // SAFETY: The Process is stored in a fixed-size array behind a
194                    // Mutex. Casting to *const and back to &'static extends the
195                    // borrow lifetime beyond the lock. Sound because processes are
196                    // not moved or deallocated while references exist.
197                    return Some(unsafe { &*(process as *const Process) });
198                }
199            }
200        }
201
202        None
203    }
204
205    /// Get mutable access to a process
206    #[cfg(feature = "alloc")]
207    pub fn get_process_mut(&self, pid: ProcessId) -> Option<&'static mut Process> {
208        let mut entries = self.entries.lock();
209
210        entries.get_mut(&pid).map(|entry| {
211            // SAFETY: The Process is stored in a BTreeMap behind a Mutex, giving
212            // it a stable heap address. Casting to *mut and back to &'static mut
213            // extends the borrow lifetime. Sound because the Mutex prevents
214            // concurrent mutable access and processes are not moved while
215            // references exist.
216            unsafe { &mut *(entry.process.as_mut() as *mut Process) }
217        })
218    }
219
220    /// Check if a process exists
221    pub fn exists(&self, pid: ProcessId) -> bool {
222        #[cfg(feature = "alloc")]
223        {
224            self.entries.lock().contains_key(&pid)
225        }
226
227        #[cfg(not(feature = "alloc"))]
228        {
229            let entries = self.entries.lock();
230            for i in 0..super::MAX_PROCESSES {
231                if let Some(ref process) = entries.processes[i] {
232                    if process.pid == pid {
233                        return true;
234                    }
235                }
236            }
237            false
238        }
239    }
240
241    /// Get total number of processes
242    pub fn count(&self) -> usize {
243        self.process_count
244            .load(core::sync::atomic::Ordering::Relaxed)
245    }
246
247    /// Find all child processes of a parent
248    #[cfg(feature = "alloc")]
249    pub fn find_children(&self, parent_pid: ProcessId) -> Vec<ProcessId> {
250        let entries = self.entries.lock();
251        let mut children = Vec::new();
252
253        for (pid, entry) in entries.iter() {
254            if entry.process.parent == Some(parent_pid) {
255                children.push(*pid);
256            }
257        }
258
259        children
260    }
261
262    /// Iterate over all processes
263    #[cfg(feature = "alloc")]
264    pub fn for_each<F>(&self, mut f: F)
265    where
266        F: FnMut(&Process),
267    {
268        let entries = self.entries.lock();
269        for (_, entry) in entries.iter() {
270            f(&entry.process);
271        }
272    }
273
274    /// Find processes by state
275    #[cfg(feature = "alloc")]
276    pub fn find_by_state(&self, state: ProcessState) -> Vec<ProcessId> {
277        let entries = self.entries.lock();
278        let mut results = Vec::new();
279
280        for (pid, entry) in entries.iter() {
281            if entry.process.get_state() == state {
282                results.push(*pid);
283            }
284        }
285
286        results
287    }
288
289    /// Clean up zombie processes
290    #[cfg(feature = "alloc")]
291    pub fn reap_zombies(&self) -> Vec<(ProcessId, i32)> {
292        let mut results = Vec::new();
293        let zombies = self.find_by_state(ProcessState::Zombie);
294
295        for pid in zombies {
296            if let Some(process) = self.remove_process(pid) {
297                results.push((pid, process.get_exit_code()));
298            }
299        }
300
301        results
302    }
303}
304
305/// Global process table instance
306pub(crate) static PROCESS_TABLE: ProcessTable = ProcessTable::new();
307
308/// Initialize the process table
309pub fn init() {
310    println!("[PROCESS] Process table initialized");
311}
312
313/// Get a process by PID
314pub fn get_process(pid: ProcessId) -> Option<&'static Process> {
315    PROCESS_TABLE.get_process(pid)
316}
317
318/// Get mutable access to a process
319#[cfg(feature = "alloc")]
320pub fn get_process_mut(pid: ProcessId) -> Option<&'static mut Process> {
321    PROCESS_TABLE.get_process_mut(pid)
322}
323
324/// Add a process to the table
325pub fn add_process(process: Process) -> Result<ProcessId, KernelError> {
326    PROCESS_TABLE.add_process(process)
327}
328
329/// Remove a process from the table
330#[cfg(feature = "alloc")]
331pub fn remove_process(pid: ProcessId) -> Option<Box<Process>> {
332    PROCESS_TABLE.remove_process(pid)
333}
334
335/// Check if a process exists
336pub fn process_exists(pid: ProcessId) -> bool {
337    PROCESS_TABLE.exists(pid)
338}
339
340/// Get total number of processes
341pub fn process_count() -> usize {
342    PROCESS_TABLE.count()
343}