305 lines
9.4 KiB
Rust
305 lines
9.4 KiB
Rust
//! Runtime utilities for thread handling
|
|
|
|
use core::{
|
|
mem::MaybeUninit,
|
|
ptr,
|
|
sync::atomic::{AtomicU32, Ordering},
|
|
};
|
|
|
|
use abi::{
|
|
error::Error,
|
|
mem::{MappingFlags, MappingSource},
|
|
process::{ThreadEvent, ThreadSpawnOptions},
|
|
};
|
|
use alloc::{boxed::Box, sync::Arc};
|
|
|
|
use crate::{process::thread_local, sync::rwlock::RwLock};
|
|
|
|
use super::{signal, thread_local::TlsImage};
|
|
|
|
pub mod options {
|
|
//! Thread option definitions
|
|
pub use abi::process::thread::*;
|
|
}
|
|
|
|
/// Describes a runtime thread.
|
|
///
|
|
/// `R` generic parameter denotes the thread's return type.
|
|
pub struct Thread<R> {
|
|
id: AtomicU32,
|
|
result: RwLock<Option<R>>,
|
|
}
|
|
|
|
/// Describes a "handle" to some runtime thread. This is what gets returned to the caller
|
|
/// when a thread is spawned and can be used to, e.g., join the created thread later.
|
|
pub struct ThreadHandle<R> {
|
|
#[allow(unused)]
|
|
stack: Option<OwnedStack>,
|
|
thread: Arc<Thread<R>>,
|
|
}
|
|
|
|
struct OwnedStack {
|
|
base: usize,
|
|
size: usize,
|
|
}
|
|
|
|
/// Describes how a thread's signal stack should be set up.
|
|
pub enum ThreadSignalStack {
|
|
/// Thread signal stack will be allocated with the requested size.
|
|
Allocate(usize),
|
|
/// Do not set up a signal stack.
|
|
None,
|
|
}
|
|
|
|
/// Describes how a thread's main stack should be set up.
|
|
pub enum ThreadStack {
|
|
/// Thread stack will be allocated with the requested size.
|
|
Allocate(usize),
|
|
/// A raw value will be used for the stack pointer (unsafe).
|
|
Raw(usize),
|
|
}
|
|
|
|
/// Describes the thread's function that will get entered once the thread starts running.
|
|
///
|
|
/// `A` parameter denotes the argument type that will be passed to the thread.
|
|
pub enum ThreadFunction<A, R> {
|
|
/// A boxed closure will be called.
|
|
Closure(Box<dyn FnOnce(A) -> R + 'static>),
|
|
/// A function pointer will be called.
|
|
Pointer(fn(A) -> R),
|
|
}
|
|
|
|
/// Describes how a thread should be constructed
|
|
pub struct ThreadCreateInfo<A, R> {
|
|
/// See [ThreadSignalStack].
|
|
pub signal_stack: ThreadSignalStack,
|
|
/// See [ThreadStack].
|
|
pub stack: ThreadStack,
|
|
/// See [ThreadFunction].
|
|
pub entry: ThreadFunction<A, R>,
|
|
/// Image used to initialize the thread's TLS
|
|
pub tls_image: Option<&'static TlsImage>,
|
|
}
|
|
|
|
struct ThreadArgument<A, R> {
|
|
entry: Option<(A, ThreadFunction<A, R>)>,
|
|
signal_stack: ThreadSignalStack,
|
|
thread_self: Arc<Thread<R>>,
|
|
set_thread_self: bool,
|
|
tls_image: Option<&'static TlsImage>,
|
|
}
|
|
|
|
// TODO maybe put this under a `thread-self` feature and avoid a TLS allocation?
|
|
#[thread_local]
|
|
static mut SELF: usize = 0;
|
|
|
|
impl<R> Thread<R> {
|
|
/// Creates a new thread with the requested options and passes it an argument.
|
|
pub fn spawn<A>(
|
|
info: ThreadCreateInfo<A, R>,
|
|
argument: A,
|
|
runtime: bool,
|
|
) -> Result<ThreadHandle<R>, Error> {
|
|
let (stack, sp) = match info.stack {
|
|
ThreadStack::Allocate(size) => {
|
|
let (stack, sp) = OwnedStack::allocate(size)?;
|
|
(Some(stack), sp)
|
|
}
|
|
ThreadStack::Raw(_) => todo!(),
|
|
};
|
|
|
|
let thread = Arc::new(Thread::<R> {
|
|
id: AtomicU32::new(0),
|
|
result: RwLock::new(None),
|
|
});
|
|
|
|
let thread_argument = Box::into_raw(Box::new(ThreadArgument {
|
|
entry: Some((argument, info.entry)),
|
|
thread_self: thread.clone(),
|
|
signal_stack: info.signal_stack,
|
|
set_thread_self: runtime,
|
|
tls_image: info.tls_image,
|
|
}))
|
|
.addr();
|
|
|
|
let spawn_options = ThreadSpawnOptions {
|
|
entry: Self::thread_entry::<A>,
|
|
argument: thread_argument,
|
|
stack_top: sp,
|
|
};
|
|
|
|
let id = unsafe { crate::sys::spawn_thread(&spawn_options) }?;
|
|
thread.id.store(id, Ordering::Release);
|
|
|
|
Ok(ThreadHandle { stack, thread })
|
|
}
|
|
|
|
/// Returns this thread's ID.
|
|
pub fn id(&self) -> u32 {
|
|
self.id.load(Ordering::Acquire)
|
|
}
|
|
|
|
/// Sets the current thread name.
|
|
pub fn set_name(name: &str) {
|
|
let mut buffer = [0; 256];
|
|
crate::process::set_thread_option_with::<options::Name>(&mut buffer, &name).ok();
|
|
}
|
|
|
|
/// # Safety
|
|
///
|
|
/// This function has to be called with the same `Self` type as the thread it was created with.
|
|
pub unsafe fn current() -> Arc<Self> {
|
|
let raw: *const Self = ptr::with_exposed_provenance(SELF);
|
|
if raw.is_null() {
|
|
panic!("Thread::SELF == NULL, was spawn() called with runtime=false?");
|
|
}
|
|
// This will "move" the pointer, so an extra strong count increment is required.
|
|
Arc::increment_strong_count(raw);
|
|
Arc::from_raw(raw)
|
|
}
|
|
|
|
extern "C" fn thread_entry<A>(raw: usize) -> ! {
|
|
let raw: *mut ThreadArgument<A, R> = ptr::with_exposed_provenance_mut(raw);
|
|
// This scope will ensure all the stuff is dropped before thread exit is called.
|
|
{
|
|
let mut argument = unsafe { Box::from_raw(raw) };
|
|
|
|
// Setup TLS as soon as possible
|
|
if let Err(err) = thread_local::init_tls(argument.tls_image, true) {
|
|
crate::debug_trace!(Warn, "thread_entry failed: TLS init error: {err:?}");
|
|
// TODO result is uninit
|
|
unsafe { crate::sys::exit_thread() };
|
|
}
|
|
|
|
// TODO there will be a better way to do this, I promise
|
|
while argument.thread_self.id.load(Ordering::Acquire) == 0 {
|
|
core::hint::spin_loop();
|
|
}
|
|
|
|
// Insert compiler fence just in case to prevent it from trying to go into
|
|
// (uninitialized) TLS
|
|
core::sync::atomic::compiler_fence(Ordering::Release);
|
|
|
|
// Setup SELF if needed
|
|
if argument.set_thread_self {
|
|
unsafe {
|
|
SELF = Arc::into_raw(argument.thread_self.clone()).addr();
|
|
debug_assert!(Arc::ptr_eq(&Thread::current(), &argument.thread_self));
|
|
}
|
|
}
|
|
|
|
// Setup signal stack if needed
|
|
let result = match argument.signal_stack {
|
|
ThreadSignalStack::None => Ok(()),
|
|
ThreadSignalStack::Allocate(size) => signal::setup_signal_stack(size),
|
|
};
|
|
if let Err(err) = result {
|
|
panic!("Failed to set up thread's signal stack: {err:?}");
|
|
}
|
|
|
|
// Call the inner function
|
|
let result = match argument.entry.take() {
|
|
Some((arg, ThreadFunction::Closure(f))) => f(arg),
|
|
Some((arg, ThreadFunction::Pointer(f))) => f(arg),
|
|
None => unreachable!(),
|
|
};
|
|
|
|
argument.thread_self.result.write().replace(result);
|
|
}
|
|
|
|
unsafe { crate::sys::exit_thread() };
|
|
}
|
|
}
|
|
|
|
impl<R> ThreadHandle<R> {
|
|
/// Returns the thread ID this handle is related to.
|
|
pub fn id(&self) -> u32 {
|
|
self.thread.id.load(Ordering::Acquire)
|
|
}
|
|
|
|
/// Waits for the thread to finish and returns its result.
|
|
pub fn join(self) -> Result<Option<R>, Error> {
|
|
// TODO prevent threads from attempting to join themselves?
|
|
loop {
|
|
let mut event = MaybeUninit::uninit();
|
|
unsafe { crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire), &mut event) }?;
|
|
let event = unsafe { event.assume_init() };
|
|
if matches!(event, ThreadEvent::Exited) {
|
|
break;
|
|
}
|
|
}
|
|
Ok(self.into_result())
|
|
}
|
|
|
|
/// Waits for the thread to finish, ignoring interrupts.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Will panic if the kernel returns any error besides [Error::Interrupted].
|
|
pub fn join_uninterruptible(self) -> Option<R> {
|
|
loop {
|
|
let mut event = MaybeUninit::uninit();
|
|
match unsafe {
|
|
crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire), &mut event)
|
|
} {
|
|
Ok(_) => {
|
|
let event = unsafe { event.assume_init() };
|
|
if !matches!(event, ThreadEvent::Exited) {
|
|
continue;
|
|
}
|
|
}
|
|
Err(Error::Interrupted) => continue,
|
|
Err(error) => panic!("wait_thread syscall returned error: {error:?}"),
|
|
}
|
|
return self.into_result();
|
|
}
|
|
}
|
|
|
|
fn into_result(self) -> Option<R> {
|
|
self.thread.result.write().take()
|
|
}
|
|
}
|
|
|
|
impl OwnedStack {
|
|
fn allocate(mut size: usize) -> Result<(Self, usize), Error> {
|
|
if size < 0x1000 {
|
|
size = 0x1000;
|
|
}
|
|
size = (size + 0xFFF) & !0xFFF;
|
|
let base = unsafe {
|
|
crate::sys::map_memory(None, size, MappingFlags::WRITE, &MappingSource::Anonymous)
|
|
}?;
|
|
let top = base + size;
|
|
|
|
Ok((Self { base, size }, top))
|
|
}
|
|
}
|
|
|
|
impl Drop for OwnedStack {
|
|
fn drop(&mut self) {
|
|
crate::debug_trace!(
|
|
Debug,
|
|
"Drop OwnedStack {:#x?}",
|
|
self.base..self.base + self.size
|
|
);
|
|
unsafe { crate::sys::unmap_memory(self.base, self.size) }.ok();
|
|
}
|
|
}
|
|
|
|
unsafe impl<R> Send for Thread<R> {}
|
|
unsafe impl<R> Sync for Thread<R> {}
|
|
|
|
/// Sets the `SELF` TLS variable
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Must only be called once by the runtime library during setup. TLS must be initialized prior to
|
|
/// this call.
|
|
pub unsafe fn track_main<R>() {
|
|
let main = Arc::new(Thread::<R> {
|
|
id: AtomicU32::new(0),
|
|
result: RwLock::new(None),
|
|
});
|
|
SELF = Arc::into_raw(main).addr();
|
|
}
|