feat(signal): Implement alarm() and timer_settime logic

Adds the implementation for the `alarm()` function and its underlying
`alarm_timespec` helper. This involves managing a process-global POSIX timer
to deliver SIGALRM signals.
This commit is contained in:
Zero
2026-03-24 01:39:00 +02:00
parent d34872cb35
commit e0c9b0ed03
3 changed files with 119 additions and 6 deletions
+1
View File
@@ -1298,6 +1298,7 @@ impl Pal for Sys {
timer_st.next_wake_time = itimerspec::default();
timer_st.thread = ptr::null_mut();
timer_st.caller_thread = caller_thread;
timer_st.process_pid = 0;
timer_buf
};
+109 -2
View File
@@ -8,11 +8,15 @@ use crate::{
bits_time::timespec,
errno::{EINVAL, ENOSYS},
signal::{
SIG_BLOCK, SIG_DFL, SIG_IGN, SIG_SETMASK, SIG_UNBLOCK, SS_DISABLE, SS_ONSTACK,
sigaction, siginfo_t, sigset_t, sigval, stack_t, ucontext_t,
SIG_BLOCK, SIG_DFL, SIG_IGN, SIG_SETMASK, SIG_UNBLOCK, SIGALRM, SIGEV_SIGNAL,
SS_DISABLE, SS_ONSTACK, sigaction, sigevent, siginfo_t, sigset_t, sigval, stack_t,
ucontext_t,
},
sys_time::{ITIMER_REAL, itimerval},
time::{itimerspec, timer_internal_t},
},
out::Out,
sync::Mutex,
};
use core::mem::offset_of;
use redox_protocols::protocol::ProcKillTarget;
@@ -20,6 +24,17 @@ use redox_rt::signal::{
PosixStackt, SigStack, Sigaction, SigactionFlags, SigactionKind, Sigaltstack, SignalHandler,
};
/// Wrapper for timer_t that implements Send (the timer_t pointer is a process-
/// wide mmap'd allocation that outlives any single thread).
struct AlarmTimer(timer_t);
// SAFETY: The timer_t pointer refers to an mmap'd timer_internal_t that is
// only accessed under the ALARM_TIMER mutex lock.
unsafe impl Send for AlarmTimer {}
/// Process-global singleton timer used by alarm(). Protected by a mutex to
/// ensure only one alarm is active at a time (POSIX requirement).
static ALARM_TIMER: Mutex<Option<AlarmTimer>> = Mutex::new(None);
const _: () = {
#[track_caller]
const fn assert_eq(a: usize, b: usize) {
@@ -245,4 +260,96 @@ impl PalSignal for Sys {
}
Ok(info.si_signo)
}
/// Thread-based alarm() that Recycles the existing POSIX timer machinery
/// (timerfd + eventfd + pthread) with process-level signal delivery.
///
/// Internally works with a timespec to allow sub-second timers in the
/// future (e.g. ualarm) as i've been asked, though the public API only exposes whole seconds.
fn alarm(seconds: c_uint) -> c_uint {
alarm_timespec(timespec {
tv_sec: seconds as time_t,
tv_nsec: 0,
})
}
}
/// Internal helper that arms/disarms the process-global alarm timer.
/// Accepts a full timespec so sub-second timers (ualarm) can reuse this later.
/// Returns the number of seconds remaining on the previous alarm (rounded up),
/// or 0 if there was no previous alarm.
fn alarm_timespec(duration: timespec) -> c_uint {
let mut guard = ALARM_TIMER.lock();
// Determine remaining time on any existing alarm
let remaining = if let Some(ref alarm) = *guard {
let mut cur = itimerspec::default();
if Sys::timer_gettime(alarm.0, Out::from_mut(&mut cur)).is_ok() {
let secs = cur.it_value.tv_sec as c_uint;
if cur.it_value.tv_nsec > 0 {
secs + 1 // POSIX: round up
} else {
secs
}
} else {
0
}
} else {
0
};
let disarm = duration.tv_sec == 0 && duration.tv_nsec == 0;
if disarm {
// alarm(0): cancel any pending alarm
if let Some(ref alarm) = *guard {
let zero = itimerspec::default();
let _ = Sys::timer_settime(alarm.0, 0, &zero, None);
}
return remaining;
}
// Lazily create the singleton timer if it doesn't exist yet
if guard.is_none() {
let evp = sigevent {
sigev_value: sigval {
sival_ptr: core::ptr::null_mut(),
},
sigev_signo: SIGALRM as c_int,
sigev_notify: SIGEV_SIGNAL,
sigev_notify_function: None,
sigev_notify_attributes: core::ptr::null_mut(),
};
let mut timer_id: timer_t = core::ptr::null_mut();
if Sys::timer_create(
crate::header::time::CLOCK_REALTIME,
&evp,
Out::from_mut(&mut timer_id),
)
.is_err()
{
return remaining;
}
// Enable process-wide signal delivery instead of thread-specific
let timer_st = unsafe { &mut *(timer_id as *mut timer_internal_t) };
timer_st.process_pid = Sys::getpid();
*guard = Some(AlarmTimer(timer_id));
}
let timer_id = guard
.as_ref()
.expect("alarm timer must exist after lazy init")
.0;
// Arm the timer as a one-shot (no interval)
let spec = itimerspec {
it_value: duration,
it_interval: timespec::default(),
};
let _ = Sys::timer_settime(timer_id, 0, &spec, None);
remaining
}
+9 -4
View File
@@ -9,7 +9,7 @@ use crate::{
time::timer_internal_t,
},
out::Out,
platform::{Pal, Sys, sys::event, types::c_void},
platform::{Pal, PalSignal, Sys, sys::event, types::c_void},
};
use core::{
mem::{MaybeUninit, size_of},
@@ -42,9 +42,14 @@ pub extern "C" fn timer_routine(arg: *mut c_void) -> *mut c_void {
fun(timer_st.evp.sigev_value);
}
} else if timer_st.evp.sigev_notify == SIGEV_SIGNAL {
if unsafe { Sys::rlct_kill(timer_st.caller_thread, timer_st.evp.sigev_signo as _) }
.is_err()
{
// process_pid != 0 => process-wide delivery (used by alarm())
// process_pid == 0 => thread-specific delivery (used by timer_create)
let result = if timer_st.process_pid != 0 {
Sys::kill(timer_st.process_pid, timer_st.evp.sigev_signo)
} else {
unsafe { Sys::rlct_kill(timer_st.caller_thread, timer_st.evp.sigev_signo as _) }
};
if result.is_err() {
break;
}
}