src/lib/librthread/rthread.c

612 lines
14 KiB
C

/* $OpenBSD: rthread.c,v 1.100 2022/12/27 17:10:07 jmc Exp $ */
/*
* Copyright (c) 2004,2005 Ted Unangst <tedu@openbsd.org>
* All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* The heart of rthreads. Basic functions like creating and joining
* threads.
*/
#include <sys/types.h>
#ifndef NO_PIC
#include <elf.h>
#pragma weak _DYNAMIC
#endif
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#include <tib.h>
#include <pthread.h>
#include "cancel.h" /* in libc/include */
#include "rthread.h"
#include "rthread_cb.h"
/*
* Call nonstandard functions via names in the reserved namespace:
* dlctl() -> _dlctl()
* getthrid -> _thread_sys_getthrid
*/
typeof(dlctl) dlctl asm("_dlctl") __attribute__((weak));
REDIRECT_SYSCALL(getthrid);
/* weak stub to be overridden by ld.so */
int dlctl(void *handle, int cmd, void *data) { return 0; }
/*
* libc's signal wrappers hide SIGTHR; we need to call the real syscall
* stubs _thread_sys_* directly.
*/
REDIRECT_SYSCALL(sigaction);
REDIRECT_SYSCALL(sigprocmask);
REDIRECT_SYSCALL(thrkill);
static int concurrency_level; /* not used */
int _threads_ready;
int _post_threaded;
size_t _thread_pagesize;
struct listhead _thread_list = LIST_HEAD_INITIALIZER(_thread_list);
_atomic_lock_t _thread_lock = _SPINLOCK_UNLOCKED;
static struct pthread_queue _thread_gc_list
= TAILQ_HEAD_INITIALIZER(_thread_gc_list);
static _atomic_lock_t _thread_gc_lock = _SPINLOCK_UNLOCKED;
static struct pthread _initial_thread;
struct pthread_attr _rthread_attr_default = {
.stack_addr = NULL,
.stack_size = RTHREAD_STACK_SIZE_DEF,
/* .guard_size set in _rthread_init */
.detach_state = PTHREAD_CREATE_JOINABLE,
.contention_scope = PTHREAD_SCOPE_SYSTEM,
.sched_policy = SCHED_OTHER,
.sched_param = { .sched_priority = 0 },
.sched_inherit = PTHREAD_INHERIT_SCHED,
};
/*
* internal support functions
*/
static void
_rthread_start(void *v)
{
pthread_t thread = v;
void *retval;
retval = thread->fn(thread->arg);
pthread_exit(retval);
}
static void
sigthr_handler(__unused int sig)
{
struct tib *tib = TIB_GET();
pthread_t self = tib->tib_thread;
/*
* Do nothing unless
* 1) pthread_cancel() has been called on this thread,
* 2) cancelation is enabled for it, and
* 3) we're not already in cancelation processing
*/
if (!tib->tib_canceled || tib->tib_cantcancel)
return;
/*
* If delaying cancels inside complex ops (pthread_cond_wait,
* pthread_join, etc), just mark that this has happened to
* prevent a race with going to sleep
*/
if (tib->tib_cancel_point & CANCEL_POINT_DELAYED) {
self->delayed_cancel = 1;
return;
}
/*
* otherwise, if in a cancel point or async cancels are
* enabled, then exit
*/
if (tib->tib_cancel_point ||
(tib->tib_thread_flags & TIB_THREAD_ASYNC_CANCEL))
pthread_exit(PTHREAD_CANCELED);
}
/*
* A few basic callbacks for libc. The first couple are only used
* on archs where there isn't a fast TCB_GET()
*/
#ifndef TCB_HAVE_MD_GET
static int *
multi_threaded_errnoptr(void)
{
return (&TIB_GET()->tib_errno);
}
static void *
multi_threaded_tcb(void)
{
return (TCB_GET());
}
#endif /* TCB_HAVE_MD_GET */
static void
_rthread_free(pthread_t thread)
{
_spinlock(&_thread_gc_lock);
TAILQ_INSERT_TAIL(&_thread_gc_list, thread, waiting);
_spinunlock(&_thread_gc_lock);
}
static void
_thread_release(pthread_t thread)
{
_spinlock(&_thread_lock);
LIST_REMOVE(thread, threads);
_spinunlock(&_thread_lock);
_spinlock(&thread->flags_lock);
if (thread->flags & THREAD_DETACHED) {
_spinunlock(&thread->flags_lock);
_rthread_free(thread);
} else {
thread->flags |= THREAD_DONE;
_spinunlock(&thread->flags_lock);
_sem_post(&thread->donesem);
}
}
static void
_thread_key_zero(int key)
{
pthread_t thread;
struct rthread_storage *rs;
LIST_FOREACH(thread, &_thread_list, threads) {
for (rs = thread->local_storage; rs; rs = rs->next) {
if (rs->keyid == key)
rs->data = NULL;
}
}
}
void
_rthread_init(void)
{
pthread_t thread = pthread_self();
struct sigaction sa;
if (_threads_ready)
return;
LIST_INSERT_HEAD(&_thread_list, thread, threads);
_thread_pagesize = (size_t)sysconf(_SC_PAGESIZE);
_rthread_attr_default.guard_size = _thread_pagesize;
thread->attr = _rthread_attr_default;
/* get libc to start using our callbacks */
{
struct thread_callbacks cb = { 0 };
#ifndef TCB_HAVE_MD_GET
cb.tc_errnoptr = multi_threaded_errnoptr;
cb.tc_tcb = multi_threaded_tcb;
#endif
cb.tc_fork = _thread_fork;
cb.tc_vfork = _thread_vfork;
cb.tc_thread_release = _thread_release;
cb.tc_thread_key_zero = _thread_key_zero;
_thread_set_callbacks(&cb, sizeof(cb));
}
#ifndef NO_PIC
if (_DYNAMIC) {
dlctl(NULL, DL_SETTHREADLCK, _rthread_dl_lock);
}
#endif
/*
* Set the handler on the signal used for cancelation and
* suspension, and make sure it's unblocked
*/
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_handler = sigthr_handler;
sigaction(SIGTHR, &sa, NULL);
sigaddset(&sa.sa_mask, SIGTHR);
sigprocmask(SIG_UNBLOCK, &sa.sa_mask, NULL);
_threads_ready = 1;
_malloc_init(1);
_rthread_debug(1, "rthread init\n");
}
static void
_rthread_reaper(void)
{
pthread_t thread;
restart:
_spinlock(&_thread_gc_lock);
TAILQ_FOREACH(thread, &_thread_gc_list, waiting) {
if (thread->tib->tib_tid != 0)
continue;
TAILQ_REMOVE(&_thread_gc_list, thread, waiting);
_spinunlock(&_thread_gc_lock);
if (thread != &_initial_thread) {
_rthread_debug(3, "rthread reaping %p stack %p\n",
(void *)thread, (void *)thread->stack);
_rthread_free_stack(thread->stack);
_dl_free_tib(thread->tib, sizeof(*thread));
} else {
/* initial thread isn't part of TIB allocation */
_rthread_debug(3, "rthread reaping %p (initial)\n",
(void *)thread);
_dl_free_tib(thread->tib, 0);
}
goto restart;
}
_spinunlock(&_thread_gc_lock);
}
/*
* real pthread functions
*/
int
pthread_join(pthread_t thread, void **retval)
{
int e;
struct tib *tib = TIB_GET();
pthread_t self;
PREP_CANCEL_POINT(tib);
if (_post_threaded) {
#define GREATSCOTT "great scott! serious repercussions on future events!\n"
write(2, GREATSCOTT, sizeof(GREATSCOTT) - 1);
abort();
}
if (!_threads_ready)
_rthread_init();
self = tib->tib_thread;
e = 0;
ENTER_DELAYED_CANCEL_POINT(tib, self);
if (thread == NULL)
e = EINVAL;
else if (thread == self)
e = EDEADLK;
else if (thread->flags & THREAD_DETACHED)
e = EINVAL;
else if ((e = _sem_wait(&thread->donesem, 0, NULL,
&self->delayed_cancel)) == 0) {
if (retval)
*retval = thread->retval;
/*
* We should be the last having a ref to this thread,
* but someone stupid or evil might haved detached it;
* in that case the thread will clean up itself
*/
if ((thread->flags & THREAD_DETACHED) == 0)
_rthread_free(thread);
}
LEAVE_CANCEL_POINT_INNER(tib, e);
_rthread_reaper();
return (e);
}
int
pthread_detach(pthread_t thread)
{
int rc = 0;
_spinlock(&thread->flags_lock);
if (thread->flags & THREAD_DETACHED) {
rc = EINVAL;
_spinunlock(&thread->flags_lock);
} else if (thread->flags & THREAD_DONE) {
_spinunlock(&thread->flags_lock);
_rthread_free(thread);
} else {
thread->flags |= THREAD_DETACHED;
_spinunlock(&thread->flags_lock);
}
_rthread_reaper();
return (rc);
}
int
pthread_create(pthread_t *threadp, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg)
{
extern int __isthreaded;
struct tib *tib;
pthread_t thread;
struct __tfork param;
int rc;
if (!_threads_ready)
_rthread_init();
_rthread_reaper();
tib = _dl_allocate_tib(sizeof(*thread));
if (tib == NULL)
return (ENOMEM);
thread = tib->tib_thread;
memset(thread, 0, sizeof(*thread));
thread->tib = tib;
thread->donesem.lock = _SPINLOCK_UNLOCKED;
thread->flags_lock = _SPINLOCK_UNLOCKED;
thread->fn = start_routine;
thread->arg = arg;
tib->tib_tid = -1;
thread->attr = attr != NULL ? *(*attr) : _rthread_attr_default;
if (thread->attr.sched_inherit == PTHREAD_INHERIT_SCHED) {
pthread_t self = pthread_self();
thread->attr.sched_policy = self->attr.sched_policy;
thread->attr.sched_param = self->attr.sched_param;
}
if (thread->attr.detach_state == PTHREAD_CREATE_DETACHED)
thread->flags |= THREAD_DETACHED;
thread->stack = _rthread_alloc_stack(thread);
if (!thread->stack) {
rc = errno;
goto fail1;
}
param.tf_tcb = TIB_TO_TCB(tib);
param.tf_tid = &tib->tib_tid;
param.tf_stack = thread->stack->sp;
_spinlock(&_thread_lock);
LIST_INSERT_HEAD(&_thread_list, thread, threads);
_spinunlock(&_thread_lock);
/* we're going to be multi-threaded real soon now */
__isthreaded = 1;
rc = __tfork_thread(&param, sizeof(param), _rthread_start, thread);
if (rc != -1) {
/* success */
*threadp = thread;
return (0);
}
rc = errno;
_spinlock(&_thread_lock);
LIST_REMOVE(thread, threads);
_spinunlock(&_thread_lock);
_rthread_free_stack(thread->stack);
fail1:
_dl_free_tib(tib, sizeof(*thread));
return (rc);
}
int
pthread_kill(pthread_t thread, int sig)
{
struct tib *tib = thread->tib;
if (sig == SIGTHR)
return (EINVAL);
if (thrkill(tib->tib_tid, sig, TIB_TO_TCB(tib)))
return (errno);
return (0);
}
int
pthread_cancel(pthread_t thread)
{
struct tib *tib = thread->tib;
pid_t tid = tib->tib_tid;
if (tib->tib_canceled == 0 && tid != 0 &&
(tib->tib_cantcancel & CANCEL_DYING) == 0) {
tib->tib_canceled = 1;
if ((tib->tib_cantcancel & CANCEL_DISABLED) == 0) {
thrkill(tid, SIGTHR, TIB_TO_TCB(tib));
return (0);
}
}
return (0);
}
void
pthread_testcancel(void)
{
struct tib *tib = TIB_GET();
if (tib->tib_canceled && (tib->tib_cantcancel & CANCEL_DISABLED) == 0)
pthread_exit(PTHREAD_CANCELED);
}
int
pthread_setcancelstate(int state, int *oldstatep)
{
struct tib *tib = TIB_GET();
int oldstate;
oldstate = tib->tib_cantcancel & CANCEL_DISABLED ?
PTHREAD_CANCEL_DISABLE : PTHREAD_CANCEL_ENABLE;
if (state == PTHREAD_CANCEL_ENABLE) {
tib->tib_cantcancel &= ~CANCEL_DISABLED;
} else if (state == PTHREAD_CANCEL_DISABLE) {
tib->tib_cantcancel |= CANCEL_DISABLED;
} else {
return (EINVAL);
}
if (oldstatep)
*oldstatep = oldstate;
return (0);
}
DEF_STD(pthread_setcancelstate);
int
pthread_setcanceltype(int type, int *oldtypep)
{
struct tib *tib = TIB_GET();
int oldtype;
oldtype = tib->tib_thread_flags & TIB_THREAD_ASYNC_CANCEL ?
PTHREAD_CANCEL_ASYNCHRONOUS : PTHREAD_CANCEL_DEFERRED;
if (type == PTHREAD_CANCEL_DEFERRED) {
tib->tib_thread_flags &=~ TIB_THREAD_ASYNC_CANCEL;
} else if (type == PTHREAD_CANCEL_ASYNCHRONOUS) {
tib->tib_thread_flags |= TIB_THREAD_ASYNC_CANCEL;
} else {
return (EINVAL);
}
if (oldtypep)
*oldtypep = oldtype;
return (0);
}
void
pthread_cleanup_push(void (*fn)(void *), void *arg)
{
struct rthread_cleanup_fn *clfn;
pthread_t self = pthread_self();
clfn = calloc(1, sizeof(*clfn));
if (!clfn)
return;
clfn->fn = fn;
clfn->arg = arg;
clfn->next = self->cleanup_fns;
self->cleanup_fns = clfn;
}
void
pthread_cleanup_pop(int execute)
{
struct rthread_cleanup_fn *clfn;
pthread_t self = pthread_self();
clfn = self->cleanup_fns;
if (clfn) {
self->cleanup_fns = clfn->next;
if (execute)
clfn->fn(clfn->arg);
free(clfn);
}
}
int
pthread_getconcurrency(void)
{
return (concurrency_level);
}
int
pthread_setconcurrency(int new_level)
{
if (new_level < 0)
return (EINVAL);
concurrency_level = new_level;
return (0);
}
/*
* compat debug stuff
*/
void
_thread_dump_info(void)
{
pthread_t thread;
_spinlock(&_thread_lock);
LIST_FOREACH(thread, &_thread_list, threads)
printf("thread %d flags 0x%x name %s\n", thread->tib->tib_tid,
thread->tib->tib_thread_flags, thread->name);
_spinunlock(&_thread_lock);
}
#ifndef NO_PIC
/*
* _rthread_dl_lock() provides the locking for dlopen(), dlclose(), and
* the function called via atexit() to invoke all destructors. The latter
* two call shared-object destructors, which may need to call dlclose(),
* so this lock needs to permit recursive locking.
* The specific code here was extracted from _rthread_mutex_lock() and
* pthread_mutex_unlock() and simplified to use the static variables.
*/
void
_rthread_dl_lock(int what)
{
static _atomic_lock_t lock = _SPINLOCK_UNLOCKED;
static pthread_t owner = NULL;
static struct pthread_queue lockers = TAILQ_HEAD_INITIALIZER(lockers);
static int count = 0;
if (what == 0) {
pthread_t self = pthread_self();
/* lock, possibly recursive */
_spinlock(&lock);
if (owner == NULL) {
owner = self;
} else if (owner != self) {
TAILQ_INSERT_TAIL(&lockers, self, waiting);
while (owner != self) {
__thrsleep(self, 0, NULL, &lock, NULL);
_spinlock(&lock);
}
}
count++;
_spinunlock(&lock);
} else if (what == 1) {
/* unlock, possibly recursive */
if (--count == 0) {
pthread_t next;
_spinlock(&lock);
owner = next = TAILQ_FIRST(&lockers);
if (next != NULL)
TAILQ_REMOVE(&lockers, next, waiting);
_spinunlock(&lock);
if (next != NULL)
__thrwakeup(next, 1);
}
} else {
/* reinit: used in child after fork to clear the queue */
lock = _SPINLOCK_UNLOCKED;
if (--count == 0)
owner = NULL;
TAILQ_INIT(&lockers);
}
}
#endif