mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
393 lines
10 KiB
C
393 lines
10 KiB
C
/*
|
|
* Copyright (C) 2013 Freie Universität Berlin
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup pthread
|
|
* @{
|
|
* @file
|
|
* @brief Thread creation features.
|
|
* @see [The Open Group Base Specifications Issue 7: pthread.h - threads](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pthread.h.html)
|
|
* @author Christian Mehlis <mehlis@inf.fu-berlin.de>
|
|
* @author René Kijewski <kijewski@inf.fu-berlin.de>
|
|
* @}
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#include "cpu_conf.h"
|
|
#include "irq.h"
|
|
#include "msg.h"
|
|
#include "mutex.h"
|
|
#include "priority_queue.h"
|
|
#include "thread.h"
|
|
#include "sched.h"
|
|
|
|
#include "pthread.h"
|
|
|
|
#ifdef HAVE_MALLOC_H
|
|
#include <malloc.h>
|
|
#endif
|
|
|
|
#define ENABLE_DEBUG 0
|
|
|
|
#ifndef CONFIG_PTHREAD_REAPER_BASE_STACKSIZE
|
|
#define CONFIG_PTHREAD_REAPER_BASE_STACKSIZE (THREAD_STACKSIZE_IDLE)
|
|
#endif
|
|
|
|
#if IS_ACTIVE(ENABLE_DEBUG)
|
|
# define PTHREAD_REAPER_STACKSIZE ((CONFIG_PTHREAD_REAPER_BASE_STACKSIZE) + THREAD_EXTRA_STACKSIZE_PRINTF)
|
|
# define PTHREAD_STACKSIZE THREAD_STACKSIZE_MAIN
|
|
#else
|
|
# define PTHREAD_REAPER_STACKSIZE (CONFIG_PTHREAD_REAPER_BASE_STACKSIZE)
|
|
# define PTHREAD_STACKSIZE THREAD_STACKSIZE_DEFAULT
|
|
#endif
|
|
|
|
#include "debug.h"
|
|
|
|
typedef enum {
|
|
PTS_RUNNING,
|
|
PTS_DETACHED,
|
|
PTS_ZOMBIE,
|
|
} pthread_thread_status_t;
|
|
|
|
typedef struct {
|
|
kernel_pid_t thread_pid;
|
|
|
|
pthread_thread_status_t status;
|
|
kernel_pid_t joining_thread;
|
|
void *returnval;
|
|
bool should_cancel;
|
|
|
|
void *(*start_routine)(void *);
|
|
void *arg;
|
|
|
|
char *stack;
|
|
|
|
struct __pthread_tls_datum *tls_head;
|
|
|
|
__pthread_cleanup_datum_t *cleanup_top;
|
|
} pthread_thread_t;
|
|
|
|
static pthread_thread_t *volatile pthread_sched_threads[MAXTHREADS];
|
|
static mutex_t pthread_mutex;
|
|
|
|
static volatile kernel_pid_t pthread_reaper_pid = KERNEL_PID_UNDEF;
|
|
|
|
static char pthread_reaper_stack[PTHREAD_REAPER_STACKSIZE];
|
|
|
|
static void *pthread_start_routine(void *pt_)
|
|
{
|
|
pthread_thread_t *pt = pt_;
|
|
void *retval = pt->start_routine(pt->arg);
|
|
pthread_exit(retval);
|
|
}
|
|
|
|
static int insert(pthread_thread_t *pt)
|
|
{
|
|
int result = KERNEL_PID_UNDEF;
|
|
mutex_lock(&pthread_mutex);
|
|
|
|
for (int i = 0; i < MAXTHREADS; i++){
|
|
if (!pthread_sched_threads[i]) {
|
|
pthread_sched_threads[i] = pt;
|
|
result = i+1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&pthread_mutex);
|
|
return result;
|
|
}
|
|
|
|
static void *pthread_reaper(void *arg)
|
|
{
|
|
(void) arg;
|
|
|
|
while (1) {
|
|
msg_t m;
|
|
msg_receive(&m);
|
|
DEBUG("pthread_reaper(): free(%p)\n", m.content.ptr);
|
|
free(m.content.ptr);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int pthread_create(pthread_t *newthread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
|
|
{
|
|
pthread_thread_t *pt = calloc(1, sizeof(pthread_thread_t));
|
|
|
|
if (pt == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
kernel_pid_t pthread_pid = insert(pt);
|
|
if (pthread_pid == KERNEL_PID_UNDEF) {
|
|
free(pt);
|
|
return -1;
|
|
}
|
|
*newthread = pthread_pid;
|
|
|
|
pt->status = attr && attr->detached ? PTS_DETACHED : PTS_RUNNING;
|
|
pt->start_routine = start_routine;
|
|
pt->arg = arg;
|
|
|
|
bool autofree = attr == NULL || attr->ss_sp == NULL || attr->ss_size == 0;
|
|
size_t stack_size = attr && attr->ss_size > 0 ? attr->ss_size : PTHREAD_STACKSIZE;
|
|
void *stack = autofree ? malloc(stack_size) : attr->ss_sp;
|
|
|
|
if (stack == NULL) {
|
|
free(pt);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pt->stack = autofree ? stack : NULL;
|
|
if (autofree && pthread_reaper_pid == KERNEL_PID_UNDEF) {
|
|
mutex_lock(&pthread_mutex);
|
|
if (pthread_reaper_pid == KERNEL_PID_UNDEF) {
|
|
/* volatile pid to overcome problems with double checking */
|
|
volatile kernel_pid_t pid = thread_create(pthread_reaper_stack,
|
|
PTHREAD_REAPER_STACKSIZE,
|
|
0,
|
|
0,
|
|
pthread_reaper,
|
|
NULL,
|
|
"pthread-reaper");
|
|
if (!pid_is_valid(pid)) {
|
|
free(pt->stack);
|
|
free(pt);
|
|
pthread_sched_threads[pthread_pid-1] = NULL;
|
|
mutex_unlock(&pthread_mutex);
|
|
return -1;
|
|
}
|
|
pthread_reaper_pid = pid;
|
|
}
|
|
mutex_unlock(&pthread_mutex);
|
|
}
|
|
|
|
pt->thread_pid = thread_create(stack,
|
|
stack_size,
|
|
THREAD_PRIORITY_MAIN,
|
|
THREAD_CREATE_SLEEPING |
|
|
0,
|
|
pthread_start_routine,
|
|
pt,
|
|
"pthread");
|
|
if (!pid_is_valid(pt->thread_pid)) {
|
|
free(pt->stack);
|
|
free(pt);
|
|
pthread_sched_threads[pthread_pid-1] = NULL;
|
|
return -1;
|
|
}
|
|
|
|
thread_wakeup(pt->thread_pid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void pthread_exit(void *retval)
|
|
{
|
|
pthread_t self_id = pthread_self();
|
|
|
|
if (self_id == 0) {
|
|
DEBUG("ERROR called pthread_self() returned 0 in \"%s\"!\n", __func__);
|
|
}
|
|
else {
|
|
pthread_thread_t *self = pthread_sched_threads[self_id - 1];
|
|
|
|
while (self->cleanup_top) {
|
|
__pthread_cleanup_datum_t *ct = self->cleanup_top;
|
|
self->cleanup_top = ct->__next;
|
|
|
|
ct->__routine(ct->__arg);
|
|
}
|
|
|
|
/* Prevent linking in pthread_tls.o if no TSS functions were used. */
|
|
extern void __pthread_keys_exit(int self_id) __attribute__((weak));
|
|
if (__pthread_keys_exit) {
|
|
__pthread_keys_exit(self_id);
|
|
}
|
|
|
|
self->thread_pid = KERNEL_PID_UNDEF;
|
|
DEBUG("pthread_exit(%p), self == %p\n", retval, (void *) self);
|
|
if (self->status != PTS_DETACHED) {
|
|
self->returnval = retval;
|
|
self->status = PTS_ZOMBIE;
|
|
|
|
if (self->joining_thread) {
|
|
/* our thread got an other thread waiting for us */
|
|
thread_wakeup(self->joining_thread);
|
|
}
|
|
}
|
|
|
|
irq_disable();
|
|
if (self->stack) {
|
|
msg_t m;
|
|
m.content.ptr = self->stack;
|
|
msg_send_int(&m, pthread_reaper_pid);
|
|
}
|
|
}
|
|
|
|
sched_task_exit();
|
|
}
|
|
|
|
int pthread_join(pthread_t th, void **thread_return)
|
|
{
|
|
if (th < 1 || th > MAXTHREADS) {
|
|
DEBUG("passed pthread_t th (%d) exceeds bounds of pthread_sched_threads[] in \"%s\"!\n", th, __func__);
|
|
return -3;
|
|
}
|
|
|
|
pthread_thread_t *other = pthread_sched_threads[th-1];
|
|
if (!other) {
|
|
return -1;
|
|
}
|
|
|
|
switch (other->status) {
|
|
case (PTS_RUNNING):
|
|
other->joining_thread = thread_getpid();
|
|
/* go blocked, I'm waking up if other thread exits */
|
|
thread_sleep();
|
|
/* falls through */
|
|
case (PTS_ZOMBIE):
|
|
if (thread_return) {
|
|
*thread_return = other->returnval;
|
|
}
|
|
free(other);
|
|
/* we only need to free the pthread layer struct,
|
|
native thread stack is freed by other */
|
|
pthread_sched_threads[th-1] = NULL;
|
|
return 0;
|
|
case (PTS_DETACHED):
|
|
return -1;
|
|
}
|
|
|
|
return -2;
|
|
}
|
|
|
|
int pthread_detach(pthread_t th)
|
|
{
|
|
if (th < 1 || th > MAXTHREADS) {
|
|
DEBUG("passed pthread_t th (%d) exceeds bounds of pthread_sched_threads[] in \"%s\"!\n", th, __func__);
|
|
return -2;
|
|
}
|
|
|
|
pthread_thread_t *other = pthread_sched_threads[th-1];
|
|
if (!other) {
|
|
return -1;
|
|
}
|
|
|
|
if (other->status == PTS_ZOMBIE) {
|
|
free(other);
|
|
/* we only need to free the pthread layer struct,
|
|
native thread stack is freed by other */
|
|
pthread_sched_threads[th-1] = NULL;
|
|
} else {
|
|
other->status = PTS_DETACHED;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
pthread_t pthread_self(void)
|
|
{
|
|
pthread_t result = 0;
|
|
mutex_lock(&pthread_mutex);
|
|
kernel_pid_t pid = thread_getpid(); /* thread_getpid() is volatile */
|
|
for (int i = 0; i < MAXTHREADS; i++) {
|
|
if (pthread_sched_threads[i] && pthread_sched_threads[i]->thread_pid == pid) {
|
|
result = i+1;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&pthread_mutex);
|
|
return result;
|
|
}
|
|
|
|
int pthread_cancel(pthread_t th)
|
|
{
|
|
pthread_thread_t *other = pthread_sched_threads[th-1];
|
|
if (!other) {
|
|
return -1;
|
|
}
|
|
|
|
other->should_cancel = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pthread_setcancelstate(int state, int *oldstate)
|
|
{
|
|
(void) state;
|
|
(void) oldstate;
|
|
return -1;
|
|
}
|
|
|
|
int pthread_setcanceltype(int type, int *oldtype)
|
|
{
|
|
(void) type;
|
|
(void) oldtype;
|
|
return -1;
|
|
}
|
|
|
|
void pthread_testcancel(void)
|
|
{
|
|
pthread_t self = pthread_self();
|
|
|
|
if (self == 0) {
|
|
DEBUG("ERROR called pthread_self() returned 0 in \"%s\"!\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (pthread_sched_threads[self-1]->should_cancel) {
|
|
pthread_exit(PTHREAD_CANCELED);
|
|
}
|
|
}
|
|
|
|
void __pthread_cleanup_push(__pthread_cleanup_datum_t *datum)
|
|
{
|
|
pthread_t self_id = pthread_self();
|
|
|
|
if (self_id == 0) {
|
|
DEBUG("ERROR called pthread_self() returned 0 in \"%s\"!\n", __func__);
|
|
return;
|
|
}
|
|
|
|
pthread_thread_t *self = pthread_sched_threads[self_id-1];
|
|
datum->__next = self->cleanup_top;
|
|
self->cleanup_top = datum;
|
|
}
|
|
|
|
void __pthread_cleanup_pop(__pthread_cleanup_datum_t *datum, int execute)
|
|
{
|
|
pthread_t self_id = pthread_self();
|
|
|
|
if (self_id == 0) {
|
|
DEBUG("ERROR called pthread_self() returned 0 in \"%s\"!\n", __func__);
|
|
return;
|
|
}
|
|
|
|
pthread_thread_t *self = pthread_sched_threads[self_id-1];
|
|
self->cleanup_top = datum->__next;
|
|
|
|
if (execute != 0) {
|
|
/* "The pthread_cleanup_pop() function shall remove the routine at the
|
|
* top of the calling thread's cancellation cleanup stack and optionally
|
|
* invoke it (if execute is non-zero)." */
|
|
datum->__routine(datum->__arg);
|
|
}
|
|
}
|
|
|
|
struct __pthread_tls_datum **__pthread_get_tls_head(int self_id)
|
|
{
|
|
pthread_thread_t *self = pthread_sched_threads[self_id-1];
|
|
return self ? &self->tls_head : NULL;
|
|
}
|