Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spec couche 0 — Famille signal

Spécification technique — Version 1.0

Vue d’ensemble de la famille

Le module air-sys-syscall::signal expose les primitives de gestion des signaux Unix. Conformément à l’ADR-020, l’approche d’Air est :

  1. signalfd par défaut pour tous les signaux différables. Le mécanisme transforme les signaux en événements lisibles sur un FD, ce qui s’intègre naturellement avec io_uring et avec l’event loop unifiée.

  2. sigaction restreint dans un sous-module synchronous_handler pour les quatre signaux synchrones non-déférables (SIGSEGV, SIGBUS, SIGFPE, SIGILL).

Cette discipline élimine structurellement une classe entière de bugs (handlers signaux non-async-signal-safe), tout en préservant la capacité à gérer les cas où sigaction est inévitable.

Caractéristiques transverses de la famille.

  1. Pas de handler générique exposé. L’API ne permet pas d’installer un handler sigaction sur un signal arbitraire. La voie est barrée par construction.

  2. Tous les FDs créés sont CLOEXEC par défaut. Cohérent avec la discipline universelle de la couche 0.

  3. Gestion thread-safe. Les opérations sur masques de signaux sont thread-safe (chaque thread a son propre masque). Les opérations sur handlers sont par processus.

  4. Intégration io_uring naturelle. Un FD signalfd peut être lu via io_uring, ce qui permet d’attendre les signaux dans le même mécanisme que les autres événements asynchrones.

Types transverses utilisés.

  • Signal : représentation typée d’un signal.
  • SignalMask : ensemble de signaux comme bitmask.
  • SignalFd : FD signalfd.
  • SignalFdInfo : structure retournée par la lecture d’un signalfd.
  • FatalSignal : enum restreint des 4 signaux synchrones acceptables pour sigaction.
  • SignalAction : type pour les handlers (utilisé uniquement dans le sous-module synchronous_handler).

Sous-section 1 : signalfd et masques de signaux

signalfd_create

Signature.

#![allow(unused)]
fn main() {
pub fn signalfd_create(
    mask: &SignalMask,
    flags: SignalFdFlags,
) -> Result<SignalFd, Errno>;

pub struct SignalFd { /* opaque, possède OwnedFd interne */ }

impl SignalFd {
    pub fn as_fd(&self) -> BorrowedFd<'_>;
    pub fn into_fd(self) -> OwnedFd;
    pub fn read(&self) -> Result<SignalFdInfo, Errno>;
    pub fn update_mask(&mut self, mask: &SignalMask) -> Result<(), Errno>;
}

bitflags! {
    pub struct SignalFdFlags: i32 {
        const NONBLOCK = 0x800;
        const CLOEXEC = 0x80000;
    }
}

#[repr(C)]
pub struct SignalFdInfo {
    pub signal: Signal,
    pub errno: i32,
    pub code: i32,
    pub pid: Pid,
    pub uid: u32,
    pub fd: i32,
    pub timer_id: u32,
    pub band: u32,
    pub overrun: u32,
    pub trap_no: u32,
    pub status: i32,
    pub int: i32,
    pub ptr: u64,
    pub utime: u64,
    pub stime: u64,
    pub addr: u64,
}
}

Syscall sous-jacent. signalfd4 (x86_64 n°289, ARM64 n°74). Page man signalfd(2). Disponible depuis Linux 2.6.27.

Préconditions.

mask indique les signaux que ce signalfd doit attraper. Les signaux non listés dans le mask continuent à être délivrés selon leur disposition par défaut.

SignalFdFlags::CLOEXEC est toujours activé par défaut par le wrapper.

Comportement.

Crée un FD qui transforme les signaux du mask en événements lisibles. Quand un des signaux listés est délivré au thread, au lieu d’invoquer un handler, il devient “lisible” sur le FD signalfd.

Important : le mask seul ne suffit pas.

