libc: add pthread_mutex_t and pthread_barrier_t

This commit is contained in:
Mark Poliakov 2024-11-19 01:47:28 +02:00
parent 03f6362756
commit a14206204a
16 changed files with 501 additions and 99 deletions

View File

@ -139,7 +139,7 @@ impl<TA: TableAllocator> ProcessAddressSpaceImpl<TA> {
let page = l3[l3i].as_page()?;
Some((page, l3[l3i].attributes().into()))
Some((page.add(virt & 0xFFF), l3[l3i].attributes().into()))
}
}

View File

@ -120,7 +120,7 @@ impl<TA: TableAllocator> ProcessAddressSpaceImpl<TA> {
let l3 = self.l0.get(l0i)?;
let page = l3[l3i].as_page()?;
Some((page, l3[l3i].attributes().into()))
Some((page.add(virt & 0xFFF), l3[l3i].attributes().into()))
}
}

View File

@ -144,7 +144,7 @@ impl<TA: TableAllocator> ProcessAddressSpaceImpl<TA> {
let page = l3[l3i].as_page()?;
Some((page, l3[l3i].attributes().into()))
Some((page.add(virt & 0xFFF), l3[l3i].attributes().into()))
}
}

View File

@ -9,6 +9,8 @@ use core::{
use bytemuck::{Pod, Zeroable};
use kernel_arch_interface::mem::KernelTableManager;
use crate::table::EntryLevel;
/// Wrapper type to represent a physical memory address
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Pod, Zeroable)]
#[repr(transparent)]

View File

