Merge branch 'feat/thread-based-alarm' into 'master'
feat: implement alarm(2) using thread-based POSIX timer machinery See merge request redox-os/relibc!1109
This commit is contained in:
@@ -66,6 +66,9 @@ pub(crate) struct timer_internal_t {
|
||||
pub caller_thread: crate::pthread::OsTid,
|
||||
// relibc handles it_interval, not the kernel
|
||||
pub next_wake_time: itimerspec,
|
||||
// When non-zero, timer_routine delivers SIGALRM via kill(process_pid, sig)
|
||||
// instead of rlct_kill (thread-specific). Used by alarm().
|
||||
pub process_pid: platform::types::pid_t,
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/time.h.html>.
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::{
|
||||
},
|
||||
out::Out,
|
||||
platform::{
|
||||
self, ERRNO, Pal, Sys,
|
||||
self, ERRNO, Pal, PalSignal, Sys,
|
||||
types::{
|
||||
c_char, c_int, c_long, c_short, c_uint, c_ulonglong, c_void, gid_t, off_t, pid_t,
|
||||
size_t, ssize_t, suseconds_t, time_t, uid_t, useconds_t,
|
||||
@@ -137,28 +137,7 @@ pub unsafe extern "C" fn access(path: *const c_char, mode: c_int) -> c_int {
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/alarm.html>.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn alarm(seconds: c_uint) -> c_uint {
|
||||
// TODO setitimer is unimplemented on Redox and obsolete
|
||||
let timer = sys_time::itimerval {
|
||||
it_value: timeval {
|
||||
tv_sec: time_t::from(seconds),
|
||||
tv_usec: 0,
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let mut otimer = sys_time::itimerval::default();
|
||||
|
||||
let errno_backup = platform::ERRNO.get();
|
||||
let secs =
|
||||
if unsafe { sys_time::setitimer(sys_time::ITIMER_REAL, &raw const timer, &raw mut otimer) }
|
||||
< 0
|
||||
{
|
||||
0
|
||||
} else {
|
||||
otimer.it_value.tv_sec as c_uint + if otimer.it_value.tv_usec > 0 { 1 } else { 0 }
|
||||
};
|
||||
platform::ERRNO.set(errno_backup);
|
||||
|
||||
secs
|
||||
Sys::alarm(seconds)
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/chdir.html>.
|
||||
|
||||
@@ -6,7 +6,7 @@ use core::{
|
||||
use super::{
|
||||
super::{
|
||||
PalSignal,
|
||||
types::{c_int, pid_t},
|
||||
types::{c_int, c_uint, pid_t, time_t},
|
||||
},
|
||||
Sys, e_raw,
|
||||
};
|
||||
@@ -15,8 +15,10 @@ use crate::{
|
||||
header::{
|
||||
bits_time::timespec,
|
||||
signal::{SA_RESTORER, SI_QUEUE, sigaction, siginfo_t, sigset_t, sigval, stack_t},
|
||||
sys_time::itimerval,
|
||||
sys_select::timeval,
|
||||
sys_time::{self, itimerval},
|
||||
},
|
||||
platform,
|
||||
};
|
||||
|
||||
impl PalSignal for Sys {
|
||||
@@ -68,6 +70,26 @@ impl PalSignal for Sys {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/alarm.html>.
|
||||
fn alarm(seconds: c_uint) -> c_uint {
|
||||
let timer = itimerval {
|
||||
it_value: timeval {
|
||||
tv_sec: time_t::from(seconds),
|
||||
tv_usec: 0,
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let mut otimer = itimerval::default();
|
||||
let errno_backup = platform::ERRNO.get();
|
||||
let secs = if Self::setitimer(sys_time::ITIMER_REAL, &timer, Some(&mut otimer)).is_err() {
|
||||
0
|
||||
} else {
|
||||
otimer.it_value.tv_sec as c_uint + if otimer.it_value.tv_usec > 0 { 1 } else { 0 }
|
||||
};
|
||||
platform::ERRNO.set(errno_backup);
|
||||
secs
|
||||
}
|
||||
|
||||
fn sigaction(
|
||||
sig: c_int,
|
||||
act: Option<&sigaction>,
|
||||
|
||||
@@ -21,6 +21,8 @@ pub trait PalSignal: Pal {
|
||||
|
||||
fn setitimer(which: c_int, new: &itimerval, old: Option<&mut itimerval>) -> Result<()>;
|
||||
|
||||
fn alarm(seconds: c_uint) -> c_uint;
|
||||
|
||||
fn sigaction(sig: c_int, act: Option<&sigaction>, oact: Option<&mut sigaction>) -> Result<()>;
|
||||
|
||||
unsafe fn sigaltstack(ss: Option<&stack_t>, old_ss: Option<&mut stack_t>) -> Result<()>;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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,100 @@ 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.
|
||||
///
|
||||
/// TODO: This implementation does not survive `exec()`. POSIX requires that a
|
||||
/// pending alarm be preserved across exec (the timer continues counting down
|
||||
/// in the new process image as i understand).
|
||||
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,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +159,7 @@ EXPECT_NAMES=\
|
||||
time/timegm \
|
||||
time/tzset \
|
||||
unistd/access \
|
||||
unistd/alarm \
|
||||
unistd/constants \
|
||||
unistd/confstr \
|
||||
unistd/dup \
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
alarm(0) baseline: ok
|
||||
alarm(1) fires: ok
|
||||
alarm(0) cancel: ok
|
||||
alarm re-arm returns remaining: ok
|
||||
@@ -0,0 +1,4 @@
|
||||
alarm(0) baseline: ok
|
||||
alarm(1) fires: ok
|
||||
alarm(0) cancel: ok
|
||||
alarm re-arm returns remaining: ok
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Tests for alarm(2) - POSIX: https://pubs.opengroup.org/onlinepubs/9799919799/functions/alarm.html
|
||||
*
|
||||
* Verifies:
|
||||
* 1. alarm(0) with no pending alarm returns 0
|
||||
* 2. alarm(1) delivers SIGALRM after ~1 second (tested via pause())
|
||||
* 3. alarm(0) cancels a pending alarm
|
||||
* 4. re-arming alarm returns the remaining seconds from the previous alarm
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "test_helpers.h"
|
||||
|
||||
static volatile sig_atomic_t alarm_count = 0;
|
||||
|
||||
static void handler(int sig) {
|
||||
(void)sig;
|
||||
alarm_count++;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = handler;
|
||||
sa.sa_flags = 0;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
int r = sigaction(SIGALRM, &sa, NULL);
|
||||
ERROR_IF(sigaction, r, == -1);
|
||||
|
||||
/* alarm(0) with no existing alarm must return 0 */
|
||||
unsigned prev = alarm(0);
|
||||
UNEXP_IF(alarm, (int)prev, != 0);
|
||||
puts("alarm(0) baseline: ok");
|
||||
|
||||
/* alarm(1): SIGALRM must fire; pause() blocks until the signal arrives */
|
||||
alarm_count = 0;
|
||||
alarm(1);
|
||||
pause();
|
||||
if (alarm_count != 1) {
|
||||
fprintf(stderr, "SIGALRM did not fire (count=%d)\n", (int)alarm_count);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
puts("alarm(1) fires: ok");
|
||||
|
||||
/* alarm(0) must cancel a pending alarm and return remaining secs > 0 */
|
||||
alarm_count = 0;
|
||||
alarm(10);
|
||||
unsigned remaining = alarm(0);
|
||||
if (remaining == 0) {
|
||||
fprintf(stderr, "alarm(0) cancel: expected remaining > 0\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* Wait longer than original alarm; SIGALRM must NOT fire */
|
||||
sleep(2);
|
||||
if (alarm_count != 0) {
|
||||
fprintf(stderr, "SIGALRM fired after alarm(0) cancel\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
puts("alarm(0) cancel: ok");
|
||||
|
||||
/*
|
||||
* Re-arming: arm with 10s, sleep 1s, then re-arm with 3s.
|
||||
* The return value must be the remaining time (~9s) which is > 0.
|
||||
*/
|
||||
alarm_count = 0;
|
||||
alarm(10);
|
||||
sleep(1);
|
||||
remaining = alarm(3);
|
||||
if (remaining == 0) {
|
||||
fprintf(stderr, "re-arm: expected remaining > 0, got 0\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
alarm(0); /* disarm so SIGALRM doesn't fire after the test */
|
||||
puts("alarm re-arm returns remaining: ok");
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user