From 9f59be32ebc9e4e07f1ecea268692901bebb9fe8 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Sun, 30 Jun 2024 12:01:04 +0200 Subject: [PATCH] Add loom test, and move signal ABI to separate mod. --- Cargo.toml | 3 + src/data.rs | 188 +----------------------------------- src/lib.rs | 4 + src/sigabi.rs | 259 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 268 insertions(+), 186 deletions(-) create mode 100644 src/sigabi.rs diff --git a/Cargo.toml b/Cargo.toml index 194976ad3b..8333562831 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,6 @@ std = [] [dependencies] bitflags = "2.4" core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" } + +[target.'cfg(loom)'.dev-dependencies] +loom = "0.7" diff --git a/src/data.rs b/src/data.rs index 6db3c99c9a..063252fb75 100644 --- a/src/data.rs +++ b/src/data.rs @@ -2,10 +2,9 @@ use core::{ mem, ops::{Deref, DerefMut}, slice, - sync::atomic::{AtomicUsize, Ordering}, }; -use crate::flag::{EventFlags, MapFlags, PtraceFlags, SigcontrolFlags}; +use crate::flag::{EventFlags, MapFlags, PtraceFlags}; #[derive(Copy, Clone, Debug, Default)] #[repr(C)] @@ -358,187 +357,4 @@ impl DerefMut for SetSighandlerData { unsafe { slice::from_raw_parts_mut(self as *mut Self as *mut u8, mem::size_of::()) } } } -/// Signal runtime struct for the entire process -#[derive(Debug)] -#[repr(C, align(4096))] -pub struct SigProcControl { - pub pending: AtomicU64, - pub actions: [RawAction; 64], -} -#[derive(Debug)] -#[repr(C, align(16))] -pub struct RawAction { - /// Only two MSBs are interesting for the kernel. If bit 63 is set, signal is ignored. If bit - /// 62 is set and the signal is SIGTSTP/SIGTTIN/SIGTTOU, it's equivalent to the action of - /// SIGSTOP. - pub first: AtomicU64, - /// Completely ignored by the kernel, but exists so userspace can (when 16-byte atomics exist) - /// atomically set both the handler, sigaction flags, and sigaction mask. - pub user_data: AtomicU64, -} - -/// Signal runtime struct for a thread -#[derive(Debug, Default)] -#[repr(C)] -pub struct Sigcontrol { - // composed of [lo "pending" | lo "unmasked", hi "pending" | hi "unmasked"] - pub word: [AtomicU64; 2], - - pub control_flags: SigatomicUsize, - - pub saved_ip: NonatomicUsize, // rip/eip/pc - pub saved_archdep_reg: NonatomicUsize, // rflags/eflags/x0 -} - -impl Sigcontrol { - pub fn currently_pending_unblocked(&self) -> u64 { - let [w0, w1] = self.word.each_ref().map(|w| w.load(Ordering::Relaxed)); - (w0 & 0xffff_ffff) | ((w1 & 0xffff_ffff) << 32) - } -} - -#[derive(Debug, Default)] -#[repr(transparent)] -pub struct SigatomicUsize(AtomicUsize); - -impl SigatomicUsize { - #[inline] - pub fn load(&self, ordering: Ordering) -> usize { - let value = self.0.load(Ordering::Relaxed); - if ordering != Ordering::Relaxed { - core::sync::atomic::compiler_fence(ordering); - } - value - } - #[inline] - pub fn store(&self, value: usize, ordering: Ordering) { - if ordering != Ordering::Relaxed { - core::sync::atomic::compiler_fence(ordering); - } - self.0.store(value, Ordering::Relaxed); - } -} -#[derive(Debug, Default)] -#[repr(transparent)] -pub struct NonatomicUsize(AtomicUsize); - -impl NonatomicUsize { - #[inline] - pub fn get(&self) -> usize { - self.0.load(Ordering::Relaxed) - } - #[inline] - pub fn set(&self, value: usize) { - self.0.store(value, Ordering::Relaxed); - } -} - -pub fn sig_bit(sig: usize) -> u64 { - 1 << (sig - 1) -} -impl SigProcControl { - pub fn signal_will_ign(&self, sig: usize) -> bool { - self.actions[sig - 1].first.load(Ordering::Relaxed) & (1 << 63) != 0 - } - pub fn signal_will_stop(&self, sig: usize) -> bool { - use crate::flag::*; - matches!(sig, SIGTSTP | SIGTTIN | SIGTTOU) && self.actions[sig - 1].first.load(Ordering::Relaxed) & (1 << 62) != 0 - } -} - -#[cfg(not(target_arch = "x86"))] -pub use core::sync::atomic::AtomicU64; - -#[cfg(target_arch = "x86")] -pub use self::atomic::AtomicU64; - -#[cfg(target_arch = "x86")] -mod atomic { - use core::{cell::UnsafeCell, sync::atomic::Ordering}; - - #[derive(Debug, Default)] - pub struct AtomicU64(UnsafeCell); - - unsafe impl Send for AtomicU64 {} - unsafe impl Sync for AtomicU64 {} - - impl AtomicU64 { - pub const fn new(inner: u64) -> Self { - Self(UnsafeCell::new(inner)) - } - pub fn compare_exchange( - &self, - old: u64, - new: u64, - _success: Ordering, - _failure: Ordering, - ) -> Result { - let old_hi = (old >> 32) as u32; - let old_lo = old as u32; - let new_hi = (new >> 32) as u32; - let new_lo = new as u32; - let mut out_hi; - let mut out_lo; - - unsafe { - core::arch::asm!("lock cmpxchg8b [{}]", in(reg) self.0.get(), inout("edx") old_hi => out_hi, inout("eax") old_lo => out_lo, in("ecx") new_hi, in("ebx") new_lo); - } - - if old_hi == out_hi && old_lo == out_lo { - Ok(old) - } else { - Ok(u64::from(out_lo) | (u64::from(out_hi) << 32)) - } - } - pub fn load(&self, ordering: Ordering) -> u64 { - match self.compare_exchange(0, 0, ordering, ordering) { - Ok(new) => new, - Err(new) => new, - } - } - pub fn store(&self, new: u64, ordering: Ordering) { - let mut old = 0; - - loop { - match self.compare_exchange(old, new, ordering, Ordering::Relaxed) { - Ok(_) => break, - Err(new) => { - old = new; - core::hint::spin_loop(); - } - } - } - } - pub fn fetch_update( - &self, - set_order: Ordering, - fetch_order: Ordering, - mut f: impl FnMut(u64) -> Option, - ) -> Result { - let mut old = self.load(fetch_order); - - loop { - let new = f(old).ok_or(old)?; - match self.compare_exchange(old, new, set_order, Ordering::Relaxed) { - Ok(_) => return Ok(new), - Err(changed) => { - old = changed; - core::hint::spin_loop(); - } - } - } - } - pub fn fetch_or(&self, bits: u64, order: Ordering) -> u64 { - self.fetch_update(order, Ordering::Relaxed, |b| Some(b | bits)) - .unwrap() - } - pub fn fetch_and(&self, bits: u64, order: Ordering) -> u64 { - self.fetch_update(order, Ordering::Relaxed, |b| Some(b & bits)) - .unwrap() - } - pub fn fetch_add(&self, term: u64, order: Ordering) -> u64 { - self.fetch_update(order, Ordering::Relaxed, |b| Some(b.wrapping_add(term))) - .unwrap() - } - } -} +pub use crate::sigabi::*; diff --git a/src/lib.rs b/src/lib.rs index 112fb1dab3..4109c98c4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(any(feature = "std", test)), no_std)] +#![allow(unexpected_cfgs)] // why does this even exist? #[cfg(test)] extern crate core; @@ -53,6 +54,9 @@ pub mod io; /// Call numbers used by each system call pub mod number; +/// ABI for shared memory based signals +pub mod sigabi; + /// V2 scheme format pub mod schemev2; diff --git a/src/sigabi.rs b/src/sigabi.rs new file mode 100644 index 0000000000..28ac2f2a57 --- /dev/null +++ b/src/sigabi.rs @@ -0,0 +1,259 @@ +use core::sync::atomic::{AtomicUsize, Ordering}; + +/// Signal runtime struct for the entire process +#[derive(Debug)] +#[repr(C, align(4096))] +pub struct SigProcControl { + pub pending: AtomicU64, + pub actions: [RawAction; 64], +} +#[derive(Debug)] +#[repr(C, align(16))] +pub struct RawAction { + /// Only two MSBs are interesting for the kernel. If bit 63 is set, signal is ignored. If bit + /// 62 is set and the signal is SIGTSTP/SIGTTIN/SIGTTOU, it's equivalent to the action of + /// SIGSTOP. + pub first: AtomicU64, + /// Completely ignored by the kernel, but exists so userspace can (when 16-byte atomics exist) + /// atomically set both the handler, sigaction flags, and sigaction mask. + pub user_data: AtomicU64, +} + +/// Signal runtime struct for a thread +#[derive(Debug, Default)] +#[repr(C)] +pub struct Sigcontrol { + // composed of [lo "pending" | lo "unmasked", hi "pending" | hi "unmasked"] + pub word: [AtomicU64; 2], + + pub control_flags: SigatomicUsize, + + pub saved_ip: NonatomicUsize, // rip/eip/pc + pub saved_archdep_reg: NonatomicUsize, // rflags/eflags/x0 +} + +impl Sigcontrol { + pub fn currently_pending_unblocked(&self) -> u64 { + let [w0, w1] = self.word.each_ref().map(|w| w.load(Ordering::Relaxed)).map(|w| (w & 0xffff_ffff) & (w >> 32)); + //core::sync::atomic::fence(Ordering::Acquire); + w0 | (w1 << 32) + } + pub fn set_allowset(&self, new_allowset: u64) -> u64 { + //core::sync::atomic::fence(Ordering::Release); + let [w0, w1] = self.word.each_ref().map(|w| w.load(Ordering::Relaxed)); + let old_a0 = w0 & 0xffff_ffff_0000_0000; + let old_a1 = w1 & 0xffff_ffff_0000_0000; + let new_a0 = (new_allowset & 0xffff_ffff) << 32; + let new_a1 = new_allowset & 0xffff_ffff_0000_0000; + + let prev_w0 = self.word[0].fetch_add(new_a0.wrapping_sub(old_a0), Ordering::Relaxed); + let prev_w1 = self.word[0].fetch_add(new_a1.wrapping_sub(old_a1), Ordering::Relaxed); + //core::sync::atomic::fence(Ordering::Acquire); + let up0 = prev_w0 & (prev_w0 >> 32); + let up1 = prev_w1 & (prev_w1 >> 32); + + up0 | (up1 << 32) + } +} + +#[derive(Debug, Default)] +#[repr(transparent)] +pub struct SigatomicUsize(AtomicUsize); + +impl SigatomicUsize { + #[inline] + pub fn load(&self, ordering: Ordering) -> usize { + let value = self.0.load(Ordering::Relaxed); + if ordering != Ordering::Relaxed { + core::sync::atomic::compiler_fence(ordering); + } + value + } + #[inline] + pub fn store(&self, value: usize, ordering: Ordering) { + if ordering != Ordering::Relaxed { + core::sync::atomic::compiler_fence(ordering); + } + self.0.store(value, Ordering::Relaxed); + } +} +#[derive(Debug, Default)] +#[repr(transparent)] +pub struct NonatomicUsize(AtomicUsize); + +impl NonatomicUsize { + #[inline] + pub fn get(&self) -> usize { + self.0.load(Ordering::Relaxed) + } + #[inline] + pub fn set(&self, value: usize) { + self.0.store(value, Ordering::Relaxed); + } +} + +pub fn sig_bit(sig: usize) -> u64 { + 1 << (sig - 1) +} +impl SigProcControl { + pub fn signal_will_ign(&self, sig: usize) -> bool { + self.actions[sig - 1].first.load(Ordering::Relaxed) & (1 << 63) != 0 + } + pub fn signal_will_stop(&self, sig: usize) -> bool { + use crate::flag::*; + matches!(sig, SIGTSTP | SIGTTIN | SIGTTOU) && self.actions[sig - 1].first.load(Ordering::Relaxed) & (1 << 62) != 0 + } +} + +#[cfg(not(target_arch = "x86"))] +pub use core::sync::atomic::AtomicU64; + +#[cfg(target_arch = "x86")] +pub use self::atomic::AtomicU64; + +#[cfg(target_arch = "x86")] +mod atomic { + use core::{cell::UnsafeCell, sync::atomic::Ordering}; + + #[derive(Debug, Default)] + pub struct AtomicU64(UnsafeCell); + + unsafe impl Send for AtomicU64 {} + unsafe impl Sync for AtomicU64 {} + + impl AtomicU64 { + pub const fn new(inner: u64) -> Self { + Self(UnsafeCell::new(inner)) + } + pub fn compare_exchange( + &self, + old: u64, + new: u64, + _success: Ordering, + _failure: Ordering, + ) -> Result { + let old_hi = (old >> 32) as u32; + let old_lo = old as u32; + let new_hi = (new >> 32) as u32; + let new_lo = new as u32; + let mut out_hi; + let mut out_lo; + + unsafe { + core::arch::asm!("lock cmpxchg8b [{}]", in(reg) self.0.get(), inout("edx") old_hi => out_hi, inout("eax") old_lo => out_lo, in("ecx") new_hi, in("ebx") new_lo); + } + + if old_hi == out_hi && old_lo == out_lo { + Ok(old) + } else { + Ok(u64::from(out_lo) | (u64::from(out_hi) << 32)) + } + } + pub fn load(&self, ordering: Ordering) -> u64 { + match self.compare_exchange(0, 0, ordering, ordering) { + Ok(new) => new, + Err(new) => new, + } + } + pub fn store(&self, new: u64, ordering: Ordering) { + let mut old = 0; + + loop { + match self.compare_exchange(old, new, ordering, Ordering::Relaxed) { + Ok(_) => break, + Err(new) => { + old = new; + core::hint::spin_loop(); + } + } + } + } + pub fn fetch_update( + &self, + set_order: Ordering, + fetch_order: Ordering, + mut f: impl FnMut(u64) -> Option, + ) -> Result { + let mut old = self.load(fetch_order); + + loop { + let new = f(old).ok_or(old)?; + match self.compare_exchange(old, new, set_order, Ordering::Relaxed) { + Ok(_) => return Ok(new), + Err(changed) => { + old = changed; + core::hint::spin_loop(); + } + } + } + } + pub fn fetch_or(&self, bits: u64, order: Ordering) -> u64 { + self.fetch_update(order, Ordering::Relaxed, |b| Some(b | bits)) + .unwrap() + } + pub fn fetch_and(&self, bits: u64, order: Ordering) -> u64 { + self.fetch_update(order, Ordering::Relaxed, |b| Some(b & bits)) + .unwrap() + } + pub fn fetch_add(&self, term: u64, order: Ordering) -> u64 { + self.fetch_update(order, Ordering::Relaxed, |b| Some(b.wrapping_add(term))) + .unwrap() + } + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + use std::sync::atomic::Ordering; + + #[cfg(not(loom))] + use std::{thread, sync::Mutex}; + #[cfg(not(loom))] + fn model(f: impl FnOnce()) { f() } + + #[cfg(loom)] + use loom::{model, thread, sync::Mutex}; + + use crate::Sigcontrol; + + #[test] + fn singlethread_mask() { + model(|| { + let tctl = Arc::new(Sigcontrol::default()); + let mutex = Arc::new(Mutex::new(())); + + let thread = { + let tctl = Arc::clone(&tctl); + let mutex = Arc::clone(&mutex); + + thread::spawn(move || { + tctl.set_allowset(!0); + { + let _g = mutex.lock(); + if tctl.currently_pending_unblocked() == 0 { + drop(_g); + thread::park(); + } + } + }) + }; + + for sig in 1..=64 { + let _g = mutex.lock(); + + let idx = sig - 1; + let bit = 1 << (idx % 32); + + tctl.word[idx / 32].fetch_or(bit, Ordering::Relaxed); + let w = tctl.word[idx / 32].load(Ordering::Relaxed); + + if w & (w >> 32) != 0 { + thread.thread().unpark(); + } + } + + thread.join().unwrap(); + }); + } +}