Créer un signalfd pour un signal S ne suffit pas à empêcher la délivrance normale de S (handler ou comportement par défaut). Il faut aussi bloquer S dans le masque de signaux du thread via block_signals. Le pattern correct :

#![allow(unused)]
fn main() {
let mask = SignalMask::from_signals(&[Signal::SIGTERM, Signal::SIGINT]);
block_signals(&mask)?;
let sfd = signalfd_create(&mask, SignalFdFlags::empty())?;
// maintenant SIGTERM et SIGINT arriveront uniquement sur sfd
}

Air fournit un helper qui combine les deux étapes :

#![allow(unused)]
fn main() {
pub fn signalfd_create_blocking(
    mask: &SignalMask,
    flags: SignalFdFlags,
) -> Result<SignalFd, Errno>;
}

Erreurs.

  • EINVAL : mask invalide ou flags invalides.
  • EMFILE, ENFILE : limites de FDs atteintes.
  • ENODEV, ENOMEM : ressources kernel insuffisantes.

Performance.

Création : ~5-10 µs. Lecture via read() : ~1-2 µs si un signal est disponible immédiatement.

block_signals et unblock_signals

Signature.

#![allow(unused)]
fn main() {
pub fn block_signals(mask: &SignalMask) -> Result<SignalMask, Errno>;
pub fn unblock_signals(mask: &SignalMask) -> Result<SignalMask, Errno>;
pub fn set_signal_mask(mask: &SignalMask) -> Result<SignalMask, Errno>;
pub fn current_signal_mask() -> Result<SignalMask, Errno>;
}

Syscall sous-jacent. rt_sigprocmask (x86_64 n°14, ARM64 n°135).

Comportement.

block_signals ajoute les signaux du mask au mask courant du thread. Retourne l’ancien mask (avant modification).

unblock_signals retire les signaux du mask.

set_signal_mask remplace entièrement le mask.

current_signal_mask lit le mask actuel sans modification.

Cas particuliers.

SIGKILL et SIGSTOP ne peuvent pas être bloqués. Tentative de les inclure dans le mask est silencieusement ignorée par le kernel.

Le mask est hérité à travers fork. execve réinitialise le mask.

Performance. ~100-200 ns. Très rapide.

Helper wait_for_signal

Signature.

#![allow(unused)]
fn main() {
pub fn wait_for_signal(
    mask: &SignalMask,
    timeout: Option<Duration>,
) -> Result<SignalFdInfo, Errno>;
}

Comportement.

Helper qui crée un signalfd temporaire, attend l’arrivée d’un signal du mask, et retourne l’info. Pratique pour les patterns simples.

Sous-section 2 : Envoi de signaux

kill et tgkill

#![allow(unused)]
fn main() {
pub fn kill(pid: Pid, signal: Option<Signal>) -> Result<(), Errno>;
pub fn tgkill(tgid: Pid, tid: Tid, signal: Option<Signal>) -> Result<(), Errno>;
}

Syscalls sous-jacents. kill (x86_64 n°62, ARM64 n°129), tgkill (x86_64 n°234, ARM64 n°131).

Préconditions.

Option<Signal> permet le pattern “signal 0” (test d’existence sans envoi effectif) via None. Cohérent avec la convention 1 de l’ADR-021.

Comportement.

kill(pid, sig) envoie un signal au processus identifié par pid.

tgkill(tgid, tid, sig) envoie un signal à un thread spécifique d’un processus.

Recommandation Air.

Pour les processus enfants, préférer pidfd_send_signal (famille process) sur kill : pas de race sur le PID recyclé.

Erreurs.

  • EINVAL : signal invalide.
  • EPERM : permissions insuffisantes.
  • ESRCH : processus inexistant.

Performance. ~1-2 µs.

rt_sigqueueinfo

#![allow(unused)]
fn main() {
pub fn rt_sigqueueinfo(
    pid: Pid,
    signal: Signal,
    info: SignalQueueInfo,
) -> Result<(), Errno>;

pub struct SignalQueueInfo {
    pub code: i32,
    pub value: SignalValue,
}

pub enum SignalValue {
    Integer(i32),
    Pointer(u64),
}
}

