Thread Signal Manager

Relevant source files

The Thread Signal Manager is a component of the axsignal crate that provides thread-level signal handling capabilities in a Unix-like signal handling system. It manages signal delivery, blocking, and handling for individual threads, working in coordination with the Process Signal Manager. For process-level signal handling, see Process Signal Manager.

Overview

The Thread Signal Manager implements thread-specific signal handling functionality including:

  • Managing thread-specific pending signals
  • Controlling which signals are blocked for a thread
  • Setting up and managing signal handler stacks
  • Handling signal delivery to user-defined handlers
  • Coordination with the process-level signal manager
classDiagram
class ThreadSignalManager {
    -Arc~ProcessSignalManager~ proc
    -Mutex~PendingSignals~ pending
    -Mutex~SignalSet~ blocked
    -Mutex~SignalStack~ stack
    +new(proc)
    +dequeue_signal(mask)
    +handle_signal(tf, restore_blocked, sig, action)
    +check_signals(tf, restore_blocked)
    +restore(tf)
    +send_signal(sig)
    +blocked()
    +with_blocked_mut(f)
    +stack()
    +with_stack_mut(f)
    +pending()
    +wait_timeout(set, timeout)
}

class ProcessSignalManager {
    +Mutex~PendingSignals~ pending
    +Arc~Mutex~SignalActions~~ actions
    +WaitQueue wq
    +usize default_restorer
    
}

class PendingSignals {
    +SignalSet set
    +Option~SignalInfo~[32] info_std
    +VecDeque~SignalInfo~[33] info_rt
    
}

class SignalSet {
    +u64 bits
    
}

class SignalStack {
    +usize sp
    +usize size
    +u32 flags
    
}

ThreadSignalManager  -->  ProcessSignalManager : references
ThreadSignalManager  *--  PendingSignals : contains
ThreadSignalManager  *--  SignalSet : contains
ThreadSignalManager  *--  SignalStack : contains

Sources: src/api/thread.rs(L21 - L31) 

Core Components

SignalFrame

Before a signal handler is invoked, the current execution context is saved in a SignalFrame structure on the stack:

classDiagram
class SignalFrame {
    +UContext ucontext
    +SignalInfo siginfo
    +TrapFrame tf
    
}

class UContext {
    +MContext mcontext
    +SignalSet sigmask
    
}

class SignalInfo {
    +Signo signo
    +i32 errno
    +i32 code
    
}

class TrapFrame {
    +architecture-specific registers
    
}

SignalFrame  *--  UContext
SignalFrame  *--  SignalInfo
SignalFrame  *--  TrapFrame

Sources: src/api/thread.rs(L14 - L18) 

Signal Handling Flow

The ThreadSignalManager follows a specific flow when handling signals:

flowchart TD
ThreadReceive["Thread receives signal"]
CheckBlocked["Is signalblocked?"]
AddPending["Add to thread'spending signals"]
CheckDisposition["Check signaldisposition"]
Later["Later when unblocked"]
DefaultAction["Execute default action(Terminate/CoreDump/Stop/Ignore/Continue)"]
DoNothing["Do nothing"]
SetupHandler["Setup handler execution"]
SaveContext["Save current contextin SignalFrame"]
ModifyTF["Modify trap framefor handler execution"]
UpdateBlocked["Update blocked signals"]
ExecuteHandler["Execute signal handler"]
RestoreContext["Restore original contextwhen handler returns"]

AddPending --> Later
CheckBlocked --> AddPending
CheckBlocked --> CheckDisposition
CheckDisposition --> DefaultAction
CheckDisposition --> DoNothing
CheckDisposition --> SetupHandler
ExecuteHandler --> RestoreContext
Later --> CheckDisposition
ModifyTF --> UpdateBlocked
SaveContext --> ModifyTF
SetupHandler --> SaveContext
ThreadReceive --> CheckBlocked
UpdateBlocked --> ExecuteHandler

Sources: src/api/thread.rs(L50 - L117)  src/api/thread.rs(L119 - L143) 

Key Methods

Constructor

The ThreadSignalManager is initialized with a reference to a ProcessSignalManager:

#![allow(unused)]
fn main() {
pub fn new(proc: Arc<ProcessSignalManager<M, WQ>>) -> Self {
    Self {
        proc,
        pending: Mutex::new(PendingSignals::new()),
        blocked: Mutex::new(SignalSet::default()),
        stack: Mutex::new(SignalStack::default()),
    }
}
}

Sources: src/api/thread.rs(L34 - L41) 

Signal Dequeuing

The dequeue_signal method attempts to retrieve a signal from the thread's pending signals. If none are found, it falls back to the process-level signal manager:

#![allow(unused)]
fn main() {
fn dequeue_signal(&self, mask: &SignalSet) -> Option<SignalInfo> {
    self.pending
        .lock()
        .dequeue_signal(mask)
        .or_else(|| self.proc.dequeue_signal(mask))
}
}

Sources: src/api/thread.rs(L43 - L48) 

Signal Handling

The handle_signal method is responsible for processing a signal based on its disposition:

