Implement sigtimedwait on Redox.
This commit is contained in:
+135
-17
@@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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] = [
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(|_| ())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user