Envoi de signal avec une “valeur” attachée. Cas d’usage rare.

Sous-section 3 : sigaction restreint

Conformément à l’ADR-020, le sous-module air-sys-syscall::signal::synchronous_handler expose sigaction uniquement pour les 4 signaux synchrones fatals.

Type FatalSignal

#![allow(unused)]
fn main() {
pub mod synchronous_handler {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub enum FatalSignal {
        Segv,
        Bus,
        Fpe,
        Ill,
    }
    
    impl FatalSignal {
        pub const fn as_signal(self) -> Signal;
    }
}
}

L’enum est restreint à ces 4 signaux. Pas moyen de passer un autre signal à cette API.

install_fatal_handler

#![allow(unused)]
fn main() {
pub mod synchronous_handler {
    pub unsafe fn install_fatal_handler(
        signal: FatalSignal,
        handler: FatalHandler,
    ) -> Result<PreviousHandler, Errno>;
    
    pub unsafe fn restore_handler(
        signal: FatalSignal,
        previous: PreviousHandler,
    ) -> Result<(), Errno>;
    
    pub type FatalHandler = unsafe extern "C" fn(
        signum: c_int,
        info: *mut SignalInfo,
        context: *mut c_void,
    );
    
    pub struct PreviousHandler {
        // Opaque
    }
    
    #[repr(C)]
    pub struct SignalInfo {
        pub signal: c_int,
        pub errno: c_int,
        pub code: c_int,
        pub addr: u64,
        // ...
    }
}
}

Préconditions (Safety).

L’API est unsafe. La documentation # Safety exige que le handler soit async-signal-safe.

Cas d’usage légitimes.

  1. Crash reporter : capture l’état du processus au moment d’un crash, écrit un mini-dump, puis termine.
  2. Stack guards : détection de stack overflow via signal handler sur une stack alternative.

Tests délicats.

Les tests s’exécutent dans des sous-processus isolés via harnais subprocess.

Récapitulatif de la famille signal

Fonctions exposées :

CatégorieFonctions principales
signalfdsignalfd_create, signalfd_create_blocking, SignalFd::read, SignalFd::update_mask
Masquesblock_signals, unblock_signals, set_signal_mask, current_signal_mask
Helperswait_for_signal
Envoikill, tgkill, rt_sigqueueinfo
Handlers (restreint)install_fatal_handler, restore_handler

Total : ~12 fonctions publiques principales.

Syscalls non-wrappés (listés dans UNSUPPORTED.md) :

  • signal (BSD-style, deprecated).
  • tkill (deprecated, remplacé par tgkill).
  • pause, alarm : legacy, remplacés par signalfd + timerfd.
  • sigsuspend, rt_sigsuspend : pattern obsolète, remplacé par signalfd.
  • sigaltstack : à ajouter plus tard si stack guards nécessaires.

Types ajoutés à air-sys-types

  • SignalFdInfo
  • SignalFdFlags
  • SignalQueueInfo, SignalValue
  • FatalSignal (sous-module synchronous_handler)
  • SignalInfo (sous-module synchronous_handler)
  • PreviousHandler (sous-module synchronous_handler)

Soit ~6 types supplémentaires.

Décisions de fond émergées dans la famille signal

1. signalfd_create_blocking comme helper canonique.

Le pattern “créer signalfd + bloquer signaux” est tellement universel qu’un helper combiné simplifie l’usage et évite le piège de l’oubli.

2. kill via Option<Signal> pour le signal 0.

Application de la convention 1 de l’ADR-021.

3. install_fatal_handler retourne PreviousHandler opaque.

Cache les détails kernel de la struct sigaction.

4. Pas de wrapper pour sigaltstack.

Cas spécialisé, à ajouter plus tard sans casser l’API.


Licence du document : MPL 2.0 Statut : Spécification technique du module air-sys-syscall::signal (couche 0).