veridian_kernel/process/
wait.rs1#[cfg(feature = "alloc")]
9extern crate alloc;
10
11#[cfg(feature = "alloc")]
12use alloc::collections::BTreeMap;
13#[cfg(feature = "alloc")]
14use alloc::vec::Vec;
15
16use spin::Mutex;
17
18use super::{pcb::ProcessState, ProcessId};
19use crate::error::KernelError;
20
21#[derive(Debug, Clone, Copy, Default)]
27pub struct WaitOptions {
28 flags: u32,
29}
30
31impl WaitOptions {
32 pub const WNOHANG: u32 = 1;
34 pub const WUNTRACED: u32 = 2;
36 pub const WCONTINUED: u32 = 8;
38
39 pub fn from_flags(flags: u32) -> Self {
41 Self { flags }
42 }
43
44 pub fn is_nohang(&self) -> bool {
46 self.flags & Self::WNOHANG != 0
47 }
48
49 pub fn is_untraced(&self) -> bool {
51 self.flags & Self::WUNTRACED != 0
52 }
53
54 pub fn is_continued(&self) -> bool {
56 self.flags & Self::WCONTINUED != 0
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum WaitStatus {
67 Exited(i32),
69 Signaled(i32),
71 Stopped(i32),
73 Continued,
75}
76
77impl WaitStatus {
78 pub fn to_raw(self) -> i32 {
85 match self {
86 Self::Exited(code) => (code & 0xFF) << 8,
87 Self::Signaled(sig) => sig & 0x7F,
88 Self::Stopped(sig) => 0x7F | (sig << 8),
89 Self::Continued => 0xFFFF_u16 as i32,
90 }
91 }
92}
93
94#[derive(Debug, Clone, Copy)]
100struct WaitEntry {
101 waiter_pid: ProcessId,
103 #[allow(dead_code)] target_pid: Option<ProcessId>,
106 #[allow(dead_code)] options: WaitOptions,
109}
110
111#[cfg(feature = "alloc")]
119struct WaitQueue {
120 entries: BTreeMap<ProcessId, Vec<WaitEntry>>,
122}
123
124#[cfg(feature = "alloc")]
125impl WaitQueue {
126 const fn new() -> Self {
127 Self {
128 entries: BTreeMap::new(),
129 }
130 }
131
132 fn register(&mut self, entry: WaitEntry) {
134 self.entries
135 .entry(entry.waiter_pid)
136 .or_default()
137 .push(entry);
138 }
139
140 fn remove_waiter(&mut self, parent_pid: ProcessId) {
142 self.entries.remove(&parent_pid);
143 }
144
145 #[allow(dead_code)] fn has_waiter(&self, parent_pid: ProcessId) -> bool {
148 self.entries.get(&parent_pid).is_some_and(|v| !v.is_empty())
149 }
150}
151
152#[cfg(feature = "alloc")]
154static WAIT_QUEUE: Mutex<WaitQueue> = Mutex::new(WaitQueue::new());
155
156#[cfg(feature = "alloc")]
172pub fn sys_waitpid(pid: i64, options: WaitOptions) -> Result<(ProcessId, WaitStatus), KernelError> {
173 let current = super::current_process().ok_or(KernelError::NotInitialized {
174 subsystem: "current process",
175 })?;
176 let parent_pid = current.pid;
177
178 let target: Option<ProcessId> = if pid > 0 {
180 Some(ProcessId(pid as u64))
181 } else {
182 None };
184
185 loop {
186 let children = super::table::PROCESS_TABLE.find_children(parent_pid);
188
189 if children.is_empty() {
190 return Err(KernelError::NotFound {
191 resource: "child process",
192 id: 0,
193 });
194 }
195
196 let mut target_exists = false;
197
198 for child_pid in &children {
199 if let Some(target_pid) = target {
200 if *child_pid != target_pid {
201 continue;
202 }
203 }
204 target_exists = true;
205
206 if let Some(child) = super::table::get_process(*child_pid) {
207 let state = child.get_state();
208
209 if state == ProcessState::Zombie {
211 let exit_code = child.get_exit_code();
212 let status = WaitStatus::Exited(exit_code);
213
214 collect_zombie(*child_pid, parent_pid)?;
216
217 return Ok((*child_pid, status));
218 }
219
220 if options.is_untraced() && state == ProcessState::Blocked {
222 return Ok((*child_pid, WaitStatus::Stopped(19)));
224 }
225
226 if options.is_continued()
228 && (state == ProcessState::Running || state == ProcessState::Ready)
229 {
230 return Ok((*child_pid, WaitStatus::Continued));
231 }
232 }
233 }
234
235 if let Some(target_pid) = target {
237 if !target_exists {
238 return Err(KernelError::ProcessNotFound { pid: target_pid.0 });
239 }
240 }
241
242 if options.is_nohang() {
244 return Err(KernelError::WouldBlock);
245 }
246
247 {
249 let mut wq = WAIT_QUEUE.lock();
250 wq.register(WaitEntry {
251 waiter_pid: parent_pid,
252 target_pid: target,
253 options,
254 });
255 }
256
257 current.set_state(ProcessState::Blocked);
260 crate::sched::yield_cpu();
261 current.set_state(ProcessState::Running);
262
263 if let Some(signum) = current.get_next_pending_signal() {
265 current.clear_pending_signal(signum);
266 WAIT_QUEUE.lock().remove_waiter(parent_pid);
268 return Err(KernelError::WouldBlock);
269 }
270 }
271}
272
273#[cfg(feature = "alloc")]
282pub fn notify_parent(child_pid: ProcessId, status: WaitStatus) {
283 let parent_pid = if let Some(child) = super::table::get_process(child_pid) {
285 child.parent
286 } else {
287 None
288 };
289
290 let parent_pid = match parent_pid {
291 Some(pid) => pid,
292 None => return, };
294
295 let _ = status; if let Some(parent) = super::table::get_process(parent_pid) {
299 use super::exit::signals::SIGCHLD;
300 if let Err(_e) = parent.send_signal(SIGCHLD as usize) {
301 crate::kprintln!("[PROCESS] Warning: Failed to send SIGCHLD to parent");
302 }
303
304 if parent.get_state() == ProcessState::Blocked {
306 parent.set_state(ProcessState::Ready);
307 crate::sched::wake_up_process(parent_pid);
308 }
309 }
310
311 WAIT_QUEUE.lock().remove_waiter(parent_pid);
314}
315
316#[cfg(feature = "alloc")]
325pub fn collect_zombie(child_pid: ProcessId, parent_pid: ProcessId) -> Result<(), KernelError> {
326 if let Some(parent) = super::table::get_process(parent_pid) {
328 parent.children.lock().retain(|&p| p != child_pid);
329 }
330
331 super::table::remove_process(child_pid);
333
334 Ok(())
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
344 fn test_wait_options_default() {
345 let opts = WaitOptions::default();
346 assert!(!opts.is_nohang());
347 assert!(!opts.is_untraced());
348 assert!(!opts.is_continued());
349 }
350
351 #[test]
352 fn test_wait_options_nohang() {
353 let opts = WaitOptions::from_flags(WaitOptions::WNOHANG);
354 assert!(opts.is_nohang());
355 assert!(!opts.is_untraced());
356 assert!(!opts.is_continued());
357 }
358
359 #[test]
360 fn test_wait_options_combined() {
361 let opts = WaitOptions::from_flags(WaitOptions::WNOHANG | WaitOptions::WUNTRACED);
362 assert!(opts.is_nohang());
363 assert!(opts.is_untraced());
364 assert!(!opts.is_continued());
365 }
366
367 #[test]
368 fn test_wait_options_all_flags() {
369 let opts = WaitOptions::from_flags(
370 WaitOptions::WNOHANG | WaitOptions::WUNTRACED | WaitOptions::WCONTINUED,
371 );
372 assert!(opts.is_nohang());
373 assert!(opts.is_untraced());
374 assert!(opts.is_continued());
375 }
376
377 #[test]
380 fn test_wait_status_exited() {
381 let status = WaitStatus::Exited(42);
382 assert_eq!(status, WaitStatus::Exited(42));
383 assert_eq!(status.to_raw(), 42 << 8);
385 }
386
387 #[test]
388 fn test_wait_status_signaled() {
389 let status = WaitStatus::Signaled(11); assert_eq!(status.to_raw(), 11);
391 }
392
393 #[test]
394 fn test_wait_status_stopped() {
395 let status = WaitStatus::Stopped(19);
397 assert_eq!(status.to_raw(), 0x7F | (19 << 8));
398 }
399
400 #[test]
401 fn test_wait_status_continued() {
402 let status = WaitStatus::Continued;
403 assert_eq!(status.to_raw(), 0xFFFF_u16 as i32);
404 }
405
406 #[test]
407 fn test_wait_status_equality() {
408 assert_eq!(WaitStatus::Exited(0), WaitStatus::Exited(0));
409 assert_ne!(WaitStatus::Exited(0), WaitStatus::Exited(1));
410 assert_ne!(WaitStatus::Exited(0), WaitStatus::Continued);
411 }
412}