From 5c78356290fe6b369fd3b8a308cacbe80bcc5767 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Sun, 4 Aug 2024 19:31:06 +0200 Subject: [PATCH] Implement sigtimedwait on Redox. --- redox-rt/src/signal.rs | 152 +++++++++++++++++++++++++++++++---- src/header/signal/mod.rs | 4 +- src/header/signal/redox.rs | 13 ++- src/platform/linux/signal.rs | 17 ++-- src/platform/pal/signal.rs | 5 +- src/platform/redox/signal.rs | 23 +++--- 6 files changed, 177 insertions(+), 37 deletions(-) diff --git a/redox-rt/src/signal.rs b/redox-rt/src/signal.rs index ce2a4344d..4a8e0a675 100644 --- a/redox-rt/src/signal.rs +++ b/redox-rt/src/signal.rs @@ -1,15 +1,17 @@ use core::{ cell::{Cell, UnsafeCell}, ffi::{c_int, c_void}, + mem::MaybeUninit, ptr::NonNull, sync::atomic::{AtomicU8, AtomicUsize, Ordering}, }; use syscall::{ - data::AtomicU64, Error, NonatomicUsize, RawAction, Result, SenderInfo, SetSighandlerData, - SigProcControl, Sigcontrol, SigcontrolFlags, EINVAL, ENOMEM, EPERM, SIGABRT, SIGBUS, SIGCHLD, - SIGCONT, SIGFPE, SIGILL, SIGKILL, SIGQUIT, SIGSEGV, SIGSTOP, SIGSYS, SIGTRAP, SIGTSTP, SIGTTIN, - SIGTTOU, SIGURG, SIGWINCH, SIGXCPU, SIGXFSZ, + data::AtomicU64, Error, NonatomicUsize, RawAction, Result, RtSigInfo, SenderInfo, + SetSighandlerData, SigProcControl, Sigcontrol, SigcontrolFlags, TimeSpec, EAGAIN, EINTR, + EINVAL, ENOMEM, EPERM, SIGABRT, SIGBUS, SIGCHLD, SIGCONT, SIGFPE, SIGILL, SIGKILL, SIGQUIT, + SIGSEGV, SIGSTOP, SIGSYS, SIGTRAP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGWINCH, SIGXCPU, + SIGXFSZ, }; use crate::{arch::*, proc::FdGuard, sync::Mutex, RtTcb, Tcb}; @@ -160,14 +162,14 @@ unsafe fn inner(stack: &mut SigStack) { }; // Set sigmask to sa_mask and unmark the signal as pending. - let prev_sigallow = get_mask_raw(&os.control.word); + let prev_sigallow = get_allowset_raw(&os.control.word); let mut sigallow_inside = !sigaction.mask & prev_sigallow; if !sigaction.flags.contains(SigactionFlags::NODEFER) { sigallow_inside &= !sig_bit(stack.sig_num); } - let _pending_when_sa_mask = set_mask_raw(&os.control.word, prev_sigallow, sigallow_inside); + let _pending_when_sa_mask = set_allowset_raw(&os.control.word, prev_sigallow, sigallow_inside); // TODO: If sa_mask caused signals to be unblocked, deliver one or all of those first? @@ -214,9 +216,9 @@ unsafe fn inner(stack: &mut SigStack) { // Update allowset again. let new_mask = stack.old_mask; - let old_mask = get_mask_raw(&os.control.word); + let old_mask = get_allowset_raw(&os.control.word); - let _pending_when_restored_mask = set_mask_raw(&os.control.word, old_mask, new_mask); + let _pending_when_restored_mask = set_allowset_raw(&os.control.word, old_mask, new_mask); // TODO: If resetting the sigmask caused signals to be unblocked, then should they be delivered // here? And would it be possible to tail-call-optimize that? @@ -267,11 +269,11 @@ pub fn andn_sigmask(new: Option<u64>, old: Option<&mut u64>) -> Result<()> { new.map(move |newmask| move |oldmask| oldmask & !newmask), ) } -fn get_mask_raw(words: &[AtomicU64; 2]) -> u64 { +fn get_allowset_raw(words: &[AtomicU64; 2]) -> u64 { (words[0].load(Ordering::Relaxed) >> 32) | ((words[1].load(Ordering::Relaxed) >> 32) << 32) } /// Sets mask from old to new, returning what was pending at the time. -fn set_mask_raw(words: &[AtomicU64; 2], old: u64, new: u64) -> u64 { +fn set_allowset_raw(words: &[AtomicU64; 2], old: u64, new: u64) -> u64 { // This assumes *only this thread* can change the allowset. If this rule is broken, the use of // fetch_add will corrupt the words entirely. fetch_add is very efficient on x86, being // generated as LOCK XADD which is the fastest RMW instruction AFAIK. @@ -290,7 +292,7 @@ fn modify_sigmask(old: Option<&mut u64>, op: Option<impl FnOnce(u64) -> u64>) -> let _guard = tmp_disable_signals(); let ctl = current_sigctl(); - let prev = get_mask_raw(&ctl.word); + let prev = get_allowset_raw(&ctl.word); if let Some(old) = old { *old = !prev; @@ -301,7 +303,7 @@ fn modify_sigmask(old: Option<&mut u64>, op: Option<impl FnOnce(u64) -> u64>) -> let next = !op(!prev); - let pending = set_mask_raw(&ctl.word, prev, next); + let pending = set_allowset_raw(&ctl.word, prev, next); // POSIX requires that at least one pending unblocked signal be delivered before // pthread_sigmask returns, if there is one. @@ -655,7 +657,7 @@ pub unsafe fn sigaltstack( Ok(()) } -pub const MIN_SIGALTSTACK_SIZE: usize = 8192; +pub const MIN_SIGALTSTACK_SIZE: usize = 2048; pub fn currently_pending_blocked() -> u64 { let control = &unsafe { Tcb::current().unwrap() }.os_specific.control; @@ -670,17 +672,133 @@ pub fn currently_pending_blocked() -> u64 { (thread_pending | proc_pending) & !allow } pub enum Unreachable {} -pub fn wait_with_mask(mask: u64) -> Result<Unreachable, Error> { + +pub fn await_signal_async(set: u64) -> Result<Unreachable> { let mut old = 0; - set_sigmask(Some(mask), Some(&mut old))?; + set_sigmask(Some(!set), Some(&mut old))?; + // TODO: RAII guard let res = syscall::nanosleep( - &syscall::TimeSpec { + &TimeSpec { tv_sec: i64::MAX, tv_nsec: 0, }, - &mut syscall::TimeSpec::default(), + &mut TimeSpec::default(), ); set_sigmask(Some(old), None)?; res?; unreachable!() } +// TODO: deadline-based API +pub fn await_signal_sync(inner_allowset: u64, timeout: &TimeSpec) -> Result<SiginfoAbi> { + let _guard = tmp_disable_signals(); + let control = &unsafe { Tcb::current().unwrap() }.os_specific.control; + + let old_allowset = get_allowset_raw(&control.word); + let proc_pending = PROC_CONTROL_STRUCT.pending.load(Ordering::Acquire); + let thread_pending = set_allowset_raw(&control.word, old_allowset, inner_allowset); + + // Check if there are already signals matching the requested set, before waiting. + if let Some(info) = try_claim_multiple(proc_pending, thread_pending, inner_allowset, control) { + // TODO: RAII + set_allowset_raw(&control.word, inner_allowset, old_allowset); + return Ok(info); + } + + let res = syscall::nanosleep(&timeout, &mut TimeSpec::default()); + let thread_pending = set_allowset_raw(&control.word, inner_allowset, old_allowset); + let proc_pending = PROC_CONTROL_STRUCT.pending.load(Ordering::Acquire); + + if let Err(error) = res + && error.errno != EINTR + { + return Err(error); + } + + // Then check if there were any signals left after waiting. + try_claim_multiple(proc_pending, thread_pending, inner_allowset, control) + // Normally ETIMEDOUT but not for sigtimedwait. + .ok_or(Error::new(EAGAIN)) +} +fn try_claim_multiple( + mut proc_pending: u64, + mut thread_pending: u64, + allowset: u64, + control: &Sigcontrol, +) -> Option<SiginfoAbi> { + while (proc_pending | thread_pending) & allowset != 0 { + let sig_idx = ((proc_pending | thread_pending) & allowset).trailing_zeros(); + if thread_pending & (1 << sig_idx) != 0 + && let Some(res) = try_claim_single(sig_idx, Some(control)) + { + return Some(res); + } + thread_pending &= !(1 << sig_idx); + if let Some(res) = try_claim_single(sig_idx, None) { + return Some(res); + } + proc_pending &= !(1 << sig_idx); + } + None +} +fn try_claim_single(sig_idx: u32, thread_control: Option<&Sigcontrol>) -> Option<SiginfoAbi> { + let sig_group = sig_idx / 1; + + if sig_group == 1 && thread_control.is_none() { + // Queued (realtime) signal + let mut ret = MaybeUninit::<RtSigInfo>::uninit(); + let rt_inf = unsafe { + syscall::syscall2( + syscall::SYS_SIGDEQUEUE, + sig_idx as usize, + ret.as_mut_ptr() as usize, + ) + .ok()?; + ret.assume_init() + }; + Some(SiginfoAbi { + si_signo: sig_idx as i32 + 1, + si_errno: 0, + si_code: rt_inf.code, + si_pid: rt_inf.pid as i32, + si_uid: rt_inf.uid as i32, + si_status: 0, + si_value: rt_inf.arg, + si_addr: core::ptr::null_mut(), + }) + } else { + // Idempotent (standard or thread realtime) signal + let info = SenderInfo::from_raw(match thread_control { + Some(ctl) => { + // Only this thread can clear pending bits, so this will always succeed. + let info = ctl.sender_infos[sig_idx as usize].load(Ordering::Acquire); + // TODO: Ordering + ctl.word[sig_group as usize].fetch_and(!(1 << (sig_idx % 32)), Ordering::Release); + info + } + None => { + let info = + PROC_CONTROL_STRUCT.sender_infos[sig_idx as usize].load(Ordering::Acquire); + if PROC_CONTROL_STRUCT + .pending + .fetch_and(!(1 << sig_idx), Ordering::Release) + & (1 << sig_idx) + == 0 + { + // already claimed + return None; + } + info + } + }); + Some(SiginfoAbi { + si_signo: sig_idx as i32 + 1, + si_errno: 0, + si_code: 0, // TODO: SI_USER const? + si_pid: info.pid as i32, + si_uid: info.ruid as i32, + si_status: 0, + si_value: 0, // undefined + si_addr: core::ptr::null_mut(), + }) + } +} diff --git a/src/header/signal/mod.rs b/src/header/signal/mod.rs index 5a4e4f252..05462424a 100644 --- a/src/header/signal/mod.rs +++ b/src/header/signal/mod.rs @@ -379,7 +379,9 @@ pub unsafe extern "C" fn sigtimedwait( sig: *mut siginfo, // https://github.com/mozilla/cbindgen/issues/621 tp: *const timespec, ) -> c_int { - Sys::sigtimedwait(set, sig, tp) + Sys::sigtimedwait(&*set, &mut *sig, &*tp) + .map(|()| 0) + .or_minus_one_errno() } pub const _signal_strings: [&str; 32] = [ diff --git a/src/header/signal/redox.rs b/src/header/signal/redox.rs index 93a698826..b0c7ed33f 100644 --- a/src/header/signal/redox.rs +++ b/src/header/signal/redox.rs @@ -1,6 +1,8 @@ use core::arch::global_asm; -use super::{sigset_t, stack_t}; +use redox_rt::signal::SiginfoAbi; + +use super::{siginfo_t, sigset_t, stack_t}; pub const SIGHUP: usize = 1; pub const SIGINT: usize = 2; @@ -57,6 +59,9 @@ const _: () = { if SS_DISABLE != redox_rt::signal::SS_DISABLE { panic!(); } + if MINSIGSTKSZ != redox_rt::signal::MIN_SIGALTSTACK_SIZE { + panic!(); + } }; // should include both SigStack size, and some extra room for the libc handler @@ -102,3 +107,9 @@ pub extern "C" fn __completely_unused_cbindgen_workaround_fn_ucontext_mcontext( b: *const mcontext_t, ) { } + +impl From<SiginfoAbi> for siginfo_t { + fn from(value: SiginfoAbi) -> Self { + unsafe { core::mem::transmute(value) } + } +} diff --git a/src/platform/linux/signal.rs b/src/platform/linux/signal.rs index 7d0f4990a..bc438a685 100644 --- a/src/platform/linux/signal.rs +++ b/src/platform/linux/signal.rs @@ -161,11 +161,16 @@ impl PalSignal for Sys { } } - unsafe fn sigtimedwait( - set: *const sigset_t, - sig: *mut siginfo_t, - tp: *const timespec, - ) -> c_int { - e(syscall!(RT_SIGTIMEDWAIT, set, sig, tp, NSIG / 8)) as c_int + fn sigtimedwait(set: &sigset_t, sig: &mut siginfo_t, tp: ×pec) -> Result<(), Errno> { + unsafe { + e_raw(syscall!( + RT_SIGTIMEDWAIT, + set as *const _, + sig as *mut _, + tp as *const _, + NSIG / 8 + )) + .map(|_| ()) + } } } diff --git a/src/platform/pal/signal.rs b/src/platform/pal/signal.rs index 690ecdf38..99699f68e 100644 --- a/src/platform/pal/signal.rs +++ b/src/platform/pal/signal.rs @@ -37,8 +37,7 @@ pub trait PalSignal: Pal { oset: Option<&mut sigset_t>, ) -> Result<(), Errno>; - fn sigsuspend(set: &sigset_t) -> Errno; // always fails + fn sigsuspend(mask: &sigset_t) -> Errno; // always fails - unsafe fn sigtimedwait(set: *const sigset_t, sig: *mut siginfo_t, tp: *const timespec) - -> c_int; + fn sigtimedwait(set: &sigset_t, sig: &mut siginfo_t, tp: ×pec) -> Result<(), Errno>; } diff --git a/src/platform/redox/signal.rs b/src/platform/redox/signal.rs index fab3e5641..189662cbc 100644 --- a/src/platform/redox/signal.rs +++ b/src/platform/redox/signal.rs @@ -264,19 +264,24 @@ impl PalSignal for Sys { }) } - fn sigsuspend(set: &sigset_t) -> Errno { - match redox_rt::signal::wait_with_mask(*set) { + fn sigsuspend(mask: &sigset_t) -> Errno { + match redox_rt::signal::await_signal_async(!*mask) { Ok(_) => unreachable!(), Err(err) => err.into(), } } - unsafe fn sigtimedwait( - set: *const sigset_t, - sig: *mut siginfo_t, - tp: *const timespec, - ) -> c_int { - ERRNO.set(ENOSYS); - -1 + fn sigtimedwait( + set: &sigset_t, + info_out: &mut siginfo_t, + timeout: ×pec, + ) -> Result<(), Errno> { + // TODO: deadline-based API + let timeout = syscall::TimeSpec { + tv_sec: timeout.tv_sec, + tv_nsec: timeout.tv_nsec as _, + }; + *info_out = redox_rt::signal::await_signal_sync(*set, &timeout)?.into(); + Ok(()) } } -- GitLab