Skip to content
197 changes: 192 additions & 5 deletions src/api/process.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use alloc::{
sync::{Arc, Weak},
vec::Vec,
};
use core::{
array,
ops::{Index, IndexMut},
sync::atomic::{AtomicBool, Ordering},
sync::atomic::{AtomicBool, AtomicU8, Ordering},
};

use alloc::{
sync::{Arc, Weak},
vec::Vec,
};
use bitflags::bitflags;
use kspin::SpinNoIrq;
use strum::IntoEnumIterator;

use crate::{
DefaultSignalAction, PendingSignals, SignalAction, SignalActionFlags, SignalDisposition,
Expand All @@ -27,6 +29,7 @@ impl Default for SignalActions {

impl Index<Signo> for SignalActions {
type Output = SignalAction;

fn index(&self, signo: Signo) -> &SignalAction {
&self.0[signo as usize - 1]
}
Expand All @@ -38,6 +41,14 @@ impl IndexMut<Signo> for SignalActions {
}
}

bitflags! {
/// A bitflag representing signal-stop and signal-continue event
pub struct SignalEventFlags: u8 {
const PENDING_STOP_EVENT = 1 << 0;
const PENDING_CONT_EVENT = 1 << 1;
}
}

/// Process-level signal manager.
pub struct ProcessSignalManager {
/// The process-level shared pending signals
Expand All @@ -53,6 +64,12 @@ pub struct ProcessSignalManager {
pub(crate) children: SpinNoIrq<Vec<(u32, Weak<ThreadSignalManager>)>>,

pub(crate) possibly_has_signal: AtomicBool,

/// Signal event flag, keep track of un-consumed stop/continue event by
/// `wait`
signal_events: AtomicU8,
/// The signal stops the process most recently
last_stop_signal: SpinNoIrq<Option<Signo>>,
}

impl ProcessSignalManager {
Expand All @@ -64,6 +81,8 @@ impl ProcessSignalManager {
default_restorer,
children: SpinNoIrq::new(Vec::new()),
possibly_has_signal: AtomicBool::new(false),
signal_events: AtomicU8::new(0),
last_stop_signal: SpinNoIrq::new(None),
}
}

Expand All @@ -77,7 +96,18 @@ impl ProcessSignalManager {
}

/// Checks if a signal is ignored by the process.
/// Only discard signals that have no side effects AND are ignored.
/// SIGCONT and SIGKILL shall always be queued for their side effects.
pub fn signal_ignored(&self, signo: Signo) -> bool {
// A speical case for SIGCONT (and may also inlcude SIGKILL even it cannot be
// ignored).
// Per POSIX.1-2024, when a process is stopped, SIGCONT should
// be able to continue this process even if the process may ignore the
// SIGCONT signal. We use the `has_side_effect` function here to deliver
// the SIGCONT under all circumstance in an early return.
if signo.has_side_effect() {
return false;
}
match &self.actions.lock()[signo].disposition {
SignalDisposition::Ignore => true,
SignalDisposition::Default => {
Expand All @@ -102,6 +132,8 @@ impl ProcessSignalManager {
#[must_use]
pub fn send_signal(&self, sig: SignalInfo) -> Option<u32> {
let signo = sig.signo();
// Only discard signals that have no side effects AND are ignored.
// SIGCONT and SIGKILL must always be queued for their side effects.
if self.signal_ignored(signo) {
return None;
}
Expand All @@ -127,4 +159,159 @@ impl ProcessSignalManager {
pub fn pending(&self) -> SignalSet {
self.pending.lock().set
}

/// Removes a signal from the process pending queue.
pub fn remove_signal(&self, signo: Signo) {
self.pending.lock().remove_signal(signo);
}

/// Determine whether there is a specific signal pending for the process
pub fn has_signal(&self, signo: Signo) -> bool {
self.pending.lock().has_signal(signo)
}

/// Clear all stopping signals in the process pending queue if any,
/// including `SIGSTOP`, `SIGTSTP`, `SIGTTIN`, and `SIGTTOU`.
pub fn flush_stop_signals(&self) {
let stop_signals: Vec<Signo> = Signo::iter()
.filter(|s| matches!(s.default_action(), DefaultSignalAction::Stop))
.collect();

let mut pending = self.pending.lock();
for sig in stop_signals {
pending.remove_signal(sig);
}
}

/// Records a stop signal effect (atomically).
///
/// This method is called by `do_stop()` when a stop signal
/// (SIGSTOP, SIGTSTP, SIGTTIN, or SIGTTOU) takes effect on the process:
/// 1. Records which signal caused the stop, stored in `last_stop_signal`
/// 2. Sets the `PENDING_STOP_EVENT` flag, for wait to detect it
///
/// # Memory Ordering
///
/// Uses `Release` ordering to synchronize with `Acquire` loads in
/// `peek_pending_stop_event()`. This ensures that when wait() observes
/// the stop event, it also observes the signal value.
///
/// # Arguments
///
/// * `signal` - The stop signal that caused the process to stop
pub fn set_stop_signal(&self, signal: Signo) {
*self.last_stop_signal.lock() = Some(signal);

// Set STOP event flag without clearing CONT event
self.signal_events.fetch_or(
SignalEventFlags::PENDING_STOP_EVENT.bits(),
Ordering::Release,
);
}

/// Records a continue signal effect (atomically).
///
/// This method should be called by `do_continue()` when a SIGCONT signal
/// takes effect on the process:
/// 1. Clears the recorded stop signal.
/// 2. Sets the `PENDING_CONT_EVENT` flag for `wait` to detect it.
///
/// # Memory Ordering
///
/// Uses `Release` ordering to synchronize with `Acquire` loads in
/// `peek_pending_cont_event()`.
pub fn set_cont_signal(&self) {
*self.last_stop_signal.lock() = None;

// Set CONT event flag without clearing STOP event
self.signal_events.fetch_or(
SignalEventFlags::PENDING_CONT_EVENT.bits(),
Ordering::Release,
);
}

/// Peeks at a pending stop signal event without consuming it.
///
/// This method checks if there is an unreported stop event and returns
/// the signal that caused it. The event remains pending until explicitly
/// consumed by `consume_stop_event`.
///
/// # Returns
///
/// * `Some(signal)` - There is a pending stop event caused by `signal`
/// * `None` - No pending stop event (either already consumed or never
/// occurred)
///
/// # Memory Ordering
///
/// Uses `Acquire` ordering to synchronize with `Release` stores in
/// `set_stop_signal`. This ensures that if we see the `PENDING_STOP_EVENT`
/// flag, we also see the signal value.
pub fn peek_pending_stop_event(&self) -> Option<Signo> {
let flags = self.signal_events.load(Ordering::Acquire);

if (flags & SignalEventFlags::PENDING_STOP_EVENT.bits()) != 0 {
*self.last_stop_signal.lock()
} else {
None
}
}

/// Consumes (clears) the pending stop signal event.
///
/// This method could be called by `wait()` after successfully reporting
/// the stop event to the waiter, unless `WNOWAIT` is set, which reflects
/// the "one-time consumption" semantics of POSIX wait:
/// once consumed, the same stop event will not be reported again.
///
/// # Memory Ordering
///
/// Uses `Release` ordering to ensure that the consumption is visible
/// to other threads that might be calling `peek_pending_stop_event`.
pub fn consume_stop_event(&self) {
*self.last_stop_signal.lock() = None;

self.signal_events.fetch_and(
!SignalEventFlags::PENDING_STOP_EVENT.bits(),
Ordering::Release,
);
}

/// Peeks at a pending continue signal event without consuming it.
///
/// This method checks if there is an unreported SIGCONT event. The event
/// remains pending until explicitly consumed by `consume_cont_event`.
///
/// # Returns
///
/// * `true` - There is a pending continue event
/// * `false` - No pending continue event
///
/// # Memory Ordering
///
/// Uses `Acquire` ordering to synchronize with `Release` stores in
/// `set_cont_signal`.
pub fn peek_pending_cont_event(&self) -> bool {
let flags = self.signal_events.load(Ordering::Acquire);
(flags & SignalEventFlags::PENDING_CONT_EVENT.bits()) != 0
}

/// Consumes (clears) the pending continue signal event.
///
/// This method should be called by `wait()` after successfully reporting
/// the continue event to the waiter, unless `WNOWAIT` is set.
///
/// This implements the "one-time consumption" semantics of POSIX wait:
/// once consumed, the same continue event will not be reported again.
///
/// # Memory Ordering
///
/// Uses `Release` ordering to ensure that the consumption is visible
/// to other threads that might be calling `peek_pending_cont_event`.
pub fn consume_cont_event(&self) {
self.signal_events.fetch_and(
!SignalEventFlags::PENDING_CONT_EVENT.bits(),
Ordering::Release,
);
}
}
46 changes: 40 additions & 6 deletions src/api/thread.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use alloc::{sync::Arc, vec::Vec};
use core::{
alloc::Layout,
mem::offset_of,
sync::atomic::{AtomicBool, Ordering},
};

use alloc::sync::Arc;
use axcpu::uspace::UserContext;
use kspin::SpinNoIrq;
use starry_vm::VmMutPtr;
use strum::IntoEnumIterator;

use super::ProcessSignalManager;
use crate::{
DefaultSignalAction, PendingSignals, SignalAction, SignalActionFlags, SignalDisposition,
SignalInfo, SignalOSAction, SignalSet, SignalStack, Signo, arch::UContext,
};

use super::ProcessSignalManager;

struct SignalFrame {
ucontext: UContext,
siginfo: SignalInfo,
Expand Down Expand Up @@ -146,7 +146,7 @@ impl ThreadSignalManager {
&self,
uctx: &mut UserContext,
restore_blocked: Option<SignalSet>,
) -> Option<(SignalInfo, SignalOSAction)> {
) -> Option<(SignalInfo, Option<SignalOSAction>)> {
let blocked = self.blocked.lock();
let mask = !*blocked;
let restore_blocked = restore_blocked.unwrap_or_else(|| *blocked);
Expand All @@ -163,7 +163,18 @@ impl ThreadSignalManager {
let action = self.proc.actions.lock()[sig.signo()].clone();

if let Some(os_action) = self.handle_signal(uctx, restore_blocked, &sig, &action) {
break Some((sig, os_action));
break Some((sig, Some(os_action)));
}

// Special case:
// `SIGCONT` with ignored disposition.
// Even though `handle_signal` returned None (signal ignored),
// we still need to report it for side effects,
// i.e., continue a stopped process even if it ignores `SIGCONT`.
if sig.signo() == Signo::SIGCONT
&& matches!(action.disposition, SignalDisposition::Ignore)
{
break Some((sig, None));
}
}
}
Expand All @@ -175,7 +186,7 @@ impl ThreadSignalManager {
&self,
uctx: &mut UserContext,
restore_blocked: Option<SignalSet>,
) -> Option<(SignalInfo, SignalOSAction)> {
) -> Option<(SignalInfo, Option<SignalOSAction>)> {
// Fast path
if !self.possibly_has_signal.load(Ordering::Acquire)
&& !self.proc.possibly_has_signal.load(Ordering::Acquire)
Expand Down Expand Up @@ -252,4 +263,27 @@ impl ThreadSignalManager {
pub fn pending(&self) -> SignalSet {
self.pending.lock().set | self.proc.pending()
}

/// Removes a signal from the thread pending queue.
pub fn remove_signal(&self, signo: Signo) {
self.pending.lock().remove_signal(signo);
}

/// Determine whether there is a specific signal pending for the thread
pub fn has_signal(&self, signo: Signo) -> bool {
self.pending.lock().has_signal(signo)
}

/// Clear all stopping signals in the thread pending queue if any,
/// including `SIGSTOP`, `SIGTSTP`, `SIGTTIN`, and `SIGTTOU`.
pub fn flush_stop_signals(&self) {
let stop_signals: Vec<Signo> = Signo::iter()
.filter(|s| matches!(s.default_action(), DefaultSignalAction::Stop))
.collect();

let mut pending = self.pending.lock();
for stop_signal in stop_signals {
pending.remove_signal(stop_signal);
}
}
}
21 changes: 19 additions & 2 deletions src/pending.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use core::array;

use alloc::{boxed::Box, collections::vec_deque::VecDeque};
use core::array;

use crate::{SignalInfo, SignalSet};

Expand Down Expand Up @@ -66,4 +65,22 @@ impl PendingSignals {
}
})
}

/// Removes a signal from the thread's pending queue.
pub fn remove_signal(&mut self, signo: crate::Signo) {
if self.set.has(signo) {
self.set.remove(signo);
// Also remove the detail info about this signal to be removed
if signo.is_realtime() {
self.info_rt[signo as usize - 32].clear();
} else {
self.info_std[signo as usize] = None;
}
}
}

/// Check whether there is a specific signal pending
pub fn has_signal(&self, signo: crate::Signo) -> bool {
self.set.has(signo)
}
}
9 changes: 9 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ impl Signo {
_ => DefaultSignalAction::Ignore,
}
}

/// Checks if a signal has kernel-level side effects that must occur
/// even when the signal is ignored.
///
/// SIGCONT and SIGKILL always have side effects (continue/kill process),
/// even when their disposition is SIG_IGN.
pub fn has_side_effect(&self) -> bool {
matches!(self, Signo::SIGCONT | Signo::SIGKILL)
}
}

/// Signal set. Compatible with `struct sigset_t` in libc.
Expand Down