@ -1,4 +1,5 @@
use core::{
future::poll_fn,
pin::Pin,
sync::atomic::{AtomicU32, Ordering},
task::{Context, Poll},
@ -6,57 +7,59 @@ use core::{
use alloc::sync::Arc;
use futures_util::Future;
use libk_mm::{
address::{AsPhysicalAddress, PhysicalAddress},
pointer::PhysicalRef,
process::ProcessAddressSpace,
};
use libk_util::waker::QueueWaker;
use yggdrasil_abi::error::Error;
use crate::task::thread::Thread;
/// User-space mutex (like BSD/Linux's futex) data structure
pub struct UserspaceMutex {
queue: QueueWaker,
space: Arc<ProcessAddressSpace>,
address: usize,
}
impl UserspaceMutex {
/// Creates a new [UserspaceMutex] associated with given `address`
pub fn new(address: usize) -> Self {
Self {
pub fn new(space: &Arc<ProcessAddressSpace>, address: usize) -> Result<Self, Error> {
Ok(Self {
queue: QueueWaker::new(),
space: space.clone(),
address,
}
})
}
fn load(&self) -> u32 {
// TODO: this is slow, but this prevents the kernel from reading from unmapped memory
let phys = self.space.translate(self.address).unwrap();
unsafe { PhysicalRef::<AtomicU32>::map(phys).load(Ordering::Acquire) }
}
async fn wait_predicate<P: Fn(u32) -> bool>(&self, predicate: P) {
poll_fn(|cx| {
if predicate(self.load()) {
self.queue.remove(cx.waker());
Poll::Ready(())
} else {
self.queue.register(cx.waker());
Poll::Pending
}
})
.await
}
pub async fn wait_until(self: Arc<Self>, compare_value: u32) {
self.wait_predicate(|value| value == compare_value).await
}
/// Blocks until the value at the mutex's address becomes different from `compare_value`
pub fn wait(self: Arc<Self>, compare_value: u32) -> impl Future<Output = ()> {
struct WaitFuture {
mutex: Arc<UserspaceMutex>,
compare_value: u32,
}
impl WaitFuture {
fn load(&self, ordering: Ordering) -> u32 {
let value = unsafe { &*(self.mutex.address as *const AtomicU32) };
value.load(ordering)
}
}
impl Future for WaitFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.mutex.queue.register(cx.waker());
// Test the mutex
if self.load(Ordering::Acquire) != self.compare_value {
self.mutex.queue.remove(cx.waker());
Poll::Ready(())
} else {
Poll::Pending
}
}
}
WaitFuture {
mutex: self,
compare_value,
}
pub async fn wait(self: Arc<Self>, compare_value: u32) {
self.wait_predicate(|value| value != compare_value).await
}
/// Wakes up a single task waiting on the mutex

View File

@ -5,7 +5,7 @@ use core::{
use abi_lib::SyscallRegister;
use alloc::{
collections::BTreeMap,
collections::{btree_map, BTreeMap},
string::String,
sync::{Arc, Weak},
vec::Vec,
@ -349,13 +349,17 @@ impl Process {
/// Returns a [UserspaceMutex] associated with the `address`. If one does not exist, will
/// create it.
pub fn get_or_insert_mutex(&self, address: usize) -> Arc<UserspaceMutex> {
pub fn get_or_insert_mutex(&self, address: usize) -> Result<Arc<UserspaceMutex>, Error> {
let space = self.space();
let mut inner = self.inner.write();
inner
.mutexes
.entry(address)
.or_insert_with(|| Arc::new(UserspaceMutex::new(address)))
.clone()
match inner.mutexes.entry(address) {
btree_map::Entry::Vacant(slot) => {
let mutex = Arc::new(UserspaceMutex::new(&space, address)?);
slot.insert(mutex.clone());
Ok(mutex)
}
btree_map::Entry::Occupied(slot) => Ok(slot.get().clone()),
}
}
// Process list

View File

@ -214,10 +214,11 @@ pub(crate) fn mutex(mutex: &AtomicU32, op: &MutexOperation) -> Result<(), Error>
let thread = Thread::current();
let process = thread.process();
let mutex = process.get_or_insert_mutex((mutex as *const AtomicU32).addr());
let mutex = process.get_or_insert_mutex((mutex as *const AtomicU32).addr())?;
match op {
&MutexOperation::Wait(value, _timeout) => block! { mutex.wait(value).await },
&MutexOperation::WaitUntil(value, _timeout) => block! { mutex.wait_until(value).await },
MutexOperation::Wake => {
mutex.wake();
Ok(())

View File

@ -54,6 +54,8 @@ pub enum SpawnOption {
pub enum MutexOperation {
/// Waits on the mutex object until it is different from "compare value"
Wait(u32, Option<Duration>),
/// Waits on the mutex object until it becomes equal to the "compare value"
WaitUntil(u32, Option<Duration>),
/// Wakes a single mutex-waiting thread
Wake,
/// Wakes all threads waiting on the mutex

26
test.c
View File

@ -1,27 +1,31 @@
#include <pthread.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/yggdrasil.h>
_Thread_local int x = 123;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static void *function(void *arg) {
printf("This is thread %u\n", pthread_self());
printf("argument: %s\n", (const char *) arg);
printf("[child] x = %d\n", x);
x += 1;
printf("[child] x = %d\n", x);
pthread_barrier_t *barrier = (pthread_barrier_t *) arg;
printf("[child] waiting for parent\n");
pthread_barrier_wait(barrier);
printf("[child] barrier signalled!!\n");
return NULL;
}
int main(int argc, const char **argv) {
pthread_t thread;
printf("[main] &x = %p\n", &x);
printf("[main] x = %d\n", x);
x = 321;
printf("[main] x = %d\n", x);
pthread_barrier_t barrier;
assert(pthread_create(&thread, NULL, function, (void *) "thread1") == 0);
pthread_barrier_init(&barrier, NULL, 2);
printf("[main] will make the child wait on a barrier\n");
assert(pthread_create(&thread, NULL, function, (void *) &barrier) == 0);
sleep(3);
pthread_barrier_wait(&barrier);
printf("[main] barrier signalled!!\n");
pthread_join(thread, NULL);

View File

@ -15,6 +15,7 @@ const RENAMES: &[(&str, &str)] = &[
("CFdResult", "int"),
("COffsetResult", "off_t"),
("CPidResult", "pid_t"),
("AtomicU32", "uint32_t"),
];
fn include_dir(d: &DirEntry) -> bool {

View File

@ -0,0 +1,6 @@
#ifndef _YGGDRASIL_PTHREAD_H
#define _YGGDRASIL_PTHREAD_H 1
#define PTHREAD_MUTEX_INITIALIZER { 0 }
#endif

View File

@ -1,9 +1,119 @@
// PTHREAD_BARRIER_SERIAL_THREAD
// int pthread_barrier_destroy(pthread_barrier_t *);
// int pthread_barrier_init(pthread_barrier_t *restrict, const pthread_barrierattr_t *restrict, unsigned);
// int pthread_barrier_wait(pthread_barrier_t *);
// int pthread_barrierattr_destroy(pthread_barrierattr_t *);
// int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict, int *restrict);
// int pthread_barrierattr_init(pthread_barrierattr_t *);
// int pthread_barrierattr_setpshared(pthread_barrierattr_t *, int);
use core::{
ffi::{c_int, c_uint},
ptr::NonNull,
sync::atomic::{self, Ordering},
};
use yggdrasil_rt::process::MutexOperation;
use crate::{
error::{self, CIntZeroResult, CResult},
headers::{
errno,
sys_types::{pthread_barrier_t, pthread_barrierattr_t},
},
util::PointerExt,
};
use super::PTHREAD_PROCESS_PRIVATE;
impl pthread_barrier_t {
fn wait(&self) {
// Fastpath
if self.__state.fetch_add(1, Ordering::Release) >= self.__limit {
self.signal();
return;
}
loop {
if self.check() {
self.signal();
return;
}
self.block();
}
}
fn block(&self) {
unsafe {
yggdrasil_rt::sys::mutex(
&self.__state,
&MutexOperation::WaitUntil(self.__limit, None),
)
.ok()
};
}
fn signal(&self) {
unsafe { yggdrasil_rt::sys::mutex(&self.__state, &MutexOperation::WakeAll).ok() };
}
fn check(&self) -> bool {
self.__state.load(Ordering::Acquire) >= self.__limit
}
}
#[no_mangle]
unsafe extern "C" fn pthread_barrier_destroy(_barrier: *mut pthread_barrier_t) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_barrier_destroy()");
0
}
#[no_mangle]
unsafe extern "C" fn pthread_barrier_init(
barrier: *mut pthread_barrier_t,
_attr: *const pthread_barrierattr_t,
limit: c_uint,
) -> c_int {
let barrier = barrier.ensure_mut();
barrier.__state.store(0, Ordering::Release);
barrier.__limit = limit;
atomic::fence(Ordering::Release);
0
}
#[no_mangle]
unsafe extern "C" fn pthread_barrier_wait(barrier: *mut pthread_barrier_t) -> c_int {
let barrier = barrier.ensure_mut();
barrier.wait();
0
}
#[no_mangle]
unsafe extern "C" fn pthread_barrierattr_destroy(_attr: *mut pthread_barrierattr_t) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_barrierattr_destroy()");
0
}
#[no_mangle]
unsafe extern "C" fn pthread_barrierattr_getpshared(
_attr: *const pthread_barrierattr_t,
shared: *mut c_int,
) -> c_int {
if let Some(shared) = NonNull::new(shared) {
shared.write(PTHREAD_PROCESS_PRIVATE);
}
0
}
#[no_mangle]
unsafe extern "C" fn pthread_barrierattr_init(_attr: *mut pthread_barrierattr_t) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_barrierattr_init()");
0
}
#[no_mangle]
unsafe extern "C" fn pthread_barrierattr_setpshared(
_attr: *mut pthread_barrierattr_t,
shared: c_int,
) -> CIntZeroResult {
if shared == PTHREAD_PROCESS_PRIVATE {
CIntZeroResult::SUCCESS
} else {
yggdrasil_rt::debug_trace!("TODO: pthread_barrierattr_setpshared()");
error::errno = errno::EINVAL;
CIntZeroResult::ERROR
}
}

View File

@ -4,7 +4,8 @@ style = "Type"
sys_includes = [
"sys/types.h",
"sched.h",
"time.h"
"time.h",
"bits/pthread.h"
]
no_includes = true

View File

@ -1,5 +1,5 @@
use core::{
ffi::c_void,
ffi::{c_int, c_void},
mem::MaybeUninit,
ptr::null_mut,
sync::atomic::{AtomicPtr, Ordering},
@ -47,5 +47,12 @@ mod tls;
// PTHREAD_SCOPE_SYSTEM
//
pub const PTHREAD_PRIO_NONE: c_int = 0;
pub const PTHREAD_PRIO_INHERIOT: c_int = 1;
pub const PTHREAD_PRIO_PROTECT: c_int = 2;
pub const PTHREAD_PROCESS_PRIVATE: c_int = 0;
pub const PTHREAD_PROCESS_SHARED: c_int = 1;
// int pthread_once(pthread_once_t *, void (*)(void));

View File

@ -1,31 +1,286 @@
// PTHREAD_MUTEX_DEFAULT
// PTHREAD_MUTEX_ERRORCHECK
// PTHREAD_MUTEX_NORMAL
// PTHREAD_MUTEX_RECURSIVE
// PTHREAD_MUTEX_ROBUST
// PTHREAD_MUTEX_STALLED
//
// PTHREAD_MUTEX_INITIALIZER (macro)
use core::{ffi::c_int, ptr::NonNull, sync::atomic::Ordering};
use yggdrasil_rt::process::MutexOperation;
// int pthread_mutex_consistent(pthread_mutex_t *);
// int pthread_mutex_destroy(pthread_mutex_t *);
// int pthread_mutex_getprioceiling(const pthread_mutex_t *restrict, int *restrict);
// int pthread_mutex_init(pthread_mutex_t *restrict, const pthread_mutexattr_t *restrict);
// int pthread_mutex_lock(pthread_mutex_t *);
// int pthread_mutex_setprioceiling(pthread_mutex_t *restrict, int, int *restrict);
// int pthread_mutex_timedlock(pthread_mutex_t *restrict, const struct timespec *restrict);
// int pthread_mutex_trylock(pthread_mutex_t *);
// int pthread_mutex_unlock(pthread_mutex_t *);
// int pthread_mutexattr_destroy(pthread_mutexattr_t *);
// int pthread_mutexattr_getprioceiling( const pthread_mutexattr_t *restrict, int *restrict);
// int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *restrict, int *restrict);
// int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict, int *restrict);
// int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict, int *restrict);
// int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict, int *restrict);
// int pthread_mutexattr_init(pthread_mutexattr_t *);
// int pthread_mutexattr_setprioceiling(pthread_mutexattr_t *, int);
// int pthread_mutexattr_setprotocol(pthread_mutexattr_t *, int);
// int pthread_mutexattr_setpshared(pthread_mutexattr_t *, int);
// int pthread_mutexattr_setrobust(pthread_mutexattr_t *, int);
// int pthread_mutexattr_settype(pthread_mutexattr_t *, int);
use crate::{
error::{self, CIntZeroResult, CResult, EResult},
headers::{
errno,
pthread::PTHREAD_PRIO_NONE,
sys_time::__ygg_timespec_t,
sys_types::{pthread_mutex_t, pthread_mutexattr_t},
},
util::PointerExt,
};
use super::PTHREAD_PROCESS_PRIVATE;
impl pthread_mutex_t {
const UNLOCKED: u32 = 0;
const LOCKED: u32 = 1;
fn try_lock(&self) -> bool {
self.__state
.compare_exchange(
Self::UNLOCKED,
Self::LOCKED,
Ordering::Acquire,
Ordering::Relaxed,
)
.is_ok()
}
fn try_lock_err(&self) -> EResult<()> {
if self.try_lock() {
EResult::Ok(())
} else {
EResult::Err(errno::EBUSY)
}
}
fn lock(&self) {
loop {
if self.try_lock() {
return;
}
self.wait();
}
}
fn wait(&self) {
unsafe {
yggdrasil_rt::sys::mutex(&self.__state, &MutexOperation::Wait(Self::LOCKED, None)).ok()
};
}
unsafe fn release(&self) {
if self.__state.swap(Self::UNLOCKED, Ordering::Release) == Self::LOCKED {
yggdrasil_rt::sys::mutex(&self.__state, &MutexOperation::Wake).ok();
// TODO maybe yield for a fairer mutex?
}
}
}
pub const PTHREAD_MUTEX_NORMAL: c_int = 0;
pub const PTHREAD_MUTEX_RECURSIVE: c_int = 1;
pub const PTHREAD_MUTEX_ERRORCHECK: c_int = 2;
pub const PTHREAD_MUTEX_DEFAULT: c_int = PTHREAD_MUTEX_NORMAL;
pub const PTHREAD_MUTEX_STALLED: c_int = 0;
pub const PTHREAD_MUTEX_ROBUST: c_int = 1;
#[no_mangle]
unsafe extern "C" fn pthread_mutex_consistent(_mutex: *mut pthread_mutex_t) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_mutex_consistent()");
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutex_destroy(_mutex: *mut pthread_mutex_t) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_mutex_destroy()");
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutex_getprioceiling(
_mutex: *const pthread_mutex_t,
maxprio: *mut c_int,
) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_mutex_getprioceiling()");
if let Some(maxprio) = NonNull::new(maxprio) {
maxprio.write(10);
}
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutex_init(
mutex: *mut pthread_mutex_t,
_attr: *const pthread_mutexattr_t,
) -> c_int {
let mutex = mutex.ensure_mut();
mutex
.__state
.store(pthread_mutex_t::UNLOCKED, Ordering::Release);
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutex_lock(mutex: *mut pthread_mutex_t) -> c_int {
let mutex = mutex.ensure_mut();
mutex.lock();
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutex_setprioceiling(
_mutex: *mut pthread_mutex_t,
new: c_int,
old: *mut c_int,
) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_mutex_setprioceiling()");
if let Some(old) = NonNull::new(old) {
old.write(new);
}
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutex_timedlock(
_mutex: *mut pthread_mutex_t,
_timeout: *const __ygg_timespec_t,
) -> c_int {
todo!("pthread_mutex_timedlock()")
}
#[no_mangle]
unsafe extern "C" fn pthread_mutex_trylock(mutex: *mut pthread_mutex_t) -> CIntZeroResult {
let mutex = mutex.ensure_mut();
mutex.try_lock_err()?;
CIntZeroResult::SUCCESS
}
#[no_mangle]
unsafe extern "C" fn pthread_mutex_unlock(mutex: *mut pthread_mutex_t) -> c_int {
let mutex = mutex.ensure_mut();
mutex.release();
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_destroy(_attr: *mut pthread_mutexattr_t) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_mutexattr_destroy()");
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_getprioceiling(
_attr: *const pthread_mutexattr_t,
maxprio: *mut c_int,
) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_mutexattr_getprioceiling()");
if let Some(maxprio) = NonNull::new(maxprio) {
maxprio.write(10);
}
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_getprotocol(
_attr: *const pthread_mutexattr_t,
protocol: *mut c_int,
) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_mutexattr_getprotocol()");
if let Some(protocol) = NonNull::new(protocol) {
protocol.write(PTHREAD_PRIO_NONE);
}
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_getpshared(
_attr: *const pthread_mutexattr_t,
shared: *mut c_int,
) -> c_int {
if let Some(shared) = NonNull::new(shared) {
shared.write(PTHREAD_PROCESS_PRIVATE);
}
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_getrobust(
_attr: *const pthread_mutexattr_t,
robust: *mut c_int,
) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_mutexattr_getrobust()");
if let Some(robust) = NonNull::new(robust) {
robust.write(PTHREAD_MUTEX_STALLED);
}
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_gettype(
_attr: *const pthread_mutexattr_t,
ty: *mut c_int,
) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_mutexattr_gettype()");
if let Some(ty) = NonNull::new(ty) {
ty.write(PTHREAD_MUTEX_NORMAL);
}
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_init(_attr: *mut pthread_mutexattr_t) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_mutexattr_init()");
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_setprioceiling(
_attr: *mut pthread_mutexattr_t,
_maxprio: c_int,
) -> c_int {
yggdrasil_rt::debug_trace!("TODO: pthread_mutexattr_setprioceiling()");
0
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_setprotocol(
_attr: *mut pthread_mutexattr_t,
protocol: c_int,
) -> CIntZeroResult {
if protocol == PTHREAD_PRIO_NONE {
CIntZeroResult::SUCCESS
} else {
yggdrasil_rt::debug_trace!("TODO: pthread_mutexattr_setprotocol()");
error::errno = errno::EINVAL;
CIntZeroResult::ERROR
}
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_setpshared(
_attr: *mut pthread_mutexattr_t,
shared: c_int,
) -> CIntZeroResult {
if shared == PTHREAD_PROCESS_PRIVATE {
CIntZeroResult::SUCCESS
} else {
yggdrasil_rt::debug_trace!("TODO: pthread_mutexattr_setpshared()");
error::errno = errno::EINVAL;
CIntZeroResult::ERROR
}
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_setrobust(
_attr: *mut pthread_mutexattr_t,
robust: c_int,
) -> CIntZeroResult {
if robust == PTHREAD_MUTEX_STALLED {
CIntZeroResult::SUCCESS
} else {
yggdrasil_rt::debug_trace!("TODO: pthread_mutexattr_setrobust()");
error::errno = errno::EINVAL;
CIntZeroResult::ERROR
}
}
#[no_mangle]
unsafe extern "C" fn pthread_mutexattr_settype(
_attr: *mut pthread_mutexattr_t,
ty: c_int,
) -> CIntZeroResult {
if ty == PTHREAD_MUTEX_NORMAL {
CIntZeroResult::SUCCESS
} else {
yggdrasil_rt::debug_trace!("TODO: pthread_mutexattr_settype()");
error::errno = errno::EINVAL;
CIntZeroResult::ERROR
}
}

View File

@ -1,4 +1,4 @@
use core::ffi::{c_int, c_ulong, c_void};
use core::{ffi::{c_int, c_ulong, c_void}, sync::atomic::AtomicU32};
#[cfg(any(target_pointer_width = "64", rust_analyzer))]
mod bits64;
@ -51,7 +51,12 @@ pub struct pthread_attr_t {
pub stack_size: usize,
}
pub struct pthread_barrier_t {}
#[repr(C)]
pub struct pthread_barrier_t {
pub(crate) __state: AtomicU32,
pub(crate) __limit: u32,
}
#[repr(C)]
pub struct pthread_barrierattr_t {}
#[repr(C)]
@ -64,8 +69,9 @@ pub struct pthread_key_t {}
#[repr(C)]
pub struct pthread_mutex_t {
__dummy: c_int
pub(crate) __state: AtomicU32
}
#[repr(C)]
pub struct pthread_mutexattr_t {}
pub struct pthread_once_t {}