diff --git a/src/action.rs b/src/action.rs index a538cd2..20b6395 100644 --- a/src/action.rs +++ b/src/action.rs @@ -11,7 +11,7 @@ use linux_raw_sys::{ use crate::SignalSet; -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DefaultSignalAction { /// Terminate the process. Terminate, @@ -32,7 +32,7 @@ pub enum DefaultSignalAction { /// Signal action that should be properly handled by the OS. /// /// See [`SignalManager::check_signals`] for details. -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SignalOSAction { /// Terminate the process. Terminate, diff --git a/tests/api_thread.rs b/tests/api_thread.rs index 2d2a841..0693378 100644 --- a/tests/api_thread.rs +++ b/tests/api_thread.rs @@ -1,178 +1,119 @@ -#![feature(maybe_uninit_write_slice)] - -use std::{ - mem::{MaybeUninit, zeroed}, - sync::{Arc, LazyLock, Mutex, MutexGuard}, -}; - use axcpu::uspace::UserContext; -use extern_trait::extern_trait; -use kspin::SpinNoIrq; -use starry_signal::{ - SignalDisposition, SignalInfo, SignalOSAction, SignalSet, Signo, - api::{ProcessSignalManager, SignalActions, ThreadSignalManager}, -}; -use starry_vm::{VmIo, VmResult}; - -struct TestEnv { - actions: Arc>, - proc: Arc, - thr: Arc, -} - -impl TestEnv { - fn new() -> Self { - let actions = Arc::new(SpinNoIrq::new(SignalActions::default())); - let proc = Arc::new(ProcessSignalManager::new(actions.clone(), 0)); - let thr = ThreadSignalManager::new(7, proc.clone()); - Self { actions, proc, thr } - } -} +use starry_signal::{SignalDisposition, SignalInfo, SignalOSAction, SignalSet, Signo}; -static POOL: LazyLock>> = LazyLock::new(|| { - let size = 0x0100_0000; // 1 MiB - Mutex::new(vec![0; size].into_boxed_slice()) -}); - -struct Vm(MutexGuard<'static, Box<[u8]>>); - -#[extern_trait] -unsafe impl VmIo for Vm { - fn new() -> Self { - let pool = POOL.lock().unwrap(); - Vm(pool) - } - - fn read(&mut self, start: usize, buf: &mut [MaybeUninit]) -> VmResult { - let base = self.0.as_ptr() as usize; - let offset = start - base; - let slice = &self.0[offset..offset + buf.len()]; - buf.write_copy_of_slice(slice); - Ok(()) - } - - fn write(&mut self, start: usize, buf: &[u8]) -> VmResult { - let base = self.0.as_ptr() as usize; - let offset = start - base; - let slice = &mut self.0[offset..offset + buf.len()]; - slice.copy_from_slice(buf); - Ok(()) - } -} +mod common; +use common::*; #[test] -fn block_ignore_send_signal() { - let env = TestEnv::new(); - let actions = env.actions.clone(); - let sig = SignalInfo::new_user(Signo::SIGINT, 0, 1); - assert!(env.thr.send_signal(sig.clone())); +fn dequeue_signal() { + let (proc, thr) = new_test_env(); - actions.lock()[Signo::SIGINT].disposition = SignalDisposition::Ignore; - let proc_ignore = Arc::new(ProcessSignalManager::new(actions.clone(), 0)); - let thr_ignore = ThreadSignalManager::new(7, proc_ignore.clone()); - assert!(!thr_ignore.send_signal(sig.clone())); + let sig1 = SignalInfo::new_user(Signo::SIGINT, 9, 9); + assert!(thr.send_signal(sig1)); - let mut set = SignalSet::default(); - set.add(Signo::SIGINT); - env.thr.set_blocked(set); - assert!(!env.thr.send_signal(sig.clone())); - assert!(env.thr.pending().has(Signo::SIGINT)); - assert!(env.thr.signal_blocked(Signo::SIGINT)); + let sig2 = SignalInfo::new_user(Signo::SIGTERM, 9, 9); + assert_eq!(proc.send_signal(sig2), Some(TID)); - let empty = SignalSet::default(); - env.thr.set_blocked(empty); - assert!(!env.thr.signal_blocked(Signo::SIGINT)); + let mask = !SignalSet::default(); + assert_eq!(thr.dequeue_signal(&mask).unwrap().signo(), Signo::SIGINT); + assert_eq!(thr.dequeue_signal(&mask).unwrap().signo(), Signo::SIGTERM); + assert!(thr.dequeue_signal(&mask).is_none()); } #[test] fn handle_signal() { + let (proc, thr) = new_test_env(); + + let signo = Signo::SIGTERM; + let sig = SignalInfo::new_user(signo, 9, 9); + unsafe extern "C" fn test_handler(_: i32) {} - let env = TestEnv::new(); - let actions = env.actions.clone(); - actions.lock()[Signo::SIGTERM].disposition = SignalDisposition::Handler(test_handler); - let sig = SignalInfo::new_user(Signo::SIGTERM, 9, 9); - - let mut uctx: UserContext = unsafe { zeroed() }; - let initial_sp = { - let pool = POOL.lock().unwrap(); - pool.as_ptr() as usize + pool.len() - }; - uctx.set_sp(initial_sp); - - let restore_blocked = env.thr.blocked(); - let action = env.actions.lock()[sig.signo()].clone(); - let result = env - .thr - .handle_signal(&mut uctx, restore_blocked, &sig, &action); - - assert!(matches!(result, Some(SignalOSAction::Handler))); + proc.actions.lock()[signo].disposition = SignalDisposition::Handler(test_handler); + + let initial = UserContext::new(0, initial_sp().into(), 0); + + let mut uctx = initial; + let restore_blocked = thr.blocked(); + let action = proc.actions.lock()[signo].clone(); + let result = thr.handle_signal(&mut uctx, restore_blocked, &sig, &action); + + assert_eq!(result, Some(SignalOSAction::Handler)); assert_eq!(uctx.ip(), test_handler as *const () as usize); - assert!(uctx.sp() < initial_sp); - assert_eq!(uctx.arg0(), Signo::SIGTERM as usize); + assert!(uctx.sp() < initial.sp()); + assert_eq!(uctx.arg0(), signo as usize); } #[test] -fn dequeue_signal() { - let env = TestEnv::new(); - let sig1 = SignalInfo::new_user(Signo::SIGINT, 9, 9); - let sig2 = SignalInfo::new_user(Signo::SIGTERM, 9, 9); - let mask = SignalSet::default(); - let allowed_mask = !mask; - assert!(env.thr.send_signal(sig1.clone())); - assert_eq!(env.proc.send_signal(sig2), Some(7)); - assert_eq!( - env.thr.dequeue_signal(&allowed_mask).unwrap().signo(), - Signo::SIGINT - ); +fn block_ignore_send_signal() { + let (proc, thr) = new_test_env(); + + let signo = Signo::SIGINT; + let sig = SignalInfo::new_user(signo, 0, 1); + assert!(thr.send_signal(sig.clone())); assert_eq!( - env.thr.dequeue_signal(&allowed_mask).unwrap().signo(), - Signo::SIGTERM + thr.dequeue_signal(&!SignalSet::default()).unwrap().signo(), + sig.signo() ); - assert!(env.thr.dequeue_signal(&allowed_mask).is_none()); + + proc.actions.lock()[signo].disposition = SignalDisposition::Ignore; + assert!(!thr.send_signal(sig.clone())); + assert!(!thr.pending().has(signo)); + + let mut set = SignalSet::default(); + set.add(signo); + thr.set_blocked(set); + assert!(thr.signal_blocked(signo)); + assert!(!thr.send_signal(sig.clone())); + assert!(!thr.pending().has(signo)); + + proc.actions.lock()[signo].disposition = SignalDisposition::Default; + assert!(!thr.send_signal(sig.clone())); + assert!(thr.pending().has(signo)); + + let empty = SignalSet::default(); + thr.set_blocked(empty); + assert!(!thr.signal_blocked(signo)); } #[test] fn check_signals() { - let env = TestEnv::new(); - let mut uctx: UserContext = unsafe { zeroed() }; - uctx.set_sp(0x8000_0000); + let (proc, thr) = new_test_env(); + + let mut uctx = UserContext::new(0, 0.into(), 0); - let sig = SignalInfo::new_user(Signo::SIGTERM, 0, 1); - assert_eq!(env.proc.send_signal(sig.clone()), Some(7)); + let signo = Signo::SIGTERM; + let sig = SignalInfo::new_user(signo, 0, 1); - let (si, _os_action) = env.thr.check_signals(&mut uctx, None).unwrap(); - assert_eq!(si.signo(), Signo::SIGTERM); + assert_eq!(proc.send_signal(sig.clone()), Some(TID)); + let (si, _os_action) = thr.check_signals(&mut uctx, None).unwrap(); + assert_eq!(si.signo(), signo); - assert!(env.thr.send_signal(sig.clone())); - let (si, _os_action) = env.thr.check_signals(&mut uctx, None).unwrap(); - assert_eq!(si.signo(), Signo::SIGTERM); + assert!(thr.send_signal(sig.clone())); + let (si, _os_action) = thr.check_signals(&mut uctx, None).unwrap(); + assert_eq!(si.signo(), signo); } #[test] fn restore() { + let (proc, thr) = new_test_env(); + + let signo = Signo::SIGTERM; + let sig = SignalInfo::new_user(signo, 0, 1); + unsafe extern "C" fn test_handler(_: i32) {} - let env = TestEnv::new(); - let sig = SignalInfo::new_user(Signo::SIGTERM, 0, 1); - env.actions.lock()[Signo::SIGTERM].disposition = SignalDisposition::Handler(test_handler); - - let sp = { - let pool = POOL.lock().unwrap(); - pool.as_ptr() as usize + pool.len() - }; - let mut initial: UserContext = unsafe { zeroed() }; - initial.set_sp(sp); - initial.set_ip(0x219); - let mut uctx_user = initial.clone(); - - let restore_blocked = env.thr.blocked(); - let action = env.actions.lock()[sig.signo()].clone(); - env.thr - .handle_signal(&mut uctx_user, restore_blocked, &sig, &action); - - let new_sp = uctx_user.sp() + 8; - uctx_user.set_sp(new_sp); - env.thr.restore(&mut uctx_user); - - assert_eq!(uctx_user.ip(), initial.ip()); - assert_eq!(uctx_user.sp(), initial.sp()); + proc.actions.lock()[signo].disposition = SignalDisposition::Handler(test_handler); + + let initial = UserContext::new(0x219, initial_sp().into(), 0); + + let mut uctx = initial; + let restore_blocked = thr.blocked(); + let action = proc.actions.lock()[sig.signo()].clone(); + thr.handle_signal(&mut uctx, restore_blocked, &sig, &action); + + let new_sp = uctx.sp() + 8; + uctx.set_sp(new_sp); + thr.restore(&mut uctx); + + assert_eq!(uctx.ip(), initial.ip()); + assert_eq!(uctx.sp(), initial.sp()); } diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..211d1c3 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,62 @@ +use std::{ + mem::MaybeUninit, + sync::{Arc, LazyLock, Mutex, MutexGuard}, +}; + +use extern_trait::extern_trait; +use kspin::SpinNoIrq; +use starry_signal::api::{ProcessSignalManager, SignalActions, ThreadSignalManager}; +use starry_vm::{VmError, VmIo, VmResult}; + +static POOL: LazyLock>> = LazyLock::new(|| { + let size = 0x0100_0000; // 16 MiB + Mutex::new(vec![0; size].into_boxed_slice()) +}); + +pub fn initial_sp() -> usize { + let pool = POOL.lock().unwrap(); + pool.as_ptr() as usize + pool.len() +} + +struct Vm(MutexGuard<'static, Box<[u8]>>); + +#[extern_trait] +unsafe impl VmIo for Vm { + fn new() -> Self { + let pool = POOL.lock().unwrap(); + Vm(pool) + } + + fn read(&mut self, start: usize, buf: &mut [MaybeUninit]) -> VmResult { + let base = self.0.as_ptr() as usize; + let offset = start.checked_sub(base).ok_or(VmError::BadAddress)?; + if offset.checked_add(buf.len()).ok_or(VmError::BadAddress)? > self.0.len() { + return Err(VmError::BadAddress); + } + let slice = &self.0[offset..offset + buf.len()]; + buf.write_copy_of_slice(slice); + Ok(()) + } + + fn write(&mut self, start: usize, buf: &[u8]) -> VmResult { + let base = self.0.as_ptr() as usize; + let offset = start.checked_sub(base).ok_or(VmError::BadAddress)?; + if offset.checked_add(buf.len()).ok_or(VmError::BadAddress)? > self.0.len() { + return Err(VmError::BadAddress); + } + let slice = &mut self.0[offset..offset + buf.len()]; + slice.copy_from_slice(buf); + Ok(()) + } +} + +pub const TID: u32 = 7; + +pub fn new_test_env() -> (Arc, Arc) { + let proc = Arc::new(ProcessSignalManager::new( + Arc::new(SpinNoIrq::new(SignalActions::default())), + 0, + )); + let thr = ThreadSignalManager::new(TID, proc.clone()); + (proc, thr) +} diff --git a/tests/concurrent.rs b/tests/concurrent.rs new file mode 100644 index 0000000..379a818 --- /dev/null +++ b/tests/concurrent.rs @@ -0,0 +1,127 @@ +use std::{ + thread, + time::{Duration, Instant}, +}; + +use axcpu::uspace::UserContext; +use starry_signal::{SignalDisposition, SignalInfo, SignalOSAction, SignalSet, Signo}; + +mod common; +use common::*; + +fn wait_until(mut check: F) -> bool +where + F: FnMut() -> bool, +{ + const TIMEOUT: Duration = Duration::from_millis(100); + + let start = Instant::now(); + while start.elapsed() < TIMEOUT { + if check() { + return true; + } + thread::sleep(Duration::from_millis(1)); + } + false +} + +#[test] +fn concurrent_send_signal() { + let (proc, thr) = new_test_env(); + + let signo = Signo::SIGTERM; + let sig = SignalInfo::new_user(signo, 9, 9); + + thread::spawn({ + let thr = thr.clone(); + move || { + thread::sleep(Duration::from_millis(10)); + let _ = thr.send_signal(sig); + } + }); + + assert!(wait_until( + || thr.pending().has(signo) || proc.pending().has(signo) + )); +} + +#[test] +fn concurrent_blocked() { + let (_proc, thr) = new_test_env(); + + let signo = Signo::SIGTERM; + let sig = SignalInfo::new_user(signo, 9, 9); + + let mut blocked = SignalSet::default(); + blocked.add(signo); + let prev = thr.set_blocked(blocked); + assert!(!prev.has(signo)); + assert!(thr.signal_blocked(signo)); + + thread::spawn({ + let thr = thr.clone(); + move || { + thread::sleep(Duration::from_millis(10)); + let _ = thr.send_signal(sig); + } + }); + + assert!(wait_until(|| thr.pending().has(signo))); + + thr.set_blocked(SignalSet::default()); + assert!(!thr.signal_blocked(signo)); + + let mut uctx = UserContext::new(0, 0.into(), 0); + let res = wait_until(|| { + if let Some((si, _)) = thr.check_signals(&mut uctx, None) { + assert_eq!(si.signo(), signo); + true + } else { + false + } + }); + assert!(res); +} + +#[test] +fn concurrent_check_signals() { + let (proc, thr) = new_test_env(); + + unsafe extern "C" fn test_handler(_: i32) {} + proc.actions.lock()[Signo::SIGTERM].disposition = SignalDisposition::Handler(test_handler); + + let mut uctx = UserContext::new(0, initial_sp().into(), 0); + + let first = SignalInfo::new_user(Signo::SIGTERM, 9, 9); + assert!(thr.send_signal(first.clone())); + + let (si, action) = thr.check_signals(&mut uctx, None).unwrap(); + assert_eq!(si.signo(), Signo::SIGTERM); + assert_eq!(action, SignalOSAction::Handler); + assert!(thr.signal_blocked(Signo::SIGTERM)); + + thread::spawn({ + let thr = thr.clone(); + move || { + let _ = thr.send_signal(SignalInfo::new_user(Signo::SIGINT, 2, 2)); + let _ = thr.send_signal(SignalInfo::new_user(Signo::SIGTERM, 3, 3)); + } + }); + + assert!(wait_until(|| thr.pending().has(Signo::SIGTERM))); + assert!(wait_until(|| thr.pending().has(Signo::SIGINT))); + + let new_sp = uctx.sp() + 8; + uctx.set_sp(new_sp); + thr.restore(&mut uctx); + + assert!(!thr.signal_blocked(Signo::SIGTERM)); + + let mut delivered = SignalSet::default(); + assert!(wait_until(|| { + if let Some((sig, _)) = thr.check_signals(&mut uctx, None) { + delivered.add(sig.signo()); + } + delivered.has(Signo::SIGINT) && delivered.has(Signo::SIGTERM) + })); +} diff --git a/tests/multithread.rs b/tests/multithread.rs deleted file mode 100644 index e894152..0000000 --- a/tests/multithread.rs +++ /dev/null @@ -1,212 +0,0 @@ -#![feature(maybe_uninit_write_slice)] - -use std::{ - mem::{MaybeUninit, zeroed}, - sync::{ - Arc, LazyLock, Mutex, MutexGuard, - atomic::{AtomicUsize, Ordering}, - }, - thread, - time::Duration, -}; - -use axcpu::uspace::UserContext; -use extern_trait::extern_trait; -use kspin::SpinNoIrq; -use starry_signal::{ - SignalDisposition, SignalInfo, SignalOSAction, SignalSet, Signo, - api::{ProcessSignalManager, SignalActions, ThreadSignalManager}, -}; -use starry_vm::{VmError, VmIo, VmResult}; - -fn wait_until(timeout: Duration, mut check: F) -> bool -where - F: FnMut() -> bool, -{ - let start = std::time::Instant::now(); - while start.elapsed() < timeout { - if check() { - return true; - } - thread::sleep(Duration::from_millis(1)); - } - false -} - -struct TestEnv { - actions: Arc>, - proc: Arc, - thr: Arc, -} - -impl TestEnv { - fn new() -> Self { - let actions = Arc::new(SpinNoIrq::new(SignalActions::default())); - let proc = Arc::new(ProcessSignalManager::new(actions.clone(), 0)); - let thr = ThreadSignalManager::new(7, proc.clone()); - Self { actions, proc, thr } - } -} - -static POOL: LazyLock>> = LazyLock::new(|| { - let size = 0x0100_0000; // 1 MiB - Mutex::new(vec![0; size].into_boxed_slice()) -}); - -struct Vm(MutexGuard<'static, Box<[u8]>>); - -#[extern_trait] -unsafe impl VmIo for Vm { - fn new() -> Self { - let pool = POOL.lock().unwrap(); - Vm(pool) - } - - fn read(&mut self, start: usize, buf: &mut [MaybeUninit]) -> VmResult { - let base = self.0.as_ptr() as usize; - if start < base { - return Err(VmError::BadAddress); - } - let offset = start - base; - if offset + buf.len() > self.0.len() { - return Err(VmError::BadAddress); - } - let slice = &self.0[offset..offset + buf.len()]; - buf.write_copy_of_slice(slice); - Ok(()) - } - - fn write(&mut self, start: usize, buf: &[u8]) -> VmResult { - let base = self.0.as_ptr() as usize; - if start < base { - return Err(VmError::BadAddress); - } - let offset = start - base; - if offset + buf.len() > self.0.len() { - return Err(VmError::BadAddress); - } - let slice = &mut self.0[offset..offset + buf.len()]; - slice.copy_from_slice(buf); - Ok(()) - } -} - -#[test] -fn thread_send_signal() { - let env = TestEnv::new(); - let sig = SignalInfo::new_user(Signo::SIGTERM, 9, 9); - - let thr = env.thr.clone(); - thread::spawn(move || { - thread::sleep(Duration::from_millis(10)); - let _ = thr.send_signal(sig); - }); - - let res = wait_until(Duration::from_millis(100), || { - env.thr.pending().has(Signo::SIGTERM) || env.proc.pending().has(Signo::SIGTERM) - }); - - assert!(res); -} - -#[test] -fn thread_blocked() { - let env = TestEnv::new(); - let sig = SignalInfo::new_user(Signo::SIGTERM, 9, 9); - - let mut blocked = SignalSet::default(); - blocked.add(Signo::SIGTERM); - let prev = env.thr.set_blocked(blocked); - assert!(!prev.has(Signo::SIGTERM)); - assert!(env.thr.signal_blocked(Signo::SIGTERM)); - - let thr = env.thr.clone(); - thread::spawn(move || { - thread::sleep(Duration::from_millis(10)); - let _ = thr.send_signal(sig); - }); - - let pending_res = wait_until(Duration::from_millis(100), || { - env.thr.pending().has(Signo::SIGTERM) - }); - assert!(pending_res); - - env.thr.set_blocked(SignalSet::default()); - assert!(!env.thr.signal_blocked(Signo::SIGTERM)); - - let uctx = Arc::new(SpinNoIrq::new(unsafe { zeroed::() })); - uctx.lock().set_sp(0x8000_0000); - let res = wait_until(Duration::from_millis(100), || { - let mut uctx_ref = uctx.lock().clone(); - if let Some((si, _)) = env.thr.check_signals(&mut uctx_ref, None) { - assert_eq!(si.signo(), Signo::SIGTERM); - true - } else { - false - } - }); - assert!(res); -} - -#[test] -fn thread_handler() { - unsafe extern "C" fn test_handler(_: i32) {} - - let env = TestEnv::new(); - env.actions.lock()[Signo::SIGTERM].disposition = SignalDisposition::Handler(test_handler); - - let uctx = Arc::new(SpinNoIrq::new(unsafe { zeroed::() })); - let initial_sp = { - let pool = POOL.lock().unwrap(); - pool.as_ptr() as usize + pool.len() - }; - uctx.lock().set_sp(initial_sp); - - let first = SignalInfo::new_user(Signo::SIGTERM, 9, 9); - assert!(env.thr.send_signal(first.clone())); - let (si, action) = { - let mut guard = uctx.lock(); - env.thr.check_signals(&mut guard, None).unwrap() - }; - assert_eq!(si.signo(), Signo::SIGTERM); - assert!(matches!(action, SignalOSAction::Handler)); - assert!(env.thr.signal_blocked(Signo::SIGTERM)); - - let thr = env.thr.clone(); - thread::spawn(move || { - let _ = thr.send_signal(SignalInfo::new_user(Signo::SIGINT, 2, 2)); - let _ = thr.send_signal(SignalInfo::new_user(Signo::SIGTERM, 3, 3)); - }); - - let pending_res = wait_until(Duration::from_millis(200), || { - env.thr.pending().has(Signo::SIGTERM) - }); - assert!(pending_res); - - let pending_res = wait_until(Duration::from_millis(200), || { - env.thr.pending().has(Signo::SIGINT) - }); - assert!(pending_res); - - let frame_sp = uctx.lock().sp() + 8; - { - let mut guard = uctx.lock(); - guard.set_sp(frame_sp); - env.thr.restore(&mut guard); - } - assert!(!env.thr.signal_blocked(Signo::SIGTERM)); - - let delivered = Arc::new(AtomicUsize::new(0)); - let delivered_result = wait_until(Duration::from_millis(200), || { - let thr = env.thr.clone(); - let delivered_ref = delivered.clone(); - let uctx_ref = uctx.clone(); - if let Some((sig, _)) = thr.check_signals(&mut *uctx_ref.lock(), None) { - assert!(matches!(sig.signo(), Signo::SIGINT | Signo::SIGTERM)); - delivered_ref.fetch_add(1, Ordering::SeqCst); - } - delivered_ref.load(Ordering::SeqCst) >= 2 - }); - - assert!(delivered_result); -}