add initial pthread implementation.
git-svn-id: https://rt-thread.googlecode.com/svn/trunk@1044 bbd45198-f89e-11dd-88c7-29a3b14d5316
This commit is contained in:
parent
7018ab0661
commit
25f50375ef
|
@ -0,0 +1,276 @@
|
|||
#include "pthread.h"
|
||||
|
||||
#define PTHREAD_MAGIC 0x70746873
|
||||
struct _pthread_data
|
||||
{
|
||||
rt_uint32_t magic;
|
||||
pthread_attr_t attr;
|
||||
rt_thread_t tid;
|
||||
|
||||
void* (*thread_entry)(void* parameter);
|
||||
void* thread_parameter;
|
||||
|
||||
/* return value */
|
||||
void* return_value;
|
||||
|
||||
/* semaphore for joinable thread */
|
||||
rt_sem_t joinable_sem;
|
||||
|
||||
void** tls; /* thread-local storage area */
|
||||
};
|
||||
typedef struct _pthread_data _pthread_data_t;
|
||||
|
||||
rt_inline _pthread_data_t* get_pthread_data(pthread_t thread)
|
||||
{
|
||||
RT_ASSERT(thread != RT_NULL);
|
||||
|
||||
return (_pthread_data_t*)thread->user_data;
|
||||
}
|
||||
|
||||
int pthread_system_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _pthread_cleanup(rt_thread_t tid)
|
||||
{
|
||||
_pthread_data_t *ptd;
|
||||
ptd = get_pthread_data(tid);
|
||||
|
||||
/* clear cleanup function */
|
||||
tid->cleanup = RT_NULL;
|
||||
if (ptd->attr.detachstate == PTHREAD_CREATE_JOINABLE)
|
||||
{
|
||||
rt_sem_release(ptd->joinable_sem);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* release pthread resource */
|
||||
pthread_detach(tid);
|
||||
}
|
||||
}
|
||||
|
||||
static void pthread_entry_stub(void* parameter)
|
||||
{
|
||||
_pthread_data_t *ptd;
|
||||
void* value;
|
||||
|
||||
ptd = (_pthread_data_t*)parameter;
|
||||
|
||||
/* execute pthread entry */
|
||||
value = ptd->thread_entry(ptd->thread_parameter);
|
||||
/* set value */
|
||||
ptd->return_value = value;
|
||||
}
|
||||
|
||||
int pthread_create (pthread_t *tid, const pthread_attr_t *attr,
|
||||
void *(*start) (void *), void *parameter)
|
||||
{
|
||||
int result;
|
||||
void* stack;
|
||||
char name[RT_NAME_MAX];
|
||||
static rt_uint16_t pthread_number = 0;
|
||||
_pthread_data_t *ptd;
|
||||
|
||||
/* tid shall be provided */
|
||||
RT_ASSERT(tid != RT_NULL);
|
||||
|
||||
/* allocate pthread data */
|
||||
ptd = (_pthread_data_t*)rt_malloc(sizeof(_pthread_data_t));
|
||||
if (ptd == RT_NULL) return ENOMEM;
|
||||
/* clean memory */
|
||||
rt_memset(ptd, 0, sizeof(_pthread_data_t));
|
||||
|
||||
if (attr != RT_NULL) ptd->attr = *attr;
|
||||
else
|
||||
{
|
||||
/* use default attribute */
|
||||
pthread_attr_init(&ptd->attr);
|
||||
}
|
||||
|
||||
rt_snprintf(name, sizeof(name), "pth%02d", pthread_number ++);
|
||||
if (ptd->attr.stack_base == 0)
|
||||
{
|
||||
stack = (void*)rt_malloc(ptd->attr.stack_size);
|
||||
}
|
||||
else stack = (void*)(ptd->attr.stack_base);
|
||||
|
||||
if (stack == RT_NULL)
|
||||
{
|
||||
rt_free(ptd);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
/* pthread is a static thread object */
|
||||
ptd->tid = (rt_thread_t) rt_malloc(sizeof(struct rt_thread));
|
||||
if (ptd->tid == RT_NULL)
|
||||
{
|
||||
if (ptd->attr.stack_base ==0) rt_free(stack);
|
||||
rt_free(ptd);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
if (ptd->attr.detachstate == PTHREAD_CREATE_JOINABLE)
|
||||
{
|
||||
ptd->joinable_sem = rt_sem_create(name, 0, RT_IPC_FLAG_FIFO);
|
||||
if (ptd->joinable_sem == RT_NULL)
|
||||
{
|
||||
if (ptd->attr.stack_base !=0) rt_free(stack);
|
||||
rt_free(ptd);
|
||||
return ENOMEM;
|
||||
}
|
||||
}
|
||||
else ptd->joinable_sem = RT_NULL;
|
||||
|
||||
/* set parameter */
|
||||
ptd->thread_entry = start;
|
||||
ptd->thread_parameter = parameter;
|
||||
|
||||
/* initial this pthread to system */
|
||||
if (rt_thread_init(ptd->tid, name, pthread_entry_stub, ptd,
|
||||
stack, ptd->attr.stack_size,
|
||||
ptd->attr.priority, 5) != RT_EOK)
|
||||
{
|
||||
if (ptd->attr.stack_base ==0) rt_free(stack);
|
||||
if (ptd->joinable_sem != RT_NULL) rt_sem_delete(ptd->joinable_sem);
|
||||
rt_free(ptd);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
/* set pthread id */
|
||||
*tid = ptd->tid;
|
||||
|
||||
/* set pthread cleanup function and ptd data */
|
||||
(*tid)->cleanup = _pthread_cleanup;
|
||||
(*tid)->user_data = (rt_uint32_t)ptd;
|
||||
|
||||
/* start thread */
|
||||
result = rt_thread_startup(*tid);
|
||||
if (result == RT_EOK) return 0;
|
||||
|
||||
/* start thread failed */
|
||||
rt_thread_detach(ptd->tid);
|
||||
if (ptd->attr.stack_base ==0) rt_free(stack);
|
||||
if (ptd->joinable_sem != RT_NULL) rt_sem_delete(ptd->joinable_sem);
|
||||
|
||||
rt_free(ptd);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
int pthread_detach(pthread_t thread)
|
||||
{
|
||||
_pthread_data_t* ptd;
|
||||
|
||||
ptd = get_pthread_data(thread);
|
||||
|
||||
if (thread->stat == RT_THREAD_CLOSE)
|
||||
{
|
||||
/* delete joinable semaphore */
|
||||
if (ptd->joinable_sem != RT_NULL)
|
||||
rt_sem_delete(ptd->joinable_sem);
|
||||
/* detach thread object */
|
||||
rt_thread_detach(ptd->tid);
|
||||
|
||||
/* release thread resource */
|
||||
if (ptd->attr.stack_base == RT_NULL)
|
||||
{
|
||||
/* release thread allocated stack */
|
||||
rt_free(ptd->tid->stack_addr);
|
||||
}
|
||||
rt_free(ptd->tid);
|
||||
rt_free(ptd);
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_enter_critical();
|
||||
/* change to detach state */
|
||||
ptd->attr.detachstate = PTHREAD_CREATE_DETACHED;
|
||||
rt_exit_critical();
|
||||
|
||||
/* detach joinable semaphore */
|
||||
rt_sem_delete(ptd->joinable_sem);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_join (pthread_t thread, void **value_ptr)
|
||||
{
|
||||
_pthread_data_t* ptd;
|
||||
rt_err_t result;
|
||||
|
||||
if (thread == rt_thread_self())
|
||||
{
|
||||
/* join self */
|
||||
return EDEADLK;
|
||||
}
|
||||
|
||||
ptd = get_pthread_data(thread);
|
||||
if (ptd->attr.detachstate == PTHREAD_CREATE_DETACHED)
|
||||
return EINVAL; /* join on a detached pthread */
|
||||
|
||||
result = rt_sem_take(ptd->joinable_sem, RT_WAITING_FOREVER);
|
||||
if (result == RT_EOK)
|
||||
{
|
||||
/* get return value */
|
||||
if (value_ptr != RT_NULL) *value_ptr = ptd->return_value;
|
||||
|
||||
/* release resource */
|
||||
pthread_detach(thread);
|
||||
}
|
||||
else return ESRCH;
|
||||
}
|
||||
|
||||
int pthread_cancel (pthread_t thread)
|
||||
{
|
||||
_pthread_data_t* ptd;
|
||||
|
||||
ptd = get_pthread_data(thread);
|
||||
|
||||
/* check cancel point */
|
||||
}
|
||||
|
||||
void pthread_exit (void* value)
|
||||
{
|
||||
_pthread_data_t* ptd;
|
||||
|
||||
ptd = get_pthread_data(rt_thread_self());
|
||||
|
||||
/* set return value */
|
||||
ptd->return_value = value;
|
||||
if (ptd->attr.detachstate == PTHREAD_CREATE_JOINABLE)
|
||||
{
|
||||
/* release the joinable pthread */
|
||||
rt_sem_release(ptd->joinable_sem);
|
||||
}
|
||||
|
||||
/* detach thread */
|
||||
rt_thread_detach(ptd->tid);
|
||||
/* reschedule thread */
|
||||
rt_schedule();
|
||||
}
|
||||
|
||||
int pthread_once(pthread_once_t * once_control, void (*init_routine) (void))
|
||||
{
|
||||
RT_ASSERT(once_control != RT_NULL);
|
||||
RT_ASSERT(init_routine != RT_NULL);
|
||||
|
||||
rt_enter_critical();
|
||||
if (!(*once_control))
|
||||
{
|
||||
/* call routine once */
|
||||
*once_control = 1;
|
||||
rt_exit_critical();
|
||||
|
||||
init_routine();
|
||||
}
|
||||
rt_exit_critical();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void))
|
||||
{
|
||||
return ENOTSUP;
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* File : pthread.h
|
||||
* This file is part of RT-Thread RTOS
|
||||
* COPYRIGHT (C) 2006 - 2010, RT-Thread Development Team
|
||||
*
|
||||
* The license and distribution terms for this file may be
|
||||
* found in the file LICENSE in this distribution or at
|
||||
* http://www.rt-thread.org/license/LICENSE
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2010-10-26 Bernard the first version
|
||||
*/
|
||||
#ifndef __PTHREAD_H__
|
||||
#define __PTHREAD_H__
|
||||
|
||||
#include <rtthread.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "pthread_attr.h"
|
||||
#include "pthread_mutex.h"
|
||||
|
||||
typedef rt_thread_t pthread_t;
|
||||
|
||||
typedef long pthread_condattr_t;
|
||||
typedef int pthread_key_t;
|
||||
typedef int pthread_once_t;
|
||||
|
||||
enum {
|
||||
PTHREAD_CANCEL_ASYNCHRONOUS = 0,
|
||||
PTHREAD_CANCEL_ENABLE,
|
||||
PTHREAD_CANCEL_DEFERRED,
|
||||
PTHREAD_CANCEL_DISABLE,
|
||||
PTHREAD_CANCELED
|
||||
};
|
||||
|
||||
#define PTHREAD_COND_INITIALIZER
|
||||
#define PTHREAD_RWLOCK_INITIALIZER
|
||||
#define PTHREAD_MUTEX_INITIALIZER {-1, 0}
|
||||
|
||||
#define PTHREAD_CREATE_JOINABLE 0x00
|
||||
#define PTHREAD_CREATE_DETACHED 0x01
|
||||
|
||||
#define PTHREAD_EXPLICIT_SCHED 0
|
||||
#define PTHREAD_INHERIT_SCHED 1
|
||||
|
||||
enum {
|
||||
PTHREAD_MUTEX_NORMAL = 0,
|
||||
PTHREAD_MUTEX_RECURSIVE = 1,
|
||||
PTHREAD_MUTEX_ERRORCHECK = 2,
|
||||
PTHREAD_MUTEX_ERRORCHECK_NP = PTHREAD_MUTEX_ERRORCHECK,
|
||||
PTHREAD_MUTEX_RECURSIVE_NP = PTHREAD_MUTEX_RECURSIVE,
|
||||
PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL
|
||||
};
|
||||
|
||||
/* init value for pthread_once_t */
|
||||
#define PTHREAD_ONCE_INIT 0
|
||||
|
||||
enum {
|
||||
PTHREAD_PRIO_INHERIT =0,
|
||||
PTHREAD_PRIO_NONE,
|
||||
PTHREAD_PRIO_PROTECT,
|
||||
};
|
||||
|
||||
#define PTHREAD_PROCESS_PRIVATE 0
|
||||
#define PTHREAD_PROCESS_SHARED 1
|
||||
|
||||
|
||||
#define PTHREAD_SCOPE_PROCESS 0
|
||||
#define PTHREAD_SCOPE_SYSTEM 1
|
||||
|
||||
struct sched_param {
|
||||
int sched_priority;
|
||||
};
|
||||
|
||||
/*
|
||||
* Scheduling policies required by IEEE Std 1003.1-2001
|
||||
*/
|
||||
#define SCHED_OTHER 0 /* Behavior can be FIFO or RR, or not */
|
||||
#define SCHED_FIFO 1
|
||||
#define SCHED_RR 2
|
||||
|
||||
int pthread_init (void);
|
||||
int pthread_create (pthread_t *tid, const pthread_attr_t *attr,
|
||||
void *(*start) (void *), void *arg);
|
||||
|
||||
int pthread_detach (pthread_t thread);
|
||||
int pthread_join (pthread_t thread, void **value_ptr);
|
||||
|
||||
rt_inline int pthread_equal (pthread_t t1, pthread_t t2)
|
||||
{
|
||||
return t1 == t2;
|
||||
}
|
||||
|
||||
rt_inline pthread_t pthread_self (void)
|
||||
{
|
||||
return rt_thread_self();
|
||||
}
|
||||
|
||||
void pthread_exit (void *value_ptr);
|
||||
|
||||
int pthread_once(pthread_once_t * once_control, void (*init_routine) (void));
|
||||
|
||||
/* pthread cancel */
|
||||
int pthread_cancel(pthread_t thread);
|
||||
void pthread_testcancel(void);
|
||||
|
||||
int pthread_setcancelstate(int state, int *oldstate);
|
||||
int pthread_setcanceltype(int type, int *oldtype);
|
||||
|
||||
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
#include <rtthread.h>
|
||||
#include "pthread.h"
|
||||
#include <string.h>
|
||||
|
||||
#define DEFAULT_STACK_SIZE 2048
|
||||
#define DEFAULT_PRIORITY (RT_THREAD_PRIORITY_MAX/2 + RT_THREAD_PRIORITY_MAX/4)
|
||||
|
||||
const pthread_attr_t pthread_default_attr =
|
||||
{
|
||||
0, /* stack base */
|
||||
DEFAULT_STACK_SIZE, /* stack size */
|
||||
DEFAULT_PRIORITY, /* priority */
|
||||
PTHREAD_CREATE_JOINABLE, /* detach state */
|
||||
SCHED_FIFO, /* scheduler policy */
|
||||
PTHREAD_INHERIT_SCHED /* Inherit parent prio/policy */
|
||||
};
|
||||
|
||||
int pthread_attr_init(pthread_attr_t *attr)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
|
||||
*attr = pthread_default_attr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_destroy(pthread_attr_t *attr)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
|
||||
memset(attr, 0, sizeof(pthread_attr_t));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_setdetachstate(pthread_attr_t * attr, int state)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
|
||||
if (state != PTHREAD_CREATE_JOINABLE && state != PTHREAD_CREATE_DETACHED)
|
||||
return EINVAL;
|
||||
|
||||
attr->detachstate = state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_getdetachstate(pthread_attr_t const * attr, int *state)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
|
||||
*state = (int)attr->detachstate;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_setschedpolicy(pthread_attr_t * attr, int policy)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
|
||||
attr->policy = policy;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_getschedpolicy(pthread_attr_t const *attr, int *policy)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
|
||||
*policy = (int)attr->policy;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param const *param)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
RT_ASSERT(param != RT_NULL);
|
||||
|
||||
attr->priority = param->sched_priority;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_getschedparam(pthread_attr_t const *attr, struct sched_param *param)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
RT_ASSERT(param != RT_NULL);
|
||||
|
||||
param->sched_priority = attr->priority;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_setstacksize(pthread_attr_t * attr, size_t stack_size)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
|
||||
attr->stack_size = stack_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_getstacksize(pthread_attr_t const * attr, size_t *stack_size)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
|
||||
*stack_size = attr->stack_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_setstackaddr(pthread_attr_t * attr, void * stack_addr)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
return ENOTSUP;
|
||||
}
|
||||
|
||||
int pthread_attr_getstackaddr(pthread_attr_t const * attr, void ** stack_addr)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
return ENOTSUP;
|
||||
}
|
||||
|
||||
int pthread_attr_setstack(pthread_attr_t * attr, void *stack_base, size_t stack_size)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
|
||||
attr->stack_base = stack_base;
|
||||
attr->stack_size = RT_ALIGN_DOWN(stack_size, RT_ALIGN_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_getstack(pthread_attr_t const * attr, void **stack_base, size_t *stack_size)
|
||||
{
|
||||
RT_ASSERT(attr != RT_NULL);
|
||||
|
||||
*stack_base = attr->stack_base;
|
||||
*stack_size = attr->stack_size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_attr_setguardsize(pthread_attr_t * attr, size_t guard_size)
|
||||
{
|
||||
return ENOTSUP;
|
||||
}
|
||||
|
||||
int pthread_attr_getguardsize(pthread_attr_t const * attr, size_t *guard_size)
|
||||
{
|
||||
return ENOTSUP;
|
||||
}
|
||||
|
||||
int pthread_attr_setscope(pthread_attr_t *attr, int scope)
|
||||
{
|
||||
if (scope == PTHREAD_SCOPE_SYSTEM)
|
||||
return 0;
|
||||
if (scope == PTHREAD_SCOPE_PROCESS)
|
||||
return ENOTSUP;
|
||||
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
int pthread_attr_getscope(pthread_attr_t const *attr)
|
||||
{
|
||||
return PTHREAD_SCOPE_SYSTEM;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef __PTHREAD_ATTR_H__
|
||||
#define __PTHREAD_ATTR_H__
|
||||
|
||||
struct pthread_attr
|
||||
{
|
||||
void* stack_base;
|
||||
rt_uint16_t stack_size; /* stack size of thread */
|
||||
|
||||
rt_uint8_t priority; /* priority of thread */
|
||||
rt_uint8_t detachstate; /* detach state */
|
||||
rt_uint8_t policy; /* scheduler policy */
|
||||
rt_uint8_t inheritsched; /* Inherit parent prio/policy */
|
||||
};
|
||||
typedef struct pthread_attr pthread_attr_t;
|
||||
extern const pthread_attr_t pthread_default_attr;
|
||||
|
||||
int pthread_attr_destroy(pthread_attr_t *attr);
|
||||
int pthread_attr_init(pthread_attr_t *attr);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,57 @@
|
|||
#include "pthread_cond.h"
|
||||
|
||||
int pthread_condattr_getpshared(const pthread_condattr_t *attr, int *pshared)
|
||||
{
|
||||
if (!attr || !pshared) return EINVAL;
|
||||
|
||||
*pshared = PTHREAD_PROCESS_PRIVATE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_condattr_setpshared(pthread_condattr_t*attr, int pshared)
|
||||
{
|
||||
if ((pshared != PTHREAD_PROCESS_PRIVATE) && (pshared != PTHREAD_PROCESS_SHARED))
|
||||
return EINVAL;
|
||||
|
||||
if (pshared != PTHREAD_PROCESS_PRIVATE)
|
||||
return ENOSYS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr)
|
||||
{
|
||||
/* parameter check */
|
||||
if (cond == RT_NULL) return EINVAL;
|
||||
if ((attr != RT_NULL) && (*attr != PTHREAD_PROCESS_PRIVATE)) return EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_cond_destroy(pthread_cond_t *cond)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_cond_broadcast(pthread_cond_t *cond)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_cond_signal(pthread_cond_t *cond)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_cond_timedwait(pthread_cond_t *cond,
|
||||
pthread_mutex_t * mutex,
|
||||
const struct timespec *abstime)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef __PTHREAD_COND_H__
|
||||
#define __PTHREAD_COND_H__
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
struct pthread_cond
|
||||
{
|
||||
pthread_mutex_t *mutex;
|
||||
};
|
||||
typedef struct pthread_cond pthread_cond_t;
|
||||
|
||||
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
|
||||
int pthread_cond_destroy(pthread_cond_t *cond);
|
||||
int pthread_cond_broadcast(pthread_cond_t *cond);
|
||||
int pthread_cond_signal(pthread_cond_t *cond);
|
||||
|
||||
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
|
||||
int pthread_cond_timedwait(pthread_cond_t *cond,
|
||||
pthread_mutex_t * mutex,
|
||||
const struct timespec *abstime);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,193 @@
|
|||
#include <rtthread.h>
|
||||
#include "pthread.h"
|
||||
|
||||
#define MUTEXATTR_SHARED_MASK 0x0010
|
||||
#define MUTEXATTR_TYPE_MASK 0x000f
|
||||
|
||||
const pthread_mutexattr_t pthread_default_mutexattr = PTHREAD_PROCESS_PRIVATE;
|
||||
|
||||
int pthread_mutexattr_init(pthread_mutexattr_t *attr)
|
||||
{
|
||||
if (attr)
|
||||
{
|
||||
*attr = pthread_default_mutexattr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr)
|
||||
{
|
||||
if (attr)
|
||||
{
|
||||
*attr = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type)
|
||||
{
|
||||
if (attr && type)
|
||||
{
|
||||
int atype = (*attr & MUTEXATTR_TYPE_MASK);
|
||||
|
||||
if (atype >= PTHREAD_MUTEX_NORMAL && atype <= PTHREAD_MUTEX_ERRORCHECK)
|
||||
{
|
||||
*type = atype;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type)
|
||||
{
|
||||
if (attr && type >= PTHREAD_MUTEX_NORMAL &&
|
||||
type <= PTHREAD_MUTEX_ERRORCHECK )
|
||||
{
|
||||
*attr = (*attr & ~MUTEXATTR_TYPE_MASK) | type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared)
|
||||
{
|
||||
if (!attr) return EINVAL;
|
||||
|
||||
switch (pshared)
|
||||
{
|
||||
case PTHREAD_PROCESS_PRIVATE:
|
||||
*attr &= ~MUTEXATTR_SHARED_MASK;
|
||||
return 0;
|
||||
|
||||
case PTHREAD_PROCESS_SHARED:
|
||||
*attr |= MUTEXATTR_SHARED_MASK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int *pshared)
|
||||
{
|
||||
if (!attr || !pshared) return EINVAL;
|
||||
|
||||
*pshared = (*attr & MUTEXATTR_SHARED_MASK) ? PTHREAD_PROCESS_SHARED
|
||||
: PTHREAD_PROCESS_PRIVATE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
|
||||
{
|
||||
rt_err_t result;
|
||||
char name[RT_NAME_MAX];
|
||||
static rt_uint16_t pthread_mutex_number = 0;
|
||||
|
||||
if (!mutex) return EINVAL;
|
||||
|
||||
/* build mutex name */
|
||||
rt_snprintf(name, sizeof(name), "pmtx%02d", pthread_mutex_number ++);
|
||||
if (attr == RT_NULL) mutex->attr = pthread_default_mutexattr;
|
||||
else mutex->attr = *attr;
|
||||
|
||||
/* init mutex lock */
|
||||
result = rt_mutex_init(&(mutex->lock), name, RT_IPC_FLAG_FIFO);
|
||||
if (result != RT_EOK) return EINVAL;
|
||||
|
||||
/* detach the object from system object container */
|
||||
rt_object_detach(&(mutex->lock.parent.parent));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_mutex_destroy(pthread_mutex_t *mutex)
|
||||
{
|
||||
if (!mutex || mutex->attr == -1) return EINVAL;
|
||||
|
||||
/* it's busy */
|
||||
if (mutex->lock.owner != RT_NULL) return EBUSY;
|
||||
|
||||
rt_memset(mutex, 0, sizeof(pthread_mutex_t));
|
||||
mutex->attr = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_mutex_lock(pthread_mutex_t *mutex)
|
||||
{
|
||||
int mtype;
|
||||
rt_err_t result;
|
||||
|
||||
if (!mutex) return EINVAL;
|
||||
|
||||
if (mutex->attr == -1)
|
||||
{
|
||||
/* init mutex */
|
||||
pthread_mutex_init(mutex, RT_NULL);
|
||||
}
|
||||
|
||||
mtype = mutex->attr & MUTEXATTR_TYPE_MASK;
|
||||
rt_enter_critical();
|
||||
if (mutex->lock.owner == rt_thread_self() && mtype != PTHREAD_MUTEX_RECURSIVE)
|
||||
{
|
||||
rt_exit_critical();
|
||||
return EDEADLK;
|
||||
}
|
||||
rt_exit_critical();
|
||||
|
||||
result = rt_mutex_take(&(mutex->lock), RT_WAITING_FOREVER);
|
||||
if (result == RT_EOK) return 0;
|
||||
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
int pthread_mutex_unlock(pthread_mutex_t *mutex)
|
||||
{
|
||||
rt_err_t result;
|
||||
|
||||
if (!mutex) return EINVAL;
|
||||
if (mutex->attr == -1)
|
||||
{
|
||||
/* init mutex */
|
||||
pthread_mutex_init(mutex, RT_NULL);
|
||||
}
|
||||
|
||||
if (mutex->lock.owner != rt_thread_self())
|
||||
{
|
||||
int mtype;
|
||||
mtype = mutex->attr & MUTEXATTR_TYPE_MASK;
|
||||
|
||||
/* error check, return EPERM */
|
||||
if (mtype == PTHREAD_MUTEX_ERRORCHECK) return EPERM;
|
||||
|
||||
/* no thread waiting on this mutex */
|
||||
if (mutex->lock.owner == RT_NULL) return 0;
|
||||
}
|
||||
|
||||
result = rt_mutex_release(&(mutex->lock));
|
||||
if (result == RT_EOK) return 0;
|
||||
}
|
||||
|
||||
int pthread_mutex_trylock(pthread_mutex_t *mutex)
|
||||
{
|
||||
rt_err_t result;
|
||||
|
||||
if (!mutex) return EINVAL;
|
||||
if (mutex->attr == -1)
|
||||
{
|
||||
/* init mutex */
|
||||
pthread_mutex_init(mutex, RT_NULL);
|
||||
}
|
||||
|
||||
result = rt_mutex_take(&(mutex->lock), 0);
|
||||
if (result == RT_EOK) return 0;
|
||||
|
||||
return EBUSY;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef __PTHREAD_MUTEX_H__
|
||||
#define __PTHREAD_MUTEX_H__
|
||||
|
||||
typedef long pthread_mutexattr_t;
|
||||
struct pthread_mutex
|
||||
{
|
||||
pthread_mutexattr_t attr;
|
||||
struct rt_mutex lock;
|
||||
};
|
||||
typedef struct pthread_mutex pthread_mutex_t;
|
||||
|
||||
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
|
||||
int pthread_mutex_destroy(pthread_mutex_t *mutex);
|
||||
int pthread_mutex_lock(pthread_mutex_t *mutex);
|
||||
int pthread_mutex_unlock(pthread_mutex_t *mutex);
|
||||
int pthread_mutex_trylock(pthread_mutex_t *mutex);
|
||||
|
||||
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
|
||||
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
|
||||
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type);
|
||||
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
|
||||
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
|
||||
int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int *pshared);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef __PTHREAD_RWLOCK_H__
|
||||
#define __PTHREAD_RWLOCK_H__
|
||||
|
||||
int pthread_rwlock_init (pthread_rwlock_t * rwlock, const pthread_rwlockattr_t * attr);
|
||||
int pthread_rwlock_destroy (pthread_rwlock_t * rwlock);
|
||||
|
||||
int pthread_rwlock_rdlock (pthread_rwlock_t * rwlock);
|
||||
int pthread_rwlock_tryrdlock (pthread_rwlock_t * rwlock);
|
||||
int pthread_rwlock_timedrdlock (pthread_rwlock_t * rwlock, const struct timespec *abstime);
|
||||
|
||||
int pthread_rwlock_timedwrlock (pthread_rwlock_t * rwlock, const struct timespec *abstime);
|
||||
int pthread_rwlock_trywrlock (pthread_rwlock_t * rwlock);
|
||||
int pthread_rwlock_unlock (pthread_rwlock_t * rwlock);
|
||||
int pthread_rwlock_wrlock (pthread_rwlock_t * rwlock);
|
||||
|
||||
int pthread_rwlockattr_init (pthread_rwlockattr_t * attr);
|
||||
int pthread_rwlockattr_destroy (pthread_rwlockattr_t * attr);
|
||||
int pthread_rwlockattr_getpshared (const pthread_rwlockattr_t * attr, int *pshared);
|
||||
int pthread_rwlockattr_setpshared (pthread_rwlockattr_t * attr, int pshared);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,26 @@
|
|||
#include "pthread_spin.h"
|
||||
|
||||
int pthread_spin_init (pthread_spinlock_t *lock, int pshared)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_spin_destroy (pthread_spinlock_t *lock)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_spin_lock (pthread_spinlock_t * lock)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_spin_trylock (pthread_spinlock_t * lock)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_spin_unlock (pthread_spinlock_t * lock)
|
||||
{
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef __PTHREAD_SPIN_H__
|
||||
#define __PTHREAD_SPIN_H__
|
||||
#include <pthread.h>
|
||||
|
||||
struct pthread_spinlock
|
||||
{
|
||||
int pshared;
|
||||
};
|
||||
typedef struct pthread_spinlock pthread_spinlock_t;
|
||||
|
||||
int pthread_spin_init (pthread_spinlock_t *lock, int pshared);
|
||||
int pthread_spin_destroy (pthread_spinlock_t *lock);
|
||||
|
||||
int pthread_spin_lock (pthread_spinlock_t * lock);
|
||||
int pthread_spin_trylock (pthread_spinlock_t * lock);
|
||||
int pthread_spin_unlock (pthread_spinlock_t * lock);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,127 @@
|
|||
#include <pthread.h>
|
||||
#include <finsh.h>
|
||||
|
||||
#define _die_(x) do { rt_kprintf(x); RT_ASSERT(0); } while (0)
|
||||
#define pr(x) do { rt_kprintf(x); } while (0)
|
||||
#define sleep(n) rt_thread_sleep((n * RT_TICK_PER_SECOND)/1000)
|
||||
#define alarm(n)
|
||||
|
||||
/* (0) once test */
|
||||
void test0_ok() { pr("(once called) "); }
|
||||
void test0_failed() { _die_("failed...\n"); }
|
||||
void pth_t0() {
|
||||
pthread_once_t v_once=PTHREAD_ONCE_INIT;
|
||||
pr("\nTEST 0: once test:\n\n");
|
||||
pr("testing once function... ");
|
||||
pthread_once(&v_once,test0_ok);
|
||||
pthread_once(&v_once,test0_failed);
|
||||
pr("OK.\n");
|
||||
}
|
||||
FINSH_FUNCTION_EXPORT(pth_t0, pthread testcase0);
|
||||
|
||||
/* (1) mutex tests */
|
||||
void test_rec_mutex() {
|
||||
pthread_mutex_t tm;
|
||||
pthread_mutexattr_t ta;
|
||||
pthread_mutexattr_settype(&ta, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&tm, &ta);
|
||||
pr("testing recursive mutex... ");
|
||||
alarm(5);
|
||||
if (pthread_mutex_lock(&tm) != 0) _die_("failed... mutex_lock on unused rec-mutex (c=0)...\n");
|
||||
if (tm.lock.owner!=pthread_self()) _die_("failed.. wrong owner....\n");
|
||||
if (tm.lock.hold!=1) _die_("failed... wrong counting (c!=1)....\n");
|
||||
if (pthread_mutex_lock(&tm) != 0) _die_("failed... mutex_lock on taken rec-mutex (c=1)...\n");
|
||||
if (tm.lock.hold!=2) _die_("failed... wrong counting (c!=2)....\n");
|
||||
if (pthread_mutex_lock(&tm) != 0) _die_("failed... mutex_lock on taken rec-mutex (c=2)...\n");
|
||||
if (tm.lock.hold!=3) _die_("failed... wrong counting (c!=3)....\n");
|
||||
if (pthread_mutex_unlock(&tm) != 0) _die_("failed... mutex_unlock on taken rec-mutex (c=3)...\n");
|
||||
if (tm.lock.hold!=2) _die_("failed... wrong counting (c!=2)....\n");
|
||||
if (tm.lock.owner==0) _die_("failed... mutex has no owner?!?!\n");
|
||||
if (pthread_mutex_unlock(&tm) != 0) _die_("failed... mutex_unlock on taken rec-mutex (c=2)...\n");
|
||||
if (tm.lock.hold!=1) _die_("failed... wrong counting (c!=1)....\n");
|
||||
if (tm.lock.owner==0) _die_("failed... mutex has no owner?!?!\n");
|
||||
if (pthread_mutex_unlock(&tm) != 0) _die_("failed... mutex_unlock on taken rec-mutex (c=1)...\n");
|
||||
if (tm.lock.hold!=0) _die_("failed... wrong counting (c!=0)....\n");
|
||||
if (tm.lock.owner!=0) _die_("failed... mutex still owned ?!?!\n");
|
||||
if (pthread_mutex_unlock(&tm) != 0) _die_("failed... mutex_unlock on free rec-mutex (c=0)...\n");
|
||||
alarm(0);
|
||||
pr("OK.\n");
|
||||
}
|
||||
|
||||
void test_err_mutex() {
|
||||
pthread_mutex_t tm;
|
||||
pthread_mutexattr_t ta;
|
||||
|
||||
pthread_mutexattr_settype(&ta, PTHREAD_MUTEX_ERRORCHECK);
|
||||
pthread_mutex_init(&tm, &ta);
|
||||
pr("testing errorcheck mutex... ");
|
||||
alarm(5);
|
||||
if (pthread_mutex_lock(&tm) != 0) _die_("failed... mutex_lock on unused errchk-mutex...\n");
|
||||
if (tm.lock.owner!=pthread_self()) _die_("failed.. wrong owner....\n");
|
||||
if (pthread_mutex_lock(&tm) != EDEADLK) _die_("failed... mutex_lock on taken errchk-mutex...\n");
|
||||
if (pthread_mutex_unlock(&tm) != 0) _die_("failed... mutex_unlock on taken errchk-mutex...\n");
|
||||
if (tm.lock.owner!=0) _die_("failed... mutex still owned ?!?!\n");
|
||||
if (pthread_mutex_unlock(&tm) != EPERM) _die_("failed... mutex_unlock on free errchk-mutex...\n");
|
||||
alarm(0);
|
||||
pr("OK.\n");
|
||||
}
|
||||
|
||||
void pth_t1() {
|
||||
pr("\nTEST 1: mutex test:\n\n");
|
||||
test_rec_mutex();
|
||||
test_err_mutex();
|
||||
}
|
||||
FINSH_FUNCTION_EXPORT(pth_t1, pthread testcase0);
|
||||
|
||||
void* thread(void*arg)
|
||||
{
|
||||
if (0) { arg=0; }
|
||||
pr("(thread created) ");
|
||||
sleep(1);
|
||||
pr("(thread exit) ");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_thread() {
|
||||
pthread_t t;
|
||||
pr("testing basic thread creation and join... ");
|
||||
if ((pthread_create(&t,0,thread,0))!=0) _die_("failed...\n");
|
||||
if (pthread_join(t,0) != 0) _die_("failed... joining thread\n");
|
||||
pr("OK.\n");
|
||||
}
|
||||
|
||||
void test_thread_join_detached() {
|
||||
pthread_t t;
|
||||
pthread_attr_t attr;
|
||||
pr("testing for failing join of a detached thread... ");
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
|
||||
if ((pthread_create(&t,&attr,thread,0))!=0) _die_("failed...\n");
|
||||
if (pthread_join(t,0) == 0) _die_("failed... I had joined a detached thread !\n");
|
||||
sleep(2);
|
||||
pr("OK.\n");
|
||||
}
|
||||
|
||||
static char alt_stack[4096];
|
||||
void test_thread_alt_stack() {
|
||||
pthread_t t;
|
||||
pthread_attr_t attr;
|
||||
pr("testing alternate thread stack... ");
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setstacksize(&attr,sizeof(alt_stack));
|
||||
if ((pthread_create(&t,&attr,thread,0))!=0) _die_("failed... creating thread\n");
|
||||
if (pthread_join(t,0) != 0) _die_("failed... joining thread\n");
|
||||
pthread_attr_setstackaddr(&attr,alt_stack);
|
||||
if ((pthread_create(&t,&attr,thread,0))!=0) _die_("failed... creating thread\n");
|
||||
if (pthread_join(t,0) != 0) _die_("failed... joining thread\n");
|
||||
pr("OK.\n");
|
||||
}
|
||||
|
||||
void pth_t2()
|
||||
{
|
||||
pr("\nTEST 2: thread creation & attributes:\n\n");
|
||||
test_thread();
|
||||
test_thread_join_detached();
|
||||
test_thread_alt_stack();
|
||||
}
|
||||
FINSH_FUNCTION_EXPORT(pth_t2, pthread testcase1);
|
|
@ -0,0 +1,98 @@
|
|||
#include <errno.h>
|
||||
#include <sys/fcntl.h>
|
||||
|
||||
#include "sem.h"
|
||||
|
||||
int sem_close(sem_t *sem)
|
||||
{
|
||||
}
|
||||
|
||||
int sem_destroy(sem_t *sem)
|
||||
{
|
||||
}
|
||||
|
||||
int sem_unlink(const char *name)
|
||||
{
|
||||
return EACCES;
|
||||
}
|
||||
|
||||
int sem_getvalue(sem_t *sem, int *sval)
|
||||
{
|
||||
RT_ASSERT(sem != RT_NULL);
|
||||
if (sval) *sval = sem->value;
|
||||
}
|
||||
|
||||
int sem_init(sem_t *sem, int pshared, unsigned int value)
|
||||
{
|
||||
RT_ASSERT(sem != RT_NULL);
|
||||
}
|
||||
|
||||
sem_t *sem_open(const char *name, int oflag, ...)
|
||||
{
|
||||
rt_sem_t sem;
|
||||
|
||||
sem = RT_NULL;
|
||||
if (oflag == O_CREAT)
|
||||
{
|
||||
sem = rt_sem_create(name, 1, RT_IPC_FLAG_FIFO);
|
||||
if (sem == RT_NULL)
|
||||
rt_set_errno(ENOSPC);
|
||||
}
|
||||
|
||||
if (oflag == O_EXCL)
|
||||
{
|
||||
rt_enter_critical();
|
||||
/* find semaphore object */
|
||||
rt_exit_critical();
|
||||
|
||||
if (sem == RT_NULL) rt_set_errno(ENOENT);
|
||||
}
|
||||
|
||||
return sem;
|
||||
}
|
||||
|
||||
int sem_post(sem_t *sem)
|
||||
{
|
||||
rt_sem_release(sem);
|
||||
}
|
||||
|
||||
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout)
|
||||
{
|
||||
rt_err_t result;
|
||||
rt_int32_t tick;
|
||||
|
||||
if (!sem || !abs_timeout) return EINVAL;
|
||||
|
||||
/* calculate os tick */
|
||||
tick = abs_timeout->tv_sec/RT_TICK_PER_SECOND + (abs_timeout->tv_nsec/1000) * (1000/RT_TICK_PER_SECOND);
|
||||
|
||||
result = rt_sem_take(sem, tick);
|
||||
if (result == -RT_ETIMEOUT) return ETIMEDOUT;
|
||||
if (result == RT_EOK) return 0;
|
||||
|
||||
return EINTR;
|
||||
}
|
||||
|
||||
int sem_trywait(sem_t *sem)
|
||||
{
|
||||
rt_err_t result;
|
||||
|
||||
if (!sem) return EINVAL;
|
||||
|
||||
result = rt_sem_take(sem, RT_WAITING_FOREVER);
|
||||
if (result == -RT_ETIMEOUT) return EAGAIN;
|
||||
if (result == RT_EOK) return 0;
|
||||
|
||||
return EINTR;
|
||||
}
|
||||
|
||||
int sem_wait(sem_t *sem)
|
||||
{
|
||||
rt_err_t result;
|
||||
|
||||
result = rt_sem_take(sem, RT_WAITING_FOREVER);
|
||||
if (result == RT_EOK) return 0;
|
||||
|
||||
return EINTR;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef __POSIX_SEMAPHORE_H__
|
||||
#define __POSIX_SEMAPHORE_H__
|
||||
|
||||
#include <rtthread.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
typedef struct rt_semaphore sem_t;
|
||||
|
||||
int sem_close(sem_t *sem);
|
||||
int sem_destroy(sem_t *sem);
|
||||
int sem_getvalue(sem_t *sem, int *sval);
|
||||
int sem_init(sem_t *sem, int pshared, unsigned int value);
|
||||
sem_t *sem_open(const char *name, int oflag, ...);
|
||||
int sem_post(sem_t *sem);
|
||||
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
|
||||
int sem_trywait(sem_t *sem);
|
||||
int sem_unlink(const char *name);
|
||||
int sem_wait(sem_t *sem);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue