Skip to main content

strat9_kernel/process/scheduler/
task_ops.rs

1use super::{runtime_ops::finish_switch, *};
2use crate::{memory::UserSliceWrite, sync::FixedQueue};
3
4const PENDING_SILO_CLEANUPS_CAPACITY: usize = 256;
5
6static PENDING_SILO_CLEANUPS: SpinLock<FixedQueue<TaskId, PENDING_SILO_CLEANUPS_CAPACITY>> =
7    SpinLock::new(FixedQueue::new());
8
9/// Mark the current task as Dead and yield to the scheduler.
10///
11/// Called by SYS_PROC_EXIT. The task will not be re-queued because
12/// `pick_next_task()` only re-queues tasks in `Running` state.
13/// This function does not return.
14pub fn exit_current_task(exit_code: i32) -> ! {
15    // -- clear_child_tid (POSIX pthread join) --
16    // Must happen BEFORE we drop the address space - write 0 to the TID pointer
17    // and do a futex_wake so any waiting pthread_join() can proceed.
18    if let Some(task) = current_task_clone() {
19        let tidptr = task
20            .clear_child_tid
21            .load(core::sync::atomic::Ordering::Relaxed);
22        if tidptr != 0 {
23            let zero = 0u32.to_ne_bytes();
24            // POSIX clear_child_tid targets a userspace u32; keep the existing
25            // alignment check for futex semantics, but validate the mapping via
26            // UserSliceWrite instead of dereferencing the raw userspace pointer.
27            if (tidptr & 3) == 0 {
28                if let Ok(user) = UserSliceWrite::new(tidptr, zero.len()) {
29                    user.copy_from(&zero);
30                }
31                // Futex wake: wake all threads waiting on this address (e.g. pthread_join).
32                let _ = crate::syscall::futex::sys_futex_wake(tidptr, u32::MAX);
33            }
34        }
35
36        // -- robust_list (clean up held mutexes) --
37        // Walk the robust list and mark any held futexes as FUTEX_OWNER_DIED,
38        // then wake waiters. Prevents deadlocks when a thread dies holding a mutex.
39        crate::syscall::robust_list::cleanup_robust_list(&task);
40    }
41
42    let cpu_index = current_cpu_index();
43    let mut parent_to_signal: Option<TaskId> = None;
44    let mut ipi_to_cpu: Option<usize> = None;
45    {
46        let saved_flags = save_flags_and_cli();
47        let mut scheduler = GLOBAL_SCHED_STATE.lock();
48        let current = {
49            let local = LOCAL_SCHEDULERS[cpu_index].lock();
50            local.as_ref().and_then(|cpu| cpu.current_task.clone())
51        };
52        if let Some(ref mut sched) = *scheduler {
53            if let Some(current) = current {
54                let current_id = current.id;
55                let current_pid = current.pid;
56                let parent = {
57                    let identity = SCHED_IDENTITY.read();
58                    identity.parent_of.get(&current_id).copied()
59                };
60                let _ = sched.clear_task_wake_deadline_locked(current_id);
61                current.set_state(TaskState::Dead);
62                // Do NOT call cleanup_task_resources or all_tasks.remove() here!
63                // The task is still in current_task[cpu_index], and an interrupt
64                // could access it. Instead, mark it Dead and let pick_next_task
65                // handle the cleanup when it moves the task to task_to_drop.
66                // We only remove task_cpu and identity mappings to prevent
67                // lookups while the task is dying.
68                sched.task_cpu.remove(&current_id);
69                {
70                    let mut identity = SCHED_IDENTITY.write();
71                    GlobalSchedState::unregister_identity_locked(
72                        &mut identity,
73                        current_id,
74                        current_pid,
75                        current.tid,
76                    );
77                    identity.parent_of.remove(&current_id);
78                }
79
80                ipi_to_cpu = {
81                    let mut identity = SCHED_IDENTITY.write();
82                    reparent_children(sched, &mut identity, current_id)
83                };
84
85                if parent.is_some() {
86                    sched.zombies.insert(current_id, (exit_code, current_pid));
87                }
88                if let Some(parent_id) = parent {
89                    let (_, ipi_wake) = sched.wake_task_locked(parent_id);
90                    if ipi_to_cpu.is_none() {
91                        ipi_to_cpu = ipi_wake;
92                    }
93                    parent_to_signal = Some(parent_id);
94                }
95            }
96        }
97        drop(scheduler);
98        restore_flags(saved_flags);
99    }
100    if let Some(ci) = ipi_to_cpu {
101        send_resched_ipi_to_cpu(ci);
102    }
103
104    if let Some(parent_id) = parent_to_signal {
105        // Must happen outside scheduler lock to avoid lock recursion.
106        let _ =
107            crate::process::signal::send_signal(parent_id, crate::process::signal::Signal::SIGCHLD);
108    }
109
110    // Yield to pick the next task. Since we're Dead, we won't come back.
111    // Use yield_dead_task() which bypasses the PreemptGuard check : the task
112    // is already marked Dead and will never run again, so the guard is irrelevant.
113    // Using yield_task() here would silently return if a PreemptGuard is active,
114    // leaving the dead task spinning in the hlt() loop below.
115    yield_dead_task();
116
117    // Safety net - should never reach here
118    loop {
119        crate::arch::x86_64::hlt();
120    }
121}
122
123/// Get the current task's ID (if any task is running).
124pub fn current_task_id() -> Option<TaskId> {
125    let saved_flags = save_flags_and_cli();
126    let cpu_index = current_cpu_index();
127    let id = LOCAL_SCHEDULERS[cpu_index]
128        .lock()
129        .as_ref()
130        .and_then(|cpu| cpu.current_task.as_ref().map(|t| t.id));
131    restore_flags(saved_flags);
132    id
133}
134
135/// Get the current task's ID without blocking (safe for exceptions).
136pub fn current_task_id_try() -> Option<TaskId> {
137    let saved_flags = save_flags_and_cli();
138    let cpu_index = current_cpu_index();
139    let id = LOCAL_SCHEDULERS[cpu_index]
140        .try_lock_no_irqsave()
141        .and_then(|guard| {
142            guard
143                .as_ref()
144                .and_then(|cpu| cpu.current_task.as_ref().map(|t| t.id))
145        });
146    restore_flags(saved_flags);
147    id
148}
149
150/// Get the current process ID (POSIX pid).
151pub fn current_pid() -> Option<Pid> {
152    current_task_clone().map(|t| t.pid)
153}
154
155/// Get the current thread ID (POSIX tid).
156pub fn current_tid() -> Option<Tid> {
157    current_task_clone().map(|t| t.tid)
158}
159
160/// Get the current process group id.
161pub fn current_pgid() -> Option<Pid> {
162    current_task_clone().map(|t| t.pgid.load(Ordering::Relaxed))
163}
164
165/// Get the current session id.
166pub fn current_sid() -> Option<Pid> {
167    current_task_clone().map(|t| t.sid.load(Ordering::Relaxed))
168}
169
170/// Get the current task (cloned Arc), if any.
171#[track_caller]
172pub fn current_task_clone() -> Option<Arc<Task>> {
173    let saved_flags = save_flags_and_cli();
174    let cpu_index = current_cpu_index();
175    let caller = core::panic::Location::caller();
176    let task = LOCAL_SCHEDULERS[cpu_index].lock().as_ref().and_then(|cpu| {
177        let arc = cpu.current_task.as_ref()?;
178        let strong = Arc::strong_count(arc);
179        // Heuristic only: strong_count can move concurrently, so this is a
180        // diagnostic signal for suspicious scheduler state, not a formal
181        // corruption proof. Keep the warning but do not mutate scheduler state.
182        if strong == 0 || strong > (isize::MAX as usize) / 2 {
183            let ptr = Arc::as_ptr(arc) as *const u8;
184            crate::serial_println!(
185                "[sched] suspicious Arc refcount (heuristic): cpu={} strong={:#x} ptr={:p} caller={}:{}",
186                cpu_index,
187                strong,
188                ptr,
189                caller.file(),
190                caller.line(),
191            );
192        }
193        Some(arc.clone())
194    });
195    restore_flags(saved_flags);
196    task
197}
198
199/// Best-effort, non-blocking variant of [`current_task_clone`].
200///
201/// Returns `None` when the scheduler lock is contended.
202/// Useful in cleanup paths where blocking on `GLOBAL_SCHED_STATE.lock()` could deadlock.
203#[track_caller]
204pub fn current_task_clone_try() -> Option<Arc<Task>> {
205    let saved_flags = save_flags_and_cli();
206    let cpu_index = current_cpu_index();
207    let caller = core::panic::Location::caller();
208    let task = LOCAL_SCHEDULERS[cpu_index]
209        .try_lock_no_irqsave()
210        .and_then(|guard| {
211            guard.as_ref().and_then(|cpu| {
212                let arc = cpu.current_task.as_ref()?;
213                let strong = Arc::strong_count(arc);
214                // Heuristic only: strong_count can move concurrently, so this is a
215                // diagnostic signal for suspicious scheduler state, not a formal
216                // corruption proof.
217                if strong == 0 || strong > (isize::MAX as usize) / 2 {
218                    let ptr = Arc::as_ptr(arc) as *const u8;
219                    crate::serial_println!(
220                        "[sched] suspicious Arc refcount (heuristic): cpu={} strong={:#x} ptr={:p} caller={}:{}",
221                        cpu_index,
222                        strong,
223                        ptr,
224                        caller.file(),
225                        caller.line(),
226                    );
227                }
228                Some(arc.clone())
229            })
230        });
231    restore_flags(saved_flags);
232    task
233}
234
235/// Debug-only blocking variant used to diagnose early ring3 entry stalls.
236///
237/// Spins with `try_lock()` so we can emit progress logs instead of blocking
238/// silently on `GLOBAL_SCHED_STATE.lock()`.
239pub fn current_task_clone_spin_debug(trace_label: &str) -> Option<Arc<Task>> {
240    let saved_flags = save_flags_and_cli();
241    let cpu_index = current_cpu_index();
242    let mut spins = 0usize;
243    let result = loop {
244        if let Some(guard) = LOCAL_SCHEDULERS[cpu_index].try_lock_no_irqsave() {
245            break guard.as_ref().and_then(|cpu| {
246                if cpu.current_task.is_none() {
247                    unsafe { core::arch::asm!("mov al, 'N'; out 0xe9, al", out("al") _) };
248                    return None;
249                }
250                let arc = cpu.current_task.as_ref().unwrap();
251                let strong = Arc::strong_count(arc);
252                // Racy, pifometric diagnostic only: strong_count can move
253                // concurrently, so this is a heuristic for suspicious
254                // scheduler state, not a formal corruption proof.
255                if strong == 0 || strong > (isize::MAX as usize) / 2 {
256                    let ptr = Arc::as_ptr(arc) as *const u8;
257                    crate::serial_force_println!(
258                        "[trace][sched] {} suspicious current_task heuristic cpu={} strong={:#x} ptr={:p}",
259                        trace_label,
260                        cpu_index,
261                        strong,
262                        ptr,
263                    );
264                }
265                Some(arc.clone())
266            });
267        }
268
269        spins = spins.saturating_add(1);
270        if spins == 2_000_000 {
271            crate::serial_force_println!(
272                "[trace][sched] {} waiting current_task cpu={} owner_cpu={}",
273                trace_label,
274                cpu_index,
275                GLOBAL_SCHED_STATE.owner_cpu()
276            );
277            spins = 0;
278        }
279        core::hint::spin_loop();
280    };
281    restore_flags(saved_flags);
282    result
283}
284
285/// Resolve a POSIX pid to internal TaskId.
286pub fn get_task_id_by_pid(pid: Pid) -> Option<TaskId> {
287    SCHED_IDENTITY.read().pid_to_task.get(&pid).copied()
288}
289
290/// Resolve a POSIX pid to the corresponding task.
291pub fn get_task_by_pid(pid: Pid) -> Option<Arc<Task>> {
292    let tid = get_task_id_by_pid(pid)?;
293    get_task_by_id(tid)
294}
295
296/// Resolve a direct child of `parent` by POSIX pid.
297///
298/// Unlike the global pid index, this remains valid after the child has called
299/// exit and before it is reaped, because the task object stays in `all_tasks`
300/// until waitpid consumes the zombie.
301///
302/// Lock order: `GLOBAL_SCHED_STATE` before `SCHED_IDENTITY` (see module docs).
303pub fn get_child_task_id_by_pid(parent: TaskId, pid: Pid) -> Option<TaskId> {
304    let saved_flags = save_flags_and_cli();
305    let out = {
306        let scheduler = GLOBAL_SCHED_STATE.lock();
307        if let Some(ref sched) = *scheduler {
308            let children = {
309                let identity = SCHED_IDENTITY.read();
310                identity
311                    .children_of
312                    .get(&parent)
313                    .cloned()
314                    .unwrap_or_default()
315            };
316            if children.is_empty() {
317                None
318            } else {
319                children.iter().copied().find(|child_id| {
320                    sched
321                        .all_tasks
322                        .get(child_id)
323                        .map(|task| task.pid == pid)
324                        .unwrap_or(false)
325                })
326            }
327        } else {
328            None
329        }
330    };
331    restore_flags(saved_flags);
332    out
333}
334
335/// Resolve a POSIX tid to the corresponding internal task id.
336pub fn get_task_id_by_tid(tid: Tid) -> Option<TaskId> {
337    let identity = SCHED_IDENTITY.read();
338    identity
339        .tid_to_task
340        .get(&tid)
341        .copied()
342        .or_else(|| identity.pid_to_task.get(&(tid as Pid)).copied())
343}
344
345/// Resolve a direct child of `parent` by POSIX tid.
346///
347/// This remains valid for dead-but-not-yet-reaped threads because it scans the
348/// caller's child set and the retained task object instead of relying on the
349/// global tid index removed during exit.
350///
351/// Lock order: `GLOBAL_SCHED_STATE` before `SCHED_IDENTITY` (see module docs).
352pub fn get_child_task_id_by_tid(parent: TaskId, tid: Tid) -> Option<TaskId> {
353    let saved_flags = save_flags_and_cli();
354    let out = {
355        let scheduler = GLOBAL_SCHED_STATE.lock();
356        if let Some(ref sched) = *scheduler {
357            let children = {
358                let identity = SCHED_IDENTITY.read();
359                identity
360                    .children_of
361                    .get(&parent)
362                    .cloned()
363                    .unwrap_or_default()
364            };
365            if children.is_empty() {
366                None
367            } else {
368                children.iter().copied().find(|child_id| {
369                    sched
370                        .all_tasks
371                        .get(child_id)
372                        .map(|task| task.tid == tid)
373                        .unwrap_or(false)
374                })
375            }
376        } else {
377            None
378        }
379    };
380    restore_flags(saved_flags);
381    out
382}
383
384/// Resolve a PID to the current process group id.
385pub fn get_pgid_by_pid(pid: Pid) -> Option<Pid> {
386    SCHED_IDENTITY.read().pid_to_pgid.get(&pid).copied()
387}
388
389/// Resolve a PID to the current session id.
390pub fn get_sid_by_pid(pid: Pid) -> Option<Pid> {
391    SCHED_IDENTITY.read().pid_to_sid.get(&pid).copied()
392}
393
394/// Collect task IDs that currently belong to process group `pgid`.
395pub fn get_task_ids_in_pgid(pgid: Pid) -> alloc::vec::Vec<TaskId> {
396    use alloc::vec::Vec;
397    SCHED_IDENTITY
398        .read()
399        .pgid_members
400        .get(&pgid)
401        .cloned()
402        .unwrap_or_else(Vec::new)
403}
404
405/// Collect task IDs that currently belong to thread group `tgid`.
406pub fn get_task_ids_in_tgid(tgid: Pid) -> alloc::vec::Vec<TaskId> {
407    use alloc::vec::Vec;
408    let saved_flags = save_flags_and_cli();
409    let out = {
410        let scheduler = GLOBAL_SCHED_STATE.lock();
411        if let Some(ref sched) = *scheduler {
412            sched
413                .all_tasks
414                .values()
415                .filter(|task| task.tgid == tgid)
416                .map(|task| task.id)
417                .collect::<Vec<_>>()
418        } else {
419            Vec::new()
420        }
421    };
422    restore_flags(saved_flags);
423    out
424}
425
426/// Set process group id for `target_pid` (or current if `None`).
427pub fn set_process_group(
428    requester: TaskId,
429    target_pid: Option<Pid>,
430    new_pgid: Option<Pid>,
431) -> Result<Pid, crate::syscall::error::SyscallError> {
432    use crate::syscall::error::SyscallError;
433
434    let saved_flags = save_flags_and_cli();
435    let result = (|| -> Result<Pid, SyscallError> {
436        let scheduler = GLOBAL_SCHED_STATE.lock();
437        let sched = scheduler.as_ref().ok_or(SyscallError::Fault)?;
438
439        let requester_task = sched
440            .all_tasks
441            .get(&requester)
442            .cloned()
443            .ok_or(SyscallError::Fault)?;
444        let requester_sid = requester_task.sid.load(Ordering::Relaxed);
445
446        let target_id = match target_pid {
447            None => requester,
448            Some(pid) => SCHED_IDENTITY
449                .read()
450                .pid_to_task
451                .get(&pid)
452                .copied()
453                .ok_or(SyscallError::NotFound)?,
454        };
455
456        if target_id != requester {
457            let is_child = SCHED_IDENTITY
458                .read()
459                .children_of
460                .get(&requester)
461                .map(|children| children.iter().any(|child| *child == target_id))
462                .unwrap_or(false);
463            if !is_child {
464                return Err(SyscallError::PermissionDenied);
465            }
466        }
467
468        let target_task = sched
469            .all_tasks
470            .get(&target_id)
471            .cloned()
472            .ok_or(SyscallError::NotFound)?;
473        let target_pid_value = target_task.pid;
474        let target_sid = target_task.sid.load(Ordering::Relaxed);
475
476        if target_sid != requester_sid {
477            return Err(SyscallError::PermissionDenied);
478        }
479
480        if target_pid_value == target_sid {
481            return Err(SyscallError::PermissionDenied);
482        }
483
484        let desired_pgid = new_pgid.unwrap_or(target_pid_value);
485        if desired_pgid != target_pid_value {
486            let group_leader_tid = SCHED_IDENTITY
487                .read()
488                .pid_to_task
489                .get(&desired_pgid)
490                .copied()
491                .ok_or(SyscallError::NotFound)?;
492            let group_leader = sched
493                .all_tasks
494                .get(&group_leader_tid)
495                .ok_or(SyscallError::NotFound)?;
496            if group_leader.sid.load(Ordering::Relaxed) != target_sid {
497                return Err(SyscallError::PermissionDenied);
498            }
499        }
500
501        // Mutate identity maps under SCHED_IDENTITY lock while still holding GLOBAL_SCHED_STATE.
502        let old_pgid = target_task.pgid.load(Ordering::Relaxed);
503        let actual_new_pgid = new_pgid.unwrap_or(target_task.pid);
504        target_task.pgid.store(actual_new_pgid, Ordering::Relaxed);
505        {
506            let mut identity = SCHED_IDENTITY.write();
507            GlobalSchedState::member_remove(&mut identity.pgid_members, old_pgid, target_id);
508            GlobalSchedState::member_add(&mut identity.pgid_members, actual_new_pgid, target_id);
509            identity
510                .pid_to_pgid
511                .insert(target_task.pid, actual_new_pgid);
512        }
513        Ok(actual_new_pgid)
514    })();
515    restore_flags(saved_flags);
516    result
517}
518
519/// Create a new session for the calling task.
520pub fn create_session(requester: TaskId) -> Result<Pid, crate::syscall::error::SyscallError> {
521    use crate::syscall::error::SyscallError;
522
523    let saved_flags = save_flags_and_cli();
524    let result = (|| -> Result<Pid, SyscallError> {
525        let scheduler = GLOBAL_SCHED_STATE.lock();
526        let sched = scheduler.as_ref().ok_or(SyscallError::Fault)?;
527        let requester_task = sched
528            .all_tasks
529            .get(&requester)
530            .cloned()
531            .ok_or(SyscallError::Fault)?;
532
533        let pid = requester_task.pid;
534        if requester_task.pgid.load(Ordering::Relaxed) == pid {
535            return Err(SyscallError::PermissionDenied);
536        }
537
538        let old_sid = requester_task.sid.load(Ordering::Relaxed);
539        let old_pgid = requester_task.pgid.load(Ordering::Relaxed);
540        requester_task.sid.store(pid, Ordering::Relaxed);
541        requester_task.pgid.store(pid, Ordering::Relaxed);
542        {
543            let mut identity = SCHED_IDENTITY.write();
544            GlobalSchedState::member_remove(&mut identity.sid_members, old_sid, requester);
545            GlobalSchedState::member_remove(&mut identity.pgid_members, old_pgid, requester);
546            GlobalSchedState::member_add(&mut identity.sid_members, pid, requester);
547            GlobalSchedState::member_add(&mut identity.pgid_members, pid, requester);
548            identity.pid_to_sid.insert(pid, pid);
549            identity.pid_to_pgid.insert(pid, pid);
550        }
551        Ok(pid)
552    })();
553    restore_flags(saved_flags);
554    result
555}
556
557/// Get a task by its TaskId (if still registered).
558pub fn get_task_by_id(id: TaskId) -> Option<Arc<Task>> {
559    let saved_flags = save_flags_and_cli();
560    let task = {
561        let scheduler = GLOBAL_SCHED_STATE.lock();
562        if let Some(ref sched) = *scheduler {
563            sched.all_tasks.get(&id).cloned()
564        } else {
565            None
566        }
567    };
568    restore_flags(saved_flags);
569    task
570}
571
572/// Update a task scheduling policy and requeue if needed.
573pub fn set_task_sched_policy(id: TaskId, policy: crate::process::sched::SchedPolicy) -> bool {
574    let saved_flags = save_flags_and_cli();
575    let mut ipi_to_cpu: Option<usize> = None;
576    let updated = {
577        let mut scheduler = GLOBAL_SCHED_STATE.lock();
578        if let Some(ref mut sched) = *scheduler {
579            let cpu_index = sched.task_cpu.get(&id).copied().unwrap_or(0);
580            let task = match sched.all_tasks.get(&id).cloned() {
581                Some(t) => t,
582                None => return false,
583            };
584            task.set_sched_policy(policy);
585            let class = sched.class_table.class_for_task(&task);
586
587            if let Some(ref mut local_cpu) = *LOCAL_SCHEDULERS[cpu_index].lock() {
588                // If task is queued in ready classes, migrate it to the new class.
589                if local_cpu.class_rqs.remove(id) {
590                    local_cpu.class_rqs.enqueue(class, task.clone());
591                }
592                local_cpu.need_resched = true;
593            }
594            if cpu_index != current_cpu_index() {
595                ipi_to_cpu = Some(cpu_index);
596            }
597            sched_trace(format_args!(
598                "set_policy task={} cpu={} policy={:?}",
599                id.as_u64(),
600                cpu_index,
601                policy
602            ));
603            true
604        } else {
605            false
606        }
607    };
608    if let Some(ci) = ipi_to_cpu {
609        send_resched_ipi_to_cpu(ci);
610    }
611    restore_flags(saved_flags);
612    updated
613}
614
615/// Get parent task ID for a child task.
616pub fn get_parent_id(child: TaskId) -> Option<TaskId> {
617    SCHED_IDENTITY.read().parent_of.get(&child).copied()
618}
619
620/// Get parent process ID for a child task.
621pub fn get_parent_pid(child: TaskId) -> Option<Pid> {
622    let parent_tid = get_parent_id(child)?;
623    let parent = get_task_by_id(parent_tid)?;
624    Some(parent.pid)
625}
626
627/// Try to reap a zombie child.
628///
629/// `target=None` means "any child".
630pub fn try_wait_child(parent: TaskId, target: Option<TaskId>) -> WaitChildResult {
631    let saved_flags = save_flags_and_cli();
632    let result = {
633        let mut scheduler = GLOBAL_SCHED_STATE.lock();
634        if let Some(ref mut sched) = *scheduler {
635            sched.try_reap_child_locked(parent, target)
636        } else {
637            WaitChildResult::NoChildren
638        }
639    };
640    restore_flags(saved_flags);
641    result
642}
643
644/// Block the current task and yield to the scheduler.
645///
646/// The current task is moved from Running to Blocked state and placed
647/// in the `blocked_tasks` map. It will not be re-scheduled until
648/// `wake_task(id)` is called.
649///
650/// ## Lock design
651///
652/// This function acquires **only** the `BLOCKED_TASKS` lock + the current
653/// CPU's `LOCAL_SCHEDULERS[cpu]` lock. It does **not** touch
654/// `GLOBAL_SCHED_STATE`, avoiding contention with cold-path operations
655/// (fork, exit, kill).
656///
657/// ## Lost-wakeup prevention
658///
659/// Before actually blocking, this function checks the task's `wake_pending`
660/// flag. If a concurrent `wake_task()` fired between the moment the task
661/// added itself to a `WaitQueue` and this call, the flag will be set and
662/// the function returns immediately without blocking.
663///
664/// Must NOT be called with interrupts disabled or while holding the
665/// scheduler lock (this function acquires both).
666pub fn block_current_task() {
667    let saved_flags = save_flags_and_cli();
668    let cpu_index = current_cpu_index();
669
670    let switch_target = {
671        // Hold BLOCKED_TASKS and LOCAL together through the state transition
672        // and task selection so a concurrent wake cannot observe the task as
673        // blocked, requeue it, and race with us tearing down current_task.
674        let mut blocked = super::BLOCKED_TASKS.lock();
675        let mut local = LOCAL_SCHEDULERS[cpu_index].lock();
676        let out = if let Some(ref mut cpu) = *local {
677            if let Some(ref current) = cpu.current_task {
678                if current
679                    .wake_pending
680                    .swap(false, core::sync::atomic::Ordering::AcqRel)
681                {
682                    // Pending wakeup consumed - do not block.
683                    None
684                } else {
685                    current.set_state(TaskState::Blocked);
686                    // Record home CPU so wake_task can route without GLOBAL.
687                    current
688                        .home_cpu
689                        .store(cpu_index, core::sync::atomic::Ordering::Relaxed);
690                    blocked.insert(current.id, current.clone());
691                    super::core_impl::yield_cpu_local(cpu, cpu_index)
692                }
693            } else {
694                None
695            }
696        } else {
697            None
698        };
699        drop(local);
700        drop(blocked);
701        out
702    }; // Locks released
703
704    if let Some(ref target) = switch_target {
705        unsafe {
706            crate::process::task::do_switch_context(target);
707        }
708        finish_switch();
709    }
710
711    restore_flags(saved_flags);
712}
713
714/// Wake a blocked task by its ID.
715///
716/// Moves the task from `blocked_tasks` to the ready queue and sets its
717/// state to Ready. Returns `true` if the task was found and woken.
718///
719/// ## Lock design
720///
721/// The primary path (task found in `BLOCKED_TASKS`) acquires **only** the
722/// `BLOCKED_TASKS` lock + the target CPU's `LOCAL_SCHEDULERS[cpu]` lock.
723/// It does **not** touch `GLOBAL_SCHED_STATE`, avoiding contention with
724/// cold-path operations (fork, exit, kill).
725///
726/// ## Lost-wakeup prevention
727///
728/// If the task is not yet in `blocked_tasks` (it is still transitioning
729/// from Ready -> Blocked inside `block_current_task()`), this function sets
730/// the task's `wake_pending` flag so that `block_current_task()` will see
731/// the pending wakeup and return immediately without actually blocking.
732pub fn wake_task(id: TaskId) -> bool {
733    let saved_flags = save_flags_and_cli();
734
735    // --- Primary path: task is in BLOCKED_TASKS ---
736    // Acquire only BLOCKED_TASKS + LOCAL[target_cpu]. No GLOBAL_SCHED_STATE.
737    let mut ipi_cpu: Option<usize> = None;
738    let mut woken = false;
739
740    {
741        let mut blocked = super::BLOCKED_TASKS.lock();
742        if let Some(task) = blocked.remove(&id) {
743            task.set_state(TaskState::Ready);
744            let home = task.home_cpu.load(core::sync::atomic::Ordering::Relaxed);
745            let cpu_index = if home != usize::MAX { home } else { 0 };
746
747            // Compute the scheduling class for this task (done without GLOBAL).
748            let class = {
749                use crate::process::sched::SchedClassId;
750                match task.sched_policy() {
751                    crate::process::sched::SchedPolicy::RealTimeRR { .. }
752                    | crate::process::sched::SchedPolicy::RealTimeFifo { .. } => {
753                        SchedClassId::RealTime
754                    }
755                    crate::process::sched::SchedPolicy::Fair(_) => SchedClassId::Fair,
756                    crate::process::sched::SchedPolicy::Idle => SchedClassId::Idle,
757                }
758            };
759
760            if let Some(ref mut local_cpu) = *LOCAL_SCHEDULERS[cpu_index].lock() {
761                local_cpu.class_rqs.enqueue(class, task.clone());
762                local_cpu.need_resched = true;
763            }
764
765            ipi_cpu = if cpu_index != current_cpu_index() {
766                Some(cpu_index)
767            } else {
768                None
769            };
770            woken = true;
771        }
772    } // BLOCKED_TASKS lock released
773
774    if woken {
775        if let Some(ci) = ipi_cpu {
776            send_resched_ipi_to_cpu(ci);
777        }
778        restore_flags(saved_flags);
779        return true;
780    }
781
782    // === Fallback path: task not yet in BLOCKED_TASKS =================================
783    // Set wake_pending so block_current_task skips blocking.
784    {
785        let mut scheduler = GLOBAL_SCHED_STATE.lock();
786        if let Some(ref mut sched) = *scheduler {
787            let (fallback_woken, fallback_ipi) = sched.wake_task_locked(id);
788            woken = fallback_woken;
789            if ipi_cpu.is_none() {
790                ipi_cpu = fallback_ipi;
791            }
792        }
793    }
794
795    if let Some(ci) = ipi_cpu {
796        send_resched_ipi_to_cpu(ci);
797    }
798    restore_flags(saved_flags);
799    woken
800}
801
802/// Sets task wake deadline.
803pub fn set_task_wake_deadline(id: TaskId, deadline_ns: u64) -> bool {
804    let saved_flags = save_flags_and_cli();
805    let out = {
806        let mut scheduler = GLOBAL_SCHED_STATE.lock();
807        if let Some(ref mut sched) = *scheduler {
808            sched.set_task_wake_deadline_locked(id, deadline_ns)
809        } else {
810            false
811        }
812    };
813    restore_flags(saved_flags);
814    out
815}
816
817/// Performs the clear task wake deadline operation.
818pub fn clear_task_wake_deadline(id: TaskId) -> bool {
819    set_task_wake_deadline(id, 0)
820}
821
822/// Suspend a task by ID (best-effort).
823///
824/// Moves the task to the blocked map and marks it Blocked.
825/// - If the task is the *current* task on *this* CPU, a context switch is
826///   performed immediately.
827/// - If the task is the *current* task on *another* CPU, an IPI is sent to
828///   trigger preemption on that CPU. The task will not be re-queued at the
829///   next tick because its state is Blocked.
830pub fn suspend_task(id: TaskId) -> bool {
831    let saved_flags = save_flags_and_cli();
832
833    let mut switch_target: Option<SwitchTarget> = None;
834    let mut suspended = false;
835    let mut ipi_to_cpu: Option<usize> = None;
836
837    let my_cpu = current_cpu_index();
838    let n = active_cpu_count();
839
840    // Check if the task is the current task on any CPU.
841    for ci in 0..n {
842        let task_id_on_cpu = LOCAL_SCHEDULERS[ci]
843            .lock()
844            .as_ref()
845            .and_then(|cpu| cpu.current_task.as_ref().map(|t| (t.id, t.clone())));
846        if let Some((tid, current)) = task_id_on_cpu {
847            if tid == id {
848                current.set_state(TaskState::Blocked);
849                current
850                    .home_cpu
851                    .store(ci, core::sync::atomic::Ordering::Relaxed);
852                super::BLOCKED_TASKS
853                    .lock()
854                    .insert(current.id, current.clone());
855                suspended = true;
856                if ci == my_cpu {
857                    // Re-acquire LOCAL to yield.  The gap between the
858                    // probe above and this lock is safe because IRQs
859                    // are disabled (save_flags_and_cli), so no timer
860                    // tick can preempt us or mutate current_task.
861                    let mut local = LOCAL_SCHEDULERS[ci].lock();
862                    if let Some(ref mut cpu) = *local {
863                        switch_target = super::core_impl::yield_cpu_local(cpu, ci);
864                    }
865                } else {
866                    // Cross-CPU: IPI will make the remote CPU preempt.
867                    ipi_to_cpu = Some(ci);
868                }
869                break;
870            }
871        }
872    }
873
874    // Remove from ready queues (task was not running anywhere).
875    if !suspended {
876        for ci in 0..n {
877            let removed = {
878                let mut local = LOCAL_SCHEDULERS[ci].lock();
879                if let Some(ref mut cpu) = *local {
880                    cpu.class_rqs.remove(id)
881                } else {
882                    false
883                }
884            };
885            if removed {
886                if let Some(task) = get_task_by_id(id) {
887                    task.set_state(TaskState::Blocked);
888                    task.home_cpu
889                        .store(ci, core::sync::atomic::Ordering::Relaxed);
890                    super::BLOCKED_TASKS.lock().insert(task.id, task.clone());
891                }
892                suspended = true;
893                break;
894            }
895        }
896    }
897
898    // Already blocked.
899    if !suspended && super::BLOCKED_TASKS.lock().contains_key(&id) {
900        suspended = true;
901    }
902
903    if let Some(ref target) = switch_target {
904        unsafe {
905            crate::process::task::do_switch_context(target);
906        }
907        finish_switch();
908    }
909
910    if let Some(ci) = ipi_to_cpu {
911        send_resched_ipi_to_cpu(ci);
912    }
913
914    restore_flags(saved_flags);
915    suspended
916}
917
918/// Resume a previously suspended task by ID.
919///
920/// Moves the task from blocked to ready queue and marks it Ready.
921pub fn resume_task(id: TaskId) -> bool {
922    let saved_flags = save_flags_and_cli();
923    let mut ipi_to_cpu: Option<usize> = None;
924
925    let mut task_to_enqueue: Option<Arc<Task>> = None;
926    {
927        let mut blocked = super::BLOCKED_TASKS.lock();
928        if let Some(task) = blocked.remove(&id) {
929            task.set_state(TaskState::Ready);
930            let home = task.home_cpu.load(core::sync::atomic::Ordering::Relaxed);
931            let cpu_index = if home != usize::MAX { home } else { 0 };
932
933            let class = {
934                use crate::process::sched::SchedClassId;
935                match task.sched_policy() {
936                    crate::process::sched::SchedPolicy::RealTimeRR { .. }
937                    | crate::process::sched::SchedPolicy::RealTimeFifo { .. } => {
938                        SchedClassId::RealTime
939                    }
940                    crate::process::sched::SchedPolicy::Fair(_) => SchedClassId::Fair,
941                    crate::process::sched::SchedPolicy::Idle => SchedClassId::Idle,
942                }
943            };
944
945            if let Some(ref mut local_cpu) = *LOCAL_SCHEDULERS[cpu_index].lock() {
946                local_cpu.class_rqs.enqueue(class, task.clone());
947                local_cpu.need_resched = true;
948            }
949
950            if cpu_index != current_cpu_index() {
951                ipi_to_cpu = Some(cpu_index);
952            }
953            drop(blocked);
954            task_to_enqueue = Some(task);
955        }
956    }
957
958    if let Some(ci) = ipi_to_cpu {
959        send_resched_ipi_to_cpu(ci);
960    }
961    restore_flags(saved_flags);
962    task_to_enqueue.is_some()
963}
964
965/// Kill a task by ID (best-effort).
966///
967/// - Ready / blocked tasks are removed and marked Dead immediately.
968/// - If the task is the *current* task on *this* CPU, a context switch is
969///   performed immediately.
970/// - If the task is the *current* task on *another* CPU, an IPI triggers
971///   preemption on that CPU; the task will not be re-queued because its
972///   state is Dead.
973///
974/// Returns `true` if the task was found and killed.
975pub fn kill_task(id: TaskId) -> bool {
976    let pid = crate::process::get_task_by_id(id)
977        .map(|t| t.pid)
978        .unwrap_or(0);
979    crate::audit::log(
980        crate::audit::AuditCategory::Process,
981        pid,
982        crate::silo::task_silo_id(id).unwrap_or(0),
983        alloc::format!("kill_task tid={}", id.as_u64()),
984    );
985    let saved_flags = save_flags_and_cli();
986
987    let mut switch_target: Option<SwitchTarget> = None;
988    let mut killed = false;
989    let mut ipi_to_cpu: Option<usize> = None;
990    let mut parent_to_signal: Option<TaskId> = None;
991
992    {
993        let mut scheduler = GLOBAL_SCHED_STATE.lock();
994        if let Some(ref mut sched) = *scheduler {
995            // Keep parent/waitpid semantics even for forced termination paths.
996            // A killed child must still become a zombie until reaped by waitpid().
997            const FORCED_KILL_EXIT_CODE: i32 = 1;
998            let my_cpu = current_cpu_index();
999
1000            // Check if the task is the current task on any CPU.
1001            let n = active_cpu_count();
1002            let mut running_hit: Option<(usize, Arc<Task>)> = None;
1003            for ci in 0..n {
1004                let hit = LOCAL_SCHEDULERS[ci].lock().as_ref().and_then(|cpu| {
1005                    cpu.current_task
1006                        .as_ref()
1007                        .map(|t| (t.id, t.get_state(), t.clone()))
1008                });
1009                if let Some((tid, state, current)) = hit {
1010                    if tid == id {
1011                        // Check if already marked Dead by a previous kill attempt
1012                        if state != TaskState::Dead {
1013                            running_hit = Some((ci, current));
1014                        }
1015                        break;
1016                    }
1017                }
1018            }
1019            if let Some((ci, current)) = running_hit {
1020                let task_pid = current.pid;
1021                let _ = sched.clear_task_wake_deadline_locked(id);
1022                current.set_state(TaskState::Dead);
1023                // Do NOT call cleanup_task_resources or all_tasks.remove() here!
1024                // The task is still in current_task[ci], and an interrupt could
1025                // access it. Instead, mark it Dead and let pick_next_task handle
1026                // the cleanup when it moves the task to task_to_drop.
1027                sched.task_cpu.remove(&id);
1028                {
1029                    let mut identity = SCHED_IDENTITY.write();
1030                    GlobalSchedState::unregister_identity_locked(
1031                        &mut identity,
1032                        id,
1033                        task_pid,
1034                        current.tid,
1035                    );
1036                }
1037                let (parent, ipi_death) =
1038                    finalize_forced_death(sched, id, FORCED_KILL_EXIT_CODE, task_pid);
1039                parent_to_signal = parent;
1040                killed = true;
1041                if ci == my_cpu {
1042                    let mut local = LOCAL_SCHEDULERS[ci].lock();
1043                    if let Some(ref mut cpu) = *local {
1044                        switch_target = super::core_impl::yield_cpu_local(cpu, ci);
1045                    }
1046                } else {
1047                    ipi_to_cpu = Some(ci);
1048                }
1049                if ipi_to_cpu.is_none() {
1050                    ipi_to_cpu = ipi_death;
1051                }
1052            }
1053
1054            // Remove from ready queues.
1055            if !killed {
1056                let mut removed_from_ready = false;
1057                for ci in 0..n {
1058                    let removed = {
1059                        let mut local = LOCAL_SCHEDULERS[ci].lock();
1060                        if let Some(ref mut cpu) = *local {
1061                            cpu.class_rqs.remove(id)
1062                        } else {
1063                            false
1064                        }
1065                    };
1066                    if removed {
1067                        removed_from_ready = true;
1068                        break;
1069                    }
1070                }
1071                if removed_from_ready {
1072                    let _ = sched.clear_task_wake_deadline_locked(id);
1073                    if let Some(task) = sched.remove_all_task_locked(id) {
1074                        let task_pid = task.pid;
1075                        task.set_state(TaskState::Dead);
1076                        cleanup_task_resources(&task);
1077                        sched.task_cpu.remove(&id);
1078                        {
1079                            let mut identity = SCHED_IDENTITY.write();
1080                            GlobalSchedState::unregister_identity_locked(
1081                                &mut identity,
1082                                id,
1083                                task_pid,
1084                                task.tid,
1085                            );
1086                        }
1087                        let (parent, ipi_death) =
1088                            finalize_forced_death(sched, id, FORCED_KILL_EXIT_CODE, task_pid);
1089                        parent_to_signal = parent;
1090                        if ipi_to_cpu.is_none() {
1091                            ipi_to_cpu = ipi_death;
1092                        }
1093                    }
1094                    killed = true;
1095                }
1096            }
1097
1098            // Remove from blocked map.
1099            if !killed {
1100                if let Some(task) = super::BLOCKED_TASKS.lock().remove(&id) {
1101                    let task_pid = task.pid;
1102                    let _ = sched.clear_task_wake_deadline_locked(id);
1103                    task.set_state(TaskState::Dead);
1104                    cleanup_task_resources(&task);
1105                    let _ = sched.remove_all_task_locked(id);
1106                    sched.task_cpu.remove(&id);
1107                    {
1108                        let mut identity = SCHED_IDENTITY.write();
1109                        GlobalSchedState::unregister_identity_locked(
1110                            &mut identity,
1111                            id,
1112                            task_pid,
1113                            task.tid,
1114                        );
1115                    }
1116                    let (parent, ipi_death) =
1117                        finalize_forced_death(sched, id, FORCED_KILL_EXIT_CODE, task_pid);
1118                    parent_to_signal = parent;
1119                    if ipi_to_cpu.is_none() {
1120                        ipi_to_cpu = ipi_death;
1121                    }
1122                    killed = true;
1123                }
1124            }
1125        }
1126    } // scheduler lock released before IPI and context switch
1127
1128    if let Some(ref target) = switch_target {
1129        unsafe {
1130            crate::process::task::do_switch_context(target);
1131        }
1132        finish_switch();
1133    }
1134
1135    if let Some(ci) = ipi_to_cpu {
1136        send_resched_ipi_to_cpu(ci);
1137    }
1138
1139    if let Some(parent_id) = parent_to_signal {
1140        // Must happen outside scheduler lock to avoid lock recursion.
1141        let _ =
1142            crate::process::signal::send_signal(parent_id, crate::process::signal::Signal::SIGCHLD);
1143    }
1144
1145    restore_flags(saved_flags);
1146    killed
1147}
1148
1149/// Performs the finalize forced death operation.
1150fn finalize_forced_death(
1151    sched: &mut GlobalSchedState,
1152    task_id: TaskId,
1153    exit_code: i32,
1154    task_pid: Pid,
1155) -> (Option<TaskId>, Option<usize>) {
1156    let ipi_reparent = {
1157        let mut identity = SCHED_IDENTITY.write();
1158        reparent_children(sched, &mut identity, task_id)
1159    };
1160    let parent = {
1161        let mut identity = SCHED_IDENTITY.write();
1162        identity.parent_of.remove(&task_id)
1163    };
1164    if let Some(parent_id) = parent {
1165        sched.zombies.insert(task_id, (exit_code, task_pid));
1166        let (_, ipi_wake) = sched.wake_task_locked(parent_id);
1167        (Some(parent_id), ipi_reparent.or(ipi_wake))
1168    } else {
1169        (None, ipi_reparent)
1170    }
1171}
1172
1173/// Performs the reparent children operation.
1174/// Reparent children of a dying task to PID 1 (init), or drop parent links
1175/// if PID 1 is not available.
1176///
1177/// # Orphan policy
1178///
1179/// - **PID 1 exists:** Orphans are reparented to init, which is responsible
1180///   for reaping them. This matches standard Unix semantics.
1181/// - **PID 1 does not exist:** Parent links are dropped entirely. Orphans
1182///   become parentless : they continue running but cannot be `wait()`-ed on.
1183///   This avoids the nondeterministic fallback of adopting an arbitrary task
1184///   (which might be short-lived or unsuitable for reaping).
1185/// - **PID 1 is the dying task:** Same as above : parent links are dropped.
1186fn reparent_children(
1187    sched: &mut GlobalSchedState,
1188    identity: &mut SchedIdentity,
1189    dying: TaskId,
1190) -> Option<usize> {
1191    let children = match identity.children_of.remove(&dying) {
1192        Some(c) => c,
1193        None => return None,
1194    };
1195
1196    // Preferred reaper: PID 1 (standard init process).
1197    let init_id = identity.pid_to_task.get(&1).copied();
1198
1199    let Some(init_id) = init_id else {
1200        // No PID 1: drop parent links. Orphans become parentless.
1201        for child in &children {
1202            identity.parent_of.remove(child);
1203        }
1204        return None;
1205    };
1206    if init_id == dying {
1207        // PID 1 is dying : cannot reparent to self. Drop parent links.
1208        for child in &children {
1209            identity.parent_of.remove(child);
1210        }
1211        return None;
1212    }
1213
1214    let mut has_zombie = false;
1215    let init_children = identity.children_of.entry(init_id).or_default();
1216    for child in children {
1217        if !has_zombie && sched.zombies.contains_key(&child) {
1218            has_zombie = true;
1219        }
1220        identity.parent_of.insert(child, init_id);
1221        init_children.push(child);
1222    }
1223    if has_zombie {
1224        let (_, ipi) = sched.wake_task_locked(init_id);
1225        ipi
1226    } else {
1227        None
1228    }
1229}
1230
1231/// Performs the cleanup task resources operation.
1232///
1233/// Called when a task exits or is killed to release ports, capabilities,
1234/// and user address space mappings.
1235///
1236/// # Safety
1237/// Must be called with the scheduler lock held and the task no longer
1238/// accessible from any global map (all_tasks, current_task, etc.).
1239fn queue_silo_cleanup(task_id: TaskId) {
1240    let mut guard = PENDING_SILO_CLEANUPS.lock();
1241    guard
1242        .push_back(task_id)
1243        .unwrap_or_else(|_| panic!("pending silo cleanup queue overflow"));
1244}
1245
1246pub fn flush_deferred_silo_cleanups() {
1247    let mut guard = match PENDING_SILO_CLEANUPS.try_lock() {
1248        Some(g) => g,
1249        None => return, // Lock held by preempted task or other CPU, skip safely
1250    };
1251    if guard.is_empty() {
1252        return;
1253    }
1254    let mut drained = FixedQueue::<TaskId, PENDING_SILO_CLEANUPS_CAPACITY>::new();
1255    core::mem::swap(&mut *guard, &mut drained);
1256    drop(guard);
1257    while let Some(task_id) = drained.pop_front() {
1258        crate::silo::on_task_terminated(task_id);
1259    }
1260}
1261
1262pub(crate) fn cleanup_task_resources(task: &Arc<Task>) {
1263    crate::ipc::port::cleanup_ports_for_task(task.id);
1264    queue_silo_cleanup(task.id);
1265
1266    // SAFETY: strong_count is racy (a concurrent get_task_by_id may temporarily
1267    // hold an extra Arc ref). Worst case: cleanup is deferred until the last ref
1268    // drops elsewhere - no resource leak, just delayed release.
1269    let is_last_process_ref = Arc::strong_count(&task.process) == 1;
1270    if !is_last_process_ref {
1271        return;
1272    }
1273
1274    unsafe {
1275        (&mut *task.process.fd_table.get()).close_all();
1276        let capabilities = (&mut *task.process.capabilities.get()).take_all();
1277        for capability in &capabilities {
1278            crate::capability::release_capability(capability, Some(task.id));
1279        }
1280    }
1281
1282    let as_ref = task.process.address_space_arc();
1283    if !as_ref.is_kernel() && Arc::strong_count(&as_ref) == 1 {
1284        as_ref.unmap_all_user_regions();
1285    }
1286}