WIP: Expand native pthreads implementation.

This commit is contained in:
4lDO2
2023-03-05 15:52:48 +01:00
parent 5b56d6305d
commit 558d43082c
21 changed files with 1123 additions and 136 deletions
+1
View File
@@ -23,6 +23,7 @@ pub mod netinet_in;
pub mod netinet_ip;
pub mod netinet_tcp;
pub mod poll;
pub mod pthread;
pub mod pwd;
pub mod regex;
pub mod sched;
+147
View File
@@ -0,0 +1,147 @@
use super::*;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Attr {
pub detachstate: u8,
pub inheritsched: u8,
pub schedpolicy: u8,
pub scope: u8,
pub guardsize: usize,
pub stacksize: usize,
pub stack: usize,
pub param: sched_param,
}
impl Default for Attr {
fn default() -> Self {
Self {
// Default according to POSIX.
detachstate: PTHREAD_CREATE_JOINABLE as _,
// Default according to POSIX.
inheritsched: PTHREAD_INHERIT_SCHED as _,
// TODO: Linux
// Redox uses a round-robin scheduler
schedpolicy: SCHED_RR as _,
// TODO: Linux uses this one.
scope: PTHREAD_SCOPE_SYSTEM as _,
guardsize: Sys::getpagesize(),
// TODO
stack: 0,
// TODO
stacksize: 1024 * 1024,
param: sched_param {
// TODO
sched_priority: 0,
}
}
}
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_destroy(_attr: *mut pthread_attr_t) -> c_int {
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_getdetachstate(attr: *const pthread_attr_t, detachstate: *mut c_int) -> c_int {
core::ptr::write(detachstate, (*attr).detachstate as _);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_getguardsize(attr: *const pthread_attr_t, size: *mut size_t) -> c_int {
core::ptr::write(size, (*attr).guardsize);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_getinheritsched(attr: *const pthread_attr_t, inheritsched: *mut c_int) -> c_int {
core::ptr::write(inheritsched, (*attr).inheritsched as _);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_getschedparam(attr: *const pthread_attr_t, param: *mut sched_param) -> c_int {
core::ptr::write(param, (*attr).param);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_getschedpolicy(attr: *const pthread_attr_t, policy: *mut c_int) -> c_int {
core::ptr::write(policy, (*attr).schedpolicy as _);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_getscope(attr: *const pthread_attr_t, scope: *mut c_int) -> c_int {
core::ptr::write(scope, (*attr).scope as _);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_getstack(attr: *const pthread_attr_t, stackaddr: *mut *mut c_void, stacksize: *mut size_t) -> c_int {
core::ptr::write(stackaddr, (*attr).stack as _);
core::ptr::write(stacksize, (*attr).stacksize as _);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_getstacksize(attr: *const pthread_attr_t, stacksize: *mut c_int) -> c_int {
core::ptr::write(stacksize, (*attr).stacksize as _);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_init(attr: *mut pthread_attr_t) -> c_int {
core::ptr::write(attr, Attr::default());
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_setdetachstate(attr: *mut pthread_attr_t, detachstate: c_int) -> c_int {
(*attr).detachstate = detachstate as _;
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_setguardsize(attr: *mut pthread_attr_t, guardsize: c_int) -> c_int {
(*attr).guardsize = guardsize as _;
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_setinheritsched(attr: *mut pthread_attr_t, inheritsched: c_int) -> c_int {
(*attr).inheritsched = inheritsched as _;
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_setschedparam(attr: *mut pthread_attr_t, param: *const sched_param) -> c_int {
(*attr).param = core::ptr::read(param);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_setschedpolicy(attr: *mut pthread_attr_t, policy: c_int) -> c_int {
(*attr).schedpolicy = policy as u8;
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_setscope(attr: *mut pthread_attr_t, scope: c_int) -> c_int {
(*attr).scope = scope as u8;
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_setstack(attr: *mut pthread_attr_t, stackaddr: *mut c_void, stacksize: size_t) -> c_int {
(*attr).stack = stackaddr as usize;
(*attr).stacksize = stacksize;
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_attr_setstacksize(attr: *mut pthread_attr_t, stacksize: size_t) -> c_int {
(*attr).stacksize = stacksize;
0
}
+85
View File
@@ -0,0 +1,85 @@
use crate::header::errno::*;
use crate::sync::AtomicLock;
use core::sync::atomic::{AtomicU32, Ordering};
use super::*;
#[repr(C)]
pub struct Barrier {
count: AtomicU32,
original_count: u32,
epoch: AtomicLock,
}
#[repr(C)]
pub struct BarrierAttr {
pshared: c_int,
}
#[no_mangle]
pub unsafe extern "C" fn pthread_barrier_destroy(barrier: *mut pthread_barrier_t) -> c_int {
// Behavior is undefined if any thread is currently waiting.
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_barrier_init(barrier: *mut pthread_barrier_t, attr: *const pthread_barrierattr_t, count: c_uint) -> c_int {
if count == 0 {
return EINVAL;
}
core::ptr::write(barrier, Barrier {
count: AtomicU32::new(0),
original_count: count,
epoch: AtomicLock::new(0),
});
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_barrier_wait(barrier: *mut pthread_barrier_t) -> c_int {
let barrier: &pthread_barrier_t = &*barrier;
// TODO: Orderings
let mut cached = barrier.count.load(Ordering::SeqCst);
loop {
let new = if cached == barrier.original_count - 1 { 0 } else { cached + 1 };
match barrier.count.compare_exchange_weak(cached, new, Ordering::SeqCst, Ordering::SeqCst) {
Ok(_) => if new == 0 {
// We reached COUNT waits, and will thus be the thread notifying every other
// waiter.
todo!();
return PTHREAD_BARRIER_SERIAL_THREAD;
} else {
// We increased the wait count, but this was not sufficient. We will thus have to
// wait for the epoch to tick up.
todo!();
return 0;
}
Err(value) => {
cached = value;
core::hint::spin_loop();
}
}
}
}
#[no_mangle]
pub unsafe extern "C" fn pthread_barrierattr_init(attr: *mut pthread_barrierattr_t) -> c_int {
// PTHREAD_PROCESS_PRIVATE is default according to POSIX.
core::ptr::write(attr, BarrierAttr { pshared: PTHREAD_PROCESS_PRIVATE });
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_barrierattr_setpshared(attr: *mut pthread_barrierattr_t, pshared: c_int) -> c_int {
(*attr).pshared = pshared;
0
}
+9
View File
@@ -0,0 +1,9 @@
sys_includes = ["sched.h", "time.h"]
include_guard = "_RELIBC_PTHREAD_H"
language = "C"
style = "Tag"
no_includes = true
cpp_compat = true
[enum]
prefix_with_name = true
+84
View File
@@ -0,0 +1,84 @@
use super::*;
// PTHREAD_COND_INITIALIZER
#[repr(C)]
pub struct CondAttr {
clock: clockid_t,
pshared: c_int,
}
#[repr(C)]
pub struct Cond {
}
// #[no_mangle]
pub extern "C" fn pthread_cond_broadcast(cond: *mut pthread_cond_t) -> c_int {
todo!()
}
// #[no_mangle]
pub extern "C" fn pthread_cond_destroy(cond: *mut pthread_cond_t) -> c_int {
todo!()
}
// #[no_mangle]
pub extern "C" fn pthread_cond_init(cond: *mut pthread_cond_t, attr: *const pthread_condattr_t) -> c_int {
todo!()
}
// #[no_mangle]
pub extern "C" fn pthread_cond_signal(cond: *mut pthread_cond_t) -> c_int {
todo!()
}
// #[no_mangle]
pub extern "C" fn pthread_cond_timedwait(cond: *mut pthread_cond_t, mutex: *const pthread_mutex_t, timeout: *const timespec) -> c_int {
todo!()
}
// #[no_mangle]
pub extern "C" fn pthread_cond_wait(cond: *mut pthread_cond_t, mutex: *const pthread_mutex_t) -> c_int {
todo!()
}
#[no_mangle]
pub unsafe extern "C" fn pthread_condattr_destroy(condattr: *mut pthread_condattr_t) -> c_int {
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_condattr_getclock(condattr: *const pthread_condattr_t, clock: *mut clockid_t) -> c_int {
core::ptr::write(clock, (*condattr).clock);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_condattr_getpshared(condattr: *const pthread_condattr_t, pshared: *mut c_int) -> c_int {
core::ptr::write(pshared, (*condattr).pshared);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_condattr_init(condattr: *mut pthread_condattr_t) -> c_int {
core::ptr::write(condattr, CondAttr {
// FIXME: system clock
clock: 0,
// Default
pshared: PTHREAD_PROCESS_PRIVATE,
});
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_condattr_setclock(condattr: *mut pthread_condattr_t, clock: clockid_t) -> c_int {
(*condattr).clock = clock;
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_condattr_setpshared(condattr: *mut pthread_condattr_t, pshared: c_int) -> c_int {
(*condattr).pshared = pshared;
0
}
+185
View File
@@ -0,0 +1,185 @@
//! pthread.h implementation for Redox, following https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pthread.h.html
use core::ptr::NonNull;
use crate::platform::{self, Pal, Sys, types::*};
use crate::header::{sched::*, time::timespec};
use crate::pthread;
pub const PTHREAD_BARRIER_SERIAL_THREAD: c_int = 1;
pub const PTHREAD_CANCEL_ASYNCHRONOUS: c_int = 0;
pub const PTHREAD_CANCEL_ENABLE: c_int = 0;
pub const PTHREAD_CANCEL_DEFERRED: c_int = 0;
pub const PTHREAD_CANCEL_DISABLE: c_int = 0;
pub const PTHREAD_CANCELED: *mut c_void = core::ptr::null_mut();
pub const PTHREAD_CREATE_DETACHED: c_int = 0;
pub const PTHREAD_CREATE_JOINABLE: c_int = 1;
pub const PTHREAD_EXPLICIT_SCHED: c_int = 0;
pub const PTHREAD_INHERIT_SCHED: c_int = 1;
pub const PTHREAD_MUTEX_DEFAULT: c_int = 0;
pub const PTHREAD_MUTEX_ERRORCHECK: c_int = 0;
pub const PTHREAD_MUTEX_NORMAL: c_int = 0;
pub const PTHREAD_MUTEX_RECURSIVE: c_int = 0;
pub const PTHREAD_MUTEX_ROBUST: c_int = 0;
pub const PTHREAD_MUTEX_STALLED: c_int = 0;
pub const PTHREAD_PRIO_INHERIT: c_int = 0;
pub const PTHREAD_PRIO_NONE: c_int = 0;
pub const PTHREAD_PRIO_PROTECT: c_int = 0;
pub const PTHREAD_PROCESS_SHARED: c_int = 0;
pub const PTHREAD_PROCESS_PRIVATE: c_int = 1;
pub const PTHREAD_SCOPE_PROCESS: c_int = 0;
pub const PTHREAD_SCOPE_SYSTEM: c_int = 1;
#[no_mangle]
pub unsafe extern "C" fn pthread_atfork(prepare: extern "C" fn(), parent: extern "C" fn(), child: extern "C" fn()) -> c_int {
let mut guard = pthread::FORK_HANDLERS.lock();
// TODO: try_reserve
guard.prepare.push(prepare);
guard.child.push(child);
guard.parent.push(parent);
0
}
pub mod attr;
pub use self::attr::*;
pub mod barrier;
pub use self::barrier::*;
#[no_mangle]
pub unsafe extern "C" fn pthread_cancel(thread: pthread_t) -> c_int {
match pthread::cancel(&*thread.cast()) {
Ok(()) => 0,
Err(pthread::Errno(error)) => error,
}
}
pub mod cond;
pub use self::cond::*;
#[no_mangle]
pub unsafe extern "C" fn pthread_create(pthread: *mut pthread_t, attr: *const pthread_attr_t, start_routine: extern "C" fn(arg: *mut c_void) -> *mut c_void, arg: *mut c_void) -> c_int {
let attr = NonNull::new(attr as *mut _).map(|n| n.as_ref());
match pthread::create(attr, start_routine, arg) {
Ok(ptr) => {
core::ptr::write(pthread, ptr);
0
}
Err(pthread::Errno(code)) => code,
}
}
#[no_mangle]
pub unsafe extern "C" fn pthread_detach(pthread: pthread_t) -> c_int {
match pthread::detach(&*pthread.cast()) {
Ok(()) => 0,
Err(pthread::Errno(errno)) => errno,
}
}
#[no_mangle]
pub extern "C" fn pthread_equal(pthread1: pthread_t, pthread2: pthread_t) -> c_int {
core::ptr::eq(pthread1, pthread2).into()
}
#[no_mangle]
pub unsafe extern "C" fn pthread_exit(retval: *mut c_void) -> ! {
pthread::exit_current_thread(pthread::Retval(retval))
}
// #[no_mangle]
pub extern "C" fn pthread_getconcurrency() -> c_int {
todo!()
}
// #[no_mangle]
pub extern "C" fn pthread_getcpuclockid(thread: pthread_t, clock: *mut clockid_t) -> c_int {
todo!()
}
// #[no_mangle]
pub extern "C" fn pthread_getschedparam(thread: pthread_t, policy: *mut clockid_t, param: *mut sched_param) -> c_int {
todo!()
}
// #[no_mangle]
pub extern "C" fn pthread_getspecific(key: pthread_key_t) -> *mut c_void {
todo!()
}
#[no_mangle]
pub unsafe extern "C" fn pthread_join(thread: pthread_t, retval: *mut *mut c_void) -> c_int {
match pthread::join(&*thread.cast()) {
Ok(pthread::Retval(ret)) => {
core::ptr::write(retval, ret);
0
}
Err(pthread::Errno(error)) => error,
}
}
// #[no_mangle]
pub extern "C" fn pthread_key_create(key: *mut pthread_key_t, destructor: extern "C" fn(value: *mut c_void)) -> c_int {
todo!()
}
// #[no_mangle]
pub extern "C" fn pthread_key_delete(key: pthread_key_t) -> c_int {
todo!()
}
pub mod mutex;
pub use self::mutex::*;
pub mod once;
pub use self::once::*;
pub mod rwlock;
pub use self::rwlock::*;
pub unsafe extern "C" fn pthread_self() -> pthread_t {
pthread::current_thread().unwrap_unchecked() as *const _ as *mut _
}
pub extern "C" fn pthread_setcancelstate(state: c_int, oldstate: *mut c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_setcanceltype(ty: c_int, oldty: *mut c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_setconcurrency(concurrency: c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_setschedparam(thread: pthread_t, policy: c_int, param: *const sched_param) -> c_int {
todo!();
}
pub extern "C" fn pthread_setschedprio(thread: pthread_t, prio: c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_setspecific(key: pthread_key_t, value: *const c_void) -> c_int {
todo!();
}
pub mod spin;
pub use self::spin::*;
pub unsafe extern "C" fn pthread_testcancel() {
pthread::testcancel();
}
// pthread_cleanup_pop()
// pthread_cleanup_push()
+88
View File
@@ -0,0 +1,88 @@
use super::*;
// PTHREAD_MUTEX_INITIALIZER
#[repr(C)]
pub struct Mutex {
}
#[repr(C)]
pub struct MutexAttr {
}
pub extern "C" fn pthread_mutex_consistent(mutex: *mut pthread_mutex_t) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutex_destroy(mutex: *mut pthread_mutex_t) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutex_getprioceiling(mutex: *const pthread_mutex_t, prioceiling: *mut c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutex_init(mutex: *mut pthread_mutex_t, attr: *const pthread_mutexattr_t) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutex_lock(mutex: *mut pthread_mutex_t) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutex_setprioceiling(mutex: *mut pthread_mutex_t, prioceiling: c_int, old_prioceiling: *mut c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutex_timedlock(mutex: *mut pthread_mutex_t, timespec: *const timespec) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutex_trylock(mutex: *mut pthread_mutex_t) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutex_unlock(mutex: *mut pthread_mutex_t) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_destroy(attr: *mut pthread_mutexattr_t) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_getprioceiling(attr: *const pthread_mutexattr_t, prioceiling: *mut c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_getprotocol(attr: *const pthread_mutexattr_t, protocol: *mut c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_getpshared(attr: *const pthread_mutexattr_t, pshared: *mut c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_getrobust(attr: *const pthread_mutexattr_t, robust: *mut c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_gettype(attr: *const pthread_mutexattr_t, ty: *mut c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_init(attr: *mut pthread_mutexattr_t) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_setprioceiling(attr: *mut pthread_mutexattr_t, prioceiling: c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_setprotocol(attr: *mut pthread_mutexattr_t, protocol: c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_setpshared(attr: *mut pthread_mutexattr_t, pshared: c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_setrobust(attr: *mut pthread_mutexattr_t, robust: c_int) -> c_int {
todo!();
}
pub extern "C" fn pthread_mutexattr_settype(attr: *mut pthread_mutexattr_t, ty: c_int) -> c_int {
todo!();
}
+18
View File
@@ -0,0 +1,18 @@
use super::*;
#[repr(C)]
pub struct Once {
inner: crate::sync::Once<()>,
}
// PTHREAD_ONCE_INIT
#[no_mangle]
pub unsafe extern "C" fn pthread_once(once: *mut pthread_once_t, constructor: extern "C" fn()) -> c_int {
let once: &pthread_once_t = &*once;
// TODO: Cancellation points
once.inner.call_once(|| constructor());
0
}
+144
View File
@@ -0,0 +1,144 @@
use super::*;
use crate::sync::AtomicLock;
use core::sync::atomic::Ordering;
use crate::header::errno::EBUSY;
// PTHREAD_RWLOCK_INITIALIZER
// TODO: Optimize for short waits and long waits, using AtomicLock::wait_until, but still
// supporting timeouts.
// TODO: Add futex ops that use bitmasks.
const EXCLUSIVE: u32 = (1 << (u32::BITS - 1)) - 1;
// Separate "waiting for wrlocks" and "waiting for rdlocks"?
//const WAITING: u32 = 1 << (u32::BITS - 1);
#[repr(C)]
pub struct Rwlock {
state: AtomicLock,
}
#[repr(C)]
pub struct RwlockAttr {
pshared: c_int,
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlock_destroy(rwlock: *mut pthread_rwlock_t) -> c_int {
// (Informing the compiler that this pointer is valid, might improve optimizations.)
let _rwlock: &pthread_rwlock_t = &*rwlock;
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlock_init(rwlock: *mut pthread_rwlock_t, _attr: *const pthread_rwlockattr_t) -> c_int {
core::ptr::write(rwlock, Rwlock {
state: AtomicLock::new(0),
});
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlock_rdlock(rwlock: *mut pthread_rwlock_t) -> c_int {
pthread_rwlock_timedrdlock(rwlock, core::ptr::null())
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlock_timedrdlock(rwlock: *mut pthread_rwlock_t, timeout: *const timespec) -> c_int {
let rwlock: &pthread_rwlock_t = &*rwlock;
let timeout = NonNull::new(timeout as *mut _).map(|n| n.as_ref());
loop {
if pthread_rwlock_tryrdlock(rwlock as *const _ as *mut _) == EBUSY {
rwlock.state.wait_if(EXCLUSIVE as i32, timeout);
}
return 0;
}
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlock_timedwrlock(rwlock: *mut pthread_rwlock_t, timeout: *const timespec) -> c_int {
let rwlock: &pthread_rwlock_t = &*rwlock;
let timeout = NonNull::new(timeout as *mut _).map(|n| n.as_ref());
loop {
if pthread_rwlock_trywrlock(rwlock as *const _ as *mut _) == EBUSY {
rwlock.state.wait_if(EXCLUSIVE as i32, timeout);
}
return 0;
}
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlock_tryrdlock(rwlock: *mut pthread_rwlock_t) -> c_int {
let rwlock: &pthread_rwlock_t = &*rwlock;
let mut cached = rwlock.state.load(Ordering::Acquire) as u32;
loop {
let old = if cached == EXCLUSIVE { 0 } else { cached };
let new = old + 1;
assert_ne!(new, EXCLUSIVE, "overflow");
match rwlock.state.compare_exchange_weak(cached as i32, new as i32, Ordering::Acquire, Ordering::Relaxed) {
Ok(_) => return 0,
Err(value) if value as u32 == EXCLUSIVE => return EBUSY,
Err(value) => {
cached = value as u32;
core::hint::spin_loop();
}
}
}
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlock_trywrlock(rwlock: *mut pthread_rwlock_t) -> c_int {
let rwlock: &pthread_rwlock_t = &*rwlock;
match rwlock.state.compare_exchange(0, EXCLUSIVE as i32, Ordering::Acquire, Ordering::Relaxed) {
Ok(_) => 0,
Err(_) => EBUSY,
}
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlock_unlock(rwlock: *const pthread_rwlock_t) -> c_int {
let rwlock: &pthread_rwlock_t = &*rwlock;
let old = rwlock.state.swap(0, Ordering::Release) as u32;
if old == EXCLUSIVE {
rwlock.state.notify_all();
}
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlock_wrlock(rwlock: *mut pthread_rwlock_t) -> c_int {
pthread_rwlock_timedwrlock(rwlock, core::ptr::null())
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlockattr_destroy(_attr: *mut pthread_rwlockattr_t) -> c_int {
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlockattr_getpshared(attr: *const pthread_rwlockattr_t, pshared: *mut c_int) -> c_int {
core::ptr::write(pshared, (*attr).pshared);
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlockattr_init(attr: *mut pthread_rwlockattr_t) -> c_int {
core::ptr::write(attr, RwlockAttr {
// Default according to POSIX.
pshared: PTHREAD_PROCESS_PRIVATE,
});
0
}
#[no_mangle]
pub unsafe extern "C" fn pthread_rwlockattr_setpshared(attr: *mut pthread_rwlockattr_t, pshared: c_int) -> c_int {
(*attr).pshared = pshared;
0
}
+50
View File
@@ -0,0 +1,50 @@
use core::sync::atomic::{AtomicI32 as AtomicInt, Ordering};
use crate::header::errno::EBUSY;
use super::*;
#[repr(C)]
pub struct Spinlock {
inner: AtomicInt,
}
pub const UNLOCKED: c_int = 0;
pub const LOCKED: c_int = 1;
pub unsafe extern "C" fn pthread_spin_destroy(spinlock: *mut pthread_spinlock_t) -> c_int {
0
}
pub unsafe extern "C" fn pthread_spin_init(spinlock: *mut pthread_spinlock_t, _pshared: c_int) -> c_int {
// TODO: pshared doesn't matter in most situations, as memory is just memory, but this may be
// different on some architectures...
core::ptr::write(spinlock, Spinlock { inner: AtomicInt::new(UNLOCKED) });
0
}
pub unsafe extern "C" fn pthread_spin_lock(spinlock: *mut pthread_spinlock_t) -> c_int {
let spinlock: &Spinlock = &*spinlock;
loop {
match spinlock.inner.compare_exchange_weak(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed) {
Ok(_) => return 0,
Err(_) => core::hint::spin_loop(),
}
}
}
pub unsafe extern "C" fn pthread_spin_trylock(spinlock: *mut pthread_spinlock_t) -> c_int {
let spinlock: &Spinlock = &*spinlock;
match spinlock.inner.compare_exchange(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed) {
Ok(_) => 0,
Err(_) => EBUSY,
}
}
pub unsafe extern "C" fn pthread_spin_unlock(spinlock: *mut pthread_spinlock_t) -> c_int {
let spinlock: &Spinlock = &*spinlock;
spinlock.inner.store(UNLOCKED, Ordering::Release);
0
}
+1 -1
View File
@@ -4,7 +4,7 @@ use crate::platform::{Pal, Sys, types::*};
use crate::header::time::timespec;
#[repr(C)]
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub struct sched_param {
sched_priority: c_int,
}
+2
View File
@@ -58,6 +58,8 @@ pub const SIGSYS: usize = 31;
pub const SIGUNUSED: usize = SIGSYS;
pub const NSIG: usize = 32;
pub const SIGRTMIN: usize = 34;
pub const SA_NOCLDSTOP: usize = 1;
pub const SA_NOCLDWAIT: usize = 2;
pub const SA_SIGINFO: usize = 4;
+2
View File
@@ -66,6 +66,8 @@ pub const SIGPWR: usize = 30;
pub const SIGSYS: usize = 31;
pub const NSIG: usize = 32;
pub const SIGRTMIN: usize = 34;
pub const SA_NOCLDSTOP: usize = 0x00000001;
pub const SA_NOCLDWAIT: usize = 0x00000002;
pub const SA_SIGINFO: usize = 0x00000004;
+3 -2
View File
@@ -6,10 +6,10 @@
#![feature(allocator_api)]
#![feature(array_chunks)]
#![feature(asm_const)]
#![feature(box_into_pin)]
#![feature(atomic_mut_ptr)]
#![feature(c_variadic)]
#![feature(const_btree_new)]
#![feature(core_intrinsics)]
#![feature(int_roundings)]
#![feature(lang_items)]
#![feature(linkage)]
#![feature(stmt_expr_attributes)]
@@ -57,6 +57,7 @@ pub mod header;
pub mod io;
pub mod ld_so;
pub mod platform;
pub mod pthread;
pub mod start;
pub mod sync;
+3 -1
View File
@@ -47,6 +47,8 @@ pub trait Pal {
fn exit(status: c_int) -> !;
fn exit_thread() -> !;
fn fchdir(fildes: c_int) -> c_int;
fn fchmod(fildes: c_int, mode: mode_t) -> c_int;
@@ -155,7 +157,7 @@ pub trait Pal {
fn pipe2(fildes: &mut [c_int], flags: c_int) -> c_int;
unsafe fn pte_clone(stack: *mut usize) -> pid_t;
unsafe fn pte_clone(stack: *mut usize) -> crate::pthread::OsTid;
fn read(fildes: c_int, buf: &mut [u8]) -> ssize_t;
-124
View File
@@ -12,10 +12,6 @@ use crate::{
sys_mman,
time::{clock_gettime, timespec, CLOCK_MONOTONIC},
},
ld_so::{
linker::Linker,
tcb::{Master, Tcb},
},
platform::{
types::{c_int, c_long, c_uint, c_void, pid_t, size_t, time_t},
Pal, Sys,
@@ -59,126 +55,6 @@ unsafe fn locals<'a>() -> &'a mut BTreeMap<c_uint, *mut c_void> {
&mut *LOCALS.get()
}
// pte_osResult pte_osInit(void)
#[no_mangle]
pub unsafe extern "C" fn pte_osInit() -> pte_osResult {
PTE_OS_OK
}
/// A shim to wrap thread entry points in logic to set up TLS, for example
unsafe extern "C" fn pte_osThreadShim(
entryPoint: pte_osThreadEntryPoint,
argv: *mut c_void,
mutex: pte_osMutexHandle,
tls_size: usize,
tls_masters_ptr: *mut Master,
tls_masters_len: usize,
tls_linker_ptr: *const Mutex<Linker>,
tls_mspace: usize,
) {
// The kernel allocated TLS does not have masters set, so do not attempt to copy it.
// It will be copied by the kernel.
if !tls_masters_ptr.is_null() {
let tcb = Tcb::new(tls_size).unwrap();
tcb.masters_ptr = tls_masters_ptr;
tcb.masters_len = tls_masters_len;
tcb.linker_ptr = tls_linker_ptr;
tcb.mspace = tls_mspace;
tcb.copy_masters().unwrap();
tcb.activate();
}
// Wait until pte_osThreadStart
pte_osMutexLock(mutex);
entryPoint(argv);
pte_osThreadExit();
}
#[no_mangle]
pub unsafe extern "C" fn pte_osThreadCreate(
entryPoint: pte_osThreadEntryPoint,
stackSize: c_int,
_initialPriority: c_int,
argv: *mut c_void,
ppte_osThreadHandle: *mut pte_osThreadHandle,
) -> pte_osResult {
// Create a locked mutex, unlocked by pte_osThreadStart
let mutex: pte_osMutexHandle = Box::into_raw(Box::new(Mutex::locked(())));
let stack_size = if stackSize == 0 {
1024 * 1024
} else {
stackSize as usize
};
let stack_base = sys_mman::mmap(
ptr::null_mut(),
stack_size,
sys_mman::PROT_READ | sys_mman::PROT_WRITE,
sys_mman::MAP_SHARED | sys_mman::MAP_ANONYMOUS,
-1,
0,
);
if stack_base as isize == -1 {
return PTE_OS_GENERAL_FAILURE;
}
let stack_end = stack_base.add(stack_size);
let mut stack = stack_end as *mut usize;
{
let mut push = |value: usize| {
stack = stack.offset(-1);
*stack = value;
};
//WARNING: Stack must be 128-bit aligned for SSE
if let Some(tcb) = Tcb::current() {
push(tcb.mspace as usize);
push(tcb.linker_ptr as usize);
push(tcb.masters_len);
push(tcb.masters_ptr as usize);
push(tcb.tls_len);
} else {
push(ALLOCATOR.get_book_keeper());
push(0);
push(0);
push(0);
push(0);
}
push(mutex as usize);
push(argv as usize);
push(entryPoint as usize);
push(pte_osThreadShim as usize);
}
let id = Sys::pte_clone(stack);
if id < 0 {
return PTE_OS_GENERAL_FAILURE;
}
pte_osMutexLock(&mut pid_mutexes_lock);
if pid_mutexes.is_none() {
pid_mutexes = Some(BTreeMap::new());
}
pid_mutexes.as_mut().unwrap().insert(id, mutex);
pte_osMutexUnlock(&mut pid_mutexes_lock);
pte_osMutexLock(&mut pid_stacks_lock);
if pid_stacks.is_none() {
pid_stacks = Some(BTreeMap::new());
}
pid_stacks
.as_mut()
.unwrap()
.insert(id, (stack_base, stack_size));
pte_osMutexUnlock(&mut pid_stacks_lock);
*ppte_osThreadHandle = id;
PTE_OS_OK
}
#[no_mangle]
pub unsafe extern "C" fn pte_osThreadStart(handle: pte_osThreadHandle) -> pte_osResult {
let mut ret = PTE_OS_GENERAL_FAILURE;
+4
View File
@@ -1005,4 +1005,8 @@ impl Pal for Sys {
// GETPID on Redox is 20, which is WRITEV on Linux
e(unsafe { syscall::syscall5(syscall::number::SYS_GETPID, !0, !0, !0, !0, !0) }) != !0
}
fn exit_thread() -> ! {
Self::exit(0)
}
}
+14
View File
@@ -76,3 +76,17 @@ pub type suseconds_t = c_int;
pub type clock_t = c_long;
pub type clockid_t = c_int;
pub type timer_t = *mut c_void;
pub type pthread_attr_t = crate::header::pthread::attr::Attr;
pub type pthread_barrier_t = crate::header::pthread::barrier::Barrier;
pub type pthread_barrierattr_t = crate::header::pthread::barrier::BarrierAttr;
pub type pthread_cond_t = crate::header::pthread::cond::Cond;
pub type pthread_condattr_t = crate::header::pthread::cond::CondAttr;
pub type pthread_key_t = *mut c_void;
pub type pthread_mutex_t = crate::header::pthread::mutex::Mutex;
pub type pthread_mutexattr_t = crate::header::pthread::mutex::MutexAttr;
pub type pthread_once_t = crate::header::pthread::once::Once;
pub type pthread_rwlock_t = crate::header::pthread::rwlock::Rwlock;
pub type pthread_rwlockattr_t = crate::header::pthread::rwlock::RwlockAttr;
pub type pthread_spinlock_t = crate::header::pthread::spin::Spinlock;
pub type pthread_t = *mut c_void;
+235
View File
@@ -0,0 +1,235 @@
use core::cell::{Cell, UnsafeCell};
use core::ptr::NonNull;
use core::sync::atomic::{AtomicBool, Ordering};
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use crate::platform::{Pal, Sys, types::*};
use crate::header::sched::sched_param;
use crate::header::errno::*;
use crate::header::sys_mman;
use crate::ld_so::{linker::Linker, tcb::{Master, Tcb}};
use crate::sync::{Mutex, waitval::Waitval};
extern "C" fn pthread_init() {
}
extern "C" fn pthread_terminate() {
}
struct Pthread {
waitval: Waitval<Retval>,
wants_cancel: AtomicBool,
stack_base: *mut c_void,
stack_size: usize,
os_tid: OsTid,
}
#[derive(Clone, Copy, Debug, Ord, Eq, PartialOrd, PartialEq)]
pub struct OsTid {
#[cfg(target_os = "redox")]
context_id: usize,
#[cfg(target_os = "linux")]
thread_id: usize,
}
unsafe impl Send for Pthread {}
unsafe impl Sync for Pthread {}
use crate::header::pthread::attr::Attr;
/// Positive error codes (EINVAL, not -EINVAL).
#[derive(Debug)]
pub struct Errno(pub c_int);
pub struct Retval(pub *mut c_void);
struct MmapGuard { page_start: *mut c_void, mmap_size: usize }
impl Drop for MmapGuard {
fn drop(&mut self) {
unsafe {
let _ = Sys::munmap(self.page_start, self.mmap_size);
}
}
}
pub unsafe fn create(attrs: Option<&pthread_attr_t>, start_routine: extern "C" fn(arg: *mut c_void) -> *mut c_void, arg: *mut c_void) -> Result<pthread_t, Errno> {
let attrs = attrs.copied().unwrap_or_default();
// Create a locked mutex, unlocked by the thread after it has started.
let synchronization_mutex = Box::into_raw(Box::new(Mutex::locked(())));
let stack_size = attrs.stacksize.next_multiple_of(Sys::getpagesize());
// TODO: Custom stacks
let stack_base = sys_mman::mmap(
core::ptr::null_mut(),
attrs.stacksize,
sys_mman::PROT_READ | sys_mman::PROT_WRITE,
sys_mman::MAP_SHARED | sys_mman::MAP_ANONYMOUS,
-1,
0,
);
if stack_base as isize == -1 {
// "Insufficient resources"
return Err(Errno(EAGAIN));
}
let pthread = Pthread {
waitval: Waitval::new(),
wants_cancel: AtomicBool::new(false),
stack_base,
stack_size,
os_tid,
};
let ptr = Box::into_raw(Box::new(pthread));
let stack_raii = MmapGuard { page_start: stack_base, mmap_size: stack_size };
let stack_end = stack_base.add(stack_size);
let mut stack = stack_end as *mut usize;
{
let mut push = |value: usize| {
stack = stack.sub(1);
stack.write(value);
};
push(0);
push(ptr as usize);
//WARNING: Stack must be 128-bit aligned for SSE
if let Some(tcb) = Tcb::current() {
push(tcb.mspace as usize);
push(tcb.linker_ptr as usize);
push(tcb.masters_len);
push(tcb.masters_ptr as usize);
push(tcb.tls_len);
} else {
push(ALLOCATOR.get_book_keeper());
push(0);
push(0);
push(0);
push(0);
}
push(synchronization_mutex as usize);
push(arg as usize);
push(start_routine as usize);
push(new_thread_shim as usize);
}
let id = Sys::pte_clone(stack);
if id < 0 {
return Err(Errno(EAGAIN));
}
let _ = (&mut *synchronization_mutex).lock();
CID_TO_PTHREAD.lock().insert(id, ForceSendSync(ptr.cast()));
core::mem::forget(stack_raii);
Ok(ptr.cast())
}
/// A shim to wrap thread entry points in logic to set up TLS, for example
unsafe extern "C" fn new_thread_shim(
entry_point: unsafe extern "C" fn(*mut c_void) -> *mut c_void,
arg: *mut c_void,
mutex: *const Mutex<()>,
tls_size: usize,
tls_masters_ptr: *mut Master,
tls_masters_len: usize,
tls_linker_ptr: *const Mutex<Linker>,
tls_mspace: usize,
pthread: *mut Pthread,
) -> ! {
// TODO: Pass less arguments by allocating the TCB from the creator thread.
if !tls_masters_ptr.is_null() {
let tcb = Tcb::new(tls_size).unwrap();
tcb.masters_ptr = tls_masters_ptr;
tcb.masters_len = tls_masters_len;
tcb.linker_ptr = tls_linker_ptr;
tcb.mspace = tls_mspace;
tcb.copy_masters().unwrap();
tcb.activate();
}
PTHREAD_SELF.set(pthread);
(&*mutex).manual_unlock();
let retval = entry_point(arg);
exit_current_thread(Retval(retval))
}
pub unsafe fn join(thread: &Pthread) -> Result<Retval, Errno> {
// We don't have to return EDEADLK, but unlike e.g. pthread_t lifetime checking, it's a
// relatively easy check.
if core::ptr::eq(thread, current_thread().unwrap_unchecked()) {
return Err(Errno(EDEADLK));
}
// Waitval starts locked, and is unlocked when the thread finishes.
let retval = *thread.waitval.wait();
// TODO: Deinitialization code
Ok(retval)
}
pub unsafe fn detach(thread: &Pthread) -> Result<(), Errno> {
todo!()
}
// Returns option because that's a no-op, but PTHREAD_SELF should always be initialized except in
// early init code.
pub fn current_thread() -> Option<&'static Pthread> {
unsafe {
NonNull::new(PTHREAD_SELF.get()).map(|p| p.as_ref())
}
}
pub unsafe fn testcancel() {
// TODO: Ordering
if current_thread().unwrap_unchecked().wants_cancel.load(Ordering::SeqCst) {
todo!("cancel")
}
}
pub unsafe fn exit_current_thread(retval: Retval) -> ! {
let this = current_thread().unwrap_unchecked();
this.waitval.post(retval);
Sys::exit_thread()
}
pub unsafe fn cancel(thread: &Pthread) -> Result<(), Errno> {
thread.wants_cancel.store(true, Ordering::SeqCst);
crate::header::signal
}
// TODO: Hash map?
static OS_TID_TO_PTHREAD: Mutex<BTreeMap<OsTid, ForceSendSync<pthread_t>>> = Mutex::new(BTreeMap::new());
struct ForceSendSync<T>(T);
unsafe impl<T> Send for ForceSendSync<T> {}
unsafe impl<T> Sync for ForceSendSync<T> {}
#[thread_local]
static PTHREAD_SELF: Cell<*mut Pthread> = Cell::new(core::ptr::null_mut());
pub struct ForkHandlers {
pub prepare: Vec<extern "C" fn()>,
pub parent: Vec<extern "C" fn()>,
pub child: Vec<extern "C" fn()>,
}
// TODO: Use fork handlers
// TODO: Append-only atomic queue, not because of performance, but because of atomicity.
pub static FORK_HANDLERS: Mutex<ForkHandlers> = Mutex::new(ForkHandlers {
parent: Vec::new(),
child: Vec::new(),
prepare: Vec::new(),
});
+9 -8
View File
@@ -1,6 +1,7 @@
pub mod mutex;
pub mod once;
pub mod semaphore;
pub mod waitval;
pub use self::{
mutex::{Mutex, MutexGuard},
@@ -13,7 +14,6 @@ use crate::{
platform::{types::*, Pal, Sys},
};
use core::{
cell::UnsafeCell,
ops::Deref,
sync::atomic::{self, AtomicI32 as AtomicInt},
};
@@ -30,18 +30,19 @@ enum AttemptStatus {
/// Convenient wrapper around the "futex" system call for
/// synchronization implementations
struct AtomicLock {
atomic: UnsafeCell<AtomicInt>,
#[repr(C)]
pub(crate) struct AtomicLock {
pub(crate) atomic: AtomicInt,
}
impl AtomicLock {
pub const fn new(value: c_int) -> Self {
Self {
atomic: UnsafeCell::new(AtomicInt::new(value)),
atomic: AtomicInt::new(value),
}
}
pub fn notify_one(&self) {
Sys::futex(
unsafe { &mut *self.atomic.get() }.get_mut(),
self.atomic.as_mut_ptr(),
FUTEX_WAKE,
1,
0,
@@ -49,7 +50,7 @@ impl AtomicLock {
}
pub fn notify_all(&self) {
Sys::futex(
unsafe { &mut *self.atomic.get() }.get_mut(),
self.atomic.as_mut_ptr(),
FUTEX_WAKE,
c_int::max_value(),
0,
@@ -57,7 +58,7 @@ impl AtomicLock {
}
pub fn wait_if(&self, value: c_int, timeout_opt: Option<&timespec>) {
Sys::futex(
unsafe { &mut *self.atomic.get() }.get_mut(),
self.atomic.as_mut_ptr(),
FUTEX_WAIT,
value,
timeout_opt.map_or(0, |timeout| timeout as *const timespec as usize),
@@ -125,6 +126,6 @@ impl Deref for AtomicLock {
type Target = AtomicInt;
fn deref(&self) -> &Self::Target {
unsafe { &*self.atomic.get() }
&self.atomic
}
}
+39
View File
@@ -0,0 +1,39 @@
// Semaphores need one post per wait, Once doesn't have separate post and wait, and while mutexes
// wait for releasing the lock, it calls notify_one and needs locking again to access the value.
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use core::sync::atomic::Ordering;
use super::*;
pub struct Waitval<T> {
state: AtomicLock,
value: UnsafeCell<MaybeUninit<T>>,
}
unsafe impl<T: Send + Sync> Send for Waitval<T> {}
unsafe impl<T: Send + Sync> Sync for Waitval<T> {}
impl<T> Waitval<T> {
pub const fn new() -> Self {
Self {
state: AtomicLock::new(0),
value: UnsafeCell::new(MaybeUninit::uninit()),
}
}
pub unsafe fn post(&self, value: T) {
self.value.get().write(MaybeUninit::new(value));
self.state.store(1, Ordering::Release);
self.state.notify_all();
}
pub fn wait(&self) -> &T {
while self.state.load(Ordering::Acquire) == 0 {
self.state.wait_if(0, None);
}
unsafe { (*self.value.get()).assume_init_ref() }
}
}