flowchart TD
HandleSignal["handle_signal()"]
CheckDisposition["Check disposition"]
DefaultAction["Get default actionfor signal number"]
ReturnOSAction["Return appropriateSignalOSAction"]
ReturnNone["Return None"]
SetupStack["Setup stack forsignal handler"]
CreateFrame["Create SignalFrameon stack"]
ModifyTF["Modify trap frameto call handler"]
AddArguments["Set arguments(signo, siginfo, ucontext)"]
SetRestorer["Set signal restorer"]
UpdateBlockedSignals["Update blocked signals"]
CheckResethand["Has RESETHANDflag?"]
ResetAction["Reset signal actionto default"]
Skip["Skip reset"]
ReturnHandler["ReturnSignalOSAction::Handler"]

AddArguments --> SetRestorer
CheckDisposition --> DefaultAction
CheckDisposition --> ReturnNone
CheckDisposition --> SetupStack
CheckResethand --> ResetAction
CheckResethand --> Skip
CreateFrame --> ModifyTF
DefaultAction --> ReturnOSAction
HandleSignal --> CheckDisposition
ModifyTF --> AddArguments
ResetAction --> ReturnHandler
SetRestorer --> UpdateBlockedSignals
SetupStack --> CreateFrame
Skip --> ReturnHandler
UpdateBlockedSignals --> CheckResethand

Sources: src/api/thread.rs(L50 - L117) 

Check and Handle Signals

The check_signals method checks for pending signals and handles them:

pub fn check_signals(
    &self,
    tf: &mut TrapFrame,
    restore_blocked: Option<SignalSet>,
) -> Option<(SignalInfo, SignalOSAction)> {
    let actions = self.proc.actions.lock();
    
    let blocked = self.blocked.lock();
    let mask = !*blocked;
    let restore_blocked = restore_blocked.unwrap_or_else(|| *blocked);
    drop(blocked);
    
    loop {
        let Some(sig) = self.dequeue_signal(&mask) else {
            return None;
        };
        let action = &actions[sig.signo()];
        if let Some(os_action) = self.handle_signal(tf, restore_blocked, &sig, action) {
            break Some((sig, os_action));
        }
    }
}

Sources: src/api/thread.rs(L119 - L143) 

Signal Frame Restoration

The restore method restores the original context from a signal frame:

pub fn restore(&self, tf: &mut TrapFrame) {
    let frame_ptr = tf.sp() as *const SignalFrame;
    // SAFETY: pointer is valid
    let frame = unsafe { &*frame_ptr };
    
    *tf = frame.tf;
    frame.ucontext.mcontext.restore(tf);
    
    *self.blocked.lock() = frame.ucontext.sigmask;
}

Sources: src/api/thread.rs(L145 - L155) 

Signal Waiting

The wait_timeout method allows a thread to wait for a specific set of signals:

pub fn wait_timeout(
    &self,
    mut set: SignalSet,
    timeout: Option<Duration>,
) -> Option<SignalInfo> {
    // Non-blocked signals cannot be waited
    set &= self.blocked();
    
    if let Some(sig) = self.dequeue_signal(&set) {
        return Some(sig);
    }
    
    let wq = &self.proc.wq;
    let deadline = timeout.map(|dur| axhal::time::wall_time() + dur);
    
    // There might be false wakeups, so we need a loop
    loop {
        match &deadline {
            Some(deadline) => {
                match deadline.checked_sub(axhal::time::wall_time()) {
                    Some(dur) => {
                        if wq.wait_timeout(Some(dur)) {
                            // timed out
                            break;
                        }
                    }
                    None => {
                        // deadline passed
                        break;
                    }
                }
            }
            _ => wq.wait(),
        }
        
        if let Some(sig) = self.dequeue_signal(&set) {
            return Some(sig);
        }
    }
    
    // TODO: EINTR
    None
}

Sources: src/api/thread.rs(L196 - L239) 

Integration with the Signal Handling System

The ThreadSignalManager is a key component in the overall signal handling architecture:


Sources: src/api/thread.rs(L21 - L31)  src/api/thread.rs(L43 - L48) 

Performance and Synchronization

The ThreadSignalManager uses mutexes to protect its internal state:

Protected ResourcePurpose
pendingProtects the thread's pending signals queue
blockedProtects the set of signals blocked from delivery
stackProtects the signal handler stack configuration

The manager also interacts with the process-level wait queue for signal notification across threads.

Sources: src/api/thread.rs(L21 - L31)  src/api/thread.rs(L196 - L239) 

Summary

The Thread Signal Manager is a crucial component of the axsignal crate that handles thread-specific signal management. It works in coordination with the Process Signal Manager to provide a complete signal handling solution, supporting both standard Unix-like signals and real-time signals across multiple architectures.

Key responsibilities include:

  • Managing thread-specific pending signals
  • Handling signal blocking and unblocking
  • Setting up and executing signal handlers
  • Managing signal stacks
  • Coordinating with the process-level signal manager
  • Supporting signal waiting operations with timeouts

The thread-specific nature of the ThreadSignalManager allows for fine-grained control over signal handling within multi-threaded applications, while still maintaining compatibility with process-level signal delivery mechanisms.