diff options
| author | thorpej <thorpej@NetBSD.org> | 2003-01-18 10:32:11 +0000 |
|---|---|---|
| committer | thorpej <thorpej@NetBSD.org> | 2003-01-18 10:32:11 +0000 |
| commit | c62a74e6d5cfb2fb5034e98b983da7e2fc87709d (patch) | |
| tree | f1281fd7111ddb57951e4472b342b53a2f56244a /lib/libpthread/pthread.c | |
| parent | af919042eceb8c19f42acd688efa8cf58168af5d (diff) | |
Merge the nathanw_sa branch.
Diffstat (limited to 'lib/libpthread/pthread.c')
| -rw-r--r-- | lib/libpthread/pthread.c | 835 |
1 files changed, 835 insertions, 0 deletions
diff --git a/lib/libpthread/pthread.c b/lib/libpthread/pthread.c new file mode 100644 index 00000000000..f69e7b60081 --- /dev/null +++ b/lib/libpthread/pthread.c @@ -0,0 +1,835 @@ +/* $NetBSD: pthread.c,v 1.2 2003/01/18 10:34:15 thorpej Exp $ */ + +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Nathan J. Williams. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <lwp.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <ucontext.h> +#include <sys/cdefs.h> + +#include <sched.h> +#include "pthread.h" +#include "pthread_int.h" + + +#undef PTHREAD_MAIN_DEBUG + +#ifdef PTHREAD_MAIN_DEBUG +#define SDPRINTF(x) DPRINTF(x) +#else +#define SDPRINTF(x) +#endif + +static void pthread__create_tramp(void *(*start)(void *), void *arg); + +int pthread__started; + +pthread_spin_t pthread__allqueue_lock; +struct pthread_queue_t pthread__allqueue; + +pthread_spin_t pthread__deadqueue_lock; +struct pthread_queue_t pthread__deadqueue; +struct pthread_queue_t pthread__reidlequeue; + +static int nthreads; +static int nextthread; +static pthread_spin_t nextthread_lock; +static pthread_attr_t pthread_default_attr; + +pthread_spin_t pthread__runqueue_lock; +struct pthread_queue_t pthread__runqueue; +struct pthread_queue_t pthread__idlequeue; + +__strong_alias(__libc_thr_self,pthread_self) +__strong_alias(__libc_thr_errno,pthread__errno) + +/* + * Static library kludge. Place a reference to a symbol any library + * file which does not already have a reference here. + */ +extern int pthread__cancel_stub_binder; +extern int pthread__sched_binder; + +void *pthread__static_lib_binder[] = { + &pthread__cancel_stub_binder, + pthread_cond_init, + pthread_mutex_init, + pthread_rwlock_init, + pthread_barrier_init, + pthread_key_create, + &pthread__sched_binder, +}; + +/* + * This needs to be started by the library loading code, before main() + * gets to run, for various things that use the state of the initial thread + * to work properly (thread-specific data is an application-visible example; + * spinlock counts for mutexes is an internal example). + */ +void +pthread_init(void) +{ + pthread_t first; + extern int __isthreaded; + + /* Initialize locks first; they're needed elsewhere. */ + pthread__lockprim_init(); + + /* Basic data structure setup */ + pthread_attr_init(&pthread_default_attr); + PTQ_INIT(&pthread__allqueue); + PTQ_INIT(&pthread__deadqueue); + PTQ_INIT(&pthread__reidlequeue); + PTQ_INIT(&pthread__runqueue); + PTQ_INIT(&pthread__idlequeue); + + /* Create the thread structure corresponding to main() */ + pthread__initmain(&first); + pthread__initthread(first, first); + first->pt_state = PT_STATE_RUNNING; + sigprocmask(0, NULL, &first->pt_sigmask); + PTQ_INSERT_HEAD(&pthread__allqueue, first, pt_allq); + + /* Start subsystems */ + pthread__alarm_init(); + pthread__signal_init(); + PTHREAD_MD_INIT +#ifdef PTHREAD__DEBUG + pthread__debug_init(); +#endif + + /* Tell libc that we're here and it should role-play accordingly. */ + __isthreaded = 1; +} + + +static void +pthread__start(void) +{ + pthread_t self, idle; + int i, ret; + + self = pthread__self(); /* should be the "main()" thread */ + + + /* Create idle threads */ + for (i = 0; i < NIDLETHREADS; i++) { + ret = pthread__stackalloc(&idle); + if (ret != 0) + err(1, "Couldn't allocate stack for idle thread!"); + pthread__initthread(self, idle); + sigfillset(&idle->pt_sigmask); + idle->pt_type = PT_THREAD_IDLE; + PTQ_INSERT_HEAD(&pthread__allqueue, idle, pt_allq); + pthread__sched_idle(self, idle); + } + + nthreads = 1; + /* Start up the SA subsystem */ + pthread__sa_start(); + SDPRINTF(("(pthread__start %p) Started.\n", self)); +} + + +/* General-purpose thread data structure sanitization. */ +void +pthread__initthread(pthread_t self, pthread_t t) +{ + int id; + + pthread_spinlock(self, &nextthread_lock); + id = nextthread; + nextthread++; + pthread_spinunlock(self, &nextthread_lock); + t->pt_num = id; + + t->pt_magic = PT_MAGIC; + t->pt_type = PT_THREAD_NORMAL; + t->pt_state = PT_STATE_RUNNABLE; + pthread_lockinit(&t->pt_statelock); + t->pt_spinlocks = 0; + t->pt_next = NULL; + t->pt_exitval = NULL; + t->pt_flags = 0; + t->pt_cancel = 0; + t->pt_errno = 0; + t->pt_parent = NULL; + t->pt_heldlock = NULL; + t->pt_switchto = NULL; + t->pt_sleepuc = NULL; + sigemptyset(&t->pt_siglist); + sigemptyset(&t->pt_sigmask); + pthread_lockinit(&t->pt_siglock); + PTQ_INIT(&t->pt_joiners); + pthread_lockinit(&t->pt_join_lock); + PTQ_INIT(&t->pt_cleanup_stack); + memset(&t->pt_specific, 0, sizeof(int) * PTHREAD_KEYS_MAX); +#ifdef PTHREAD__DEBUG + t->blocks = 0; + t->preempts = 0; + t->rescheds = 0; +#endif +} + + +int +pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*startfunc)(void *), void *arg) +{ + pthread_t self, newthread; + pthread_attr_t nattr; + int ret; + + PTHREADD_ADD(PTHREADD_CREATE); + assert(thread != NULL); + + /* + * It's okay to check this without a lock because there can + * only be one thread before it becomes true. + */ + if (pthread__started == 0) { + pthread__start(); + pthread__started = 1; + } + + if (attr == NULL) + nattr = pthread_default_attr; + else if (((attr != NULL) && (attr->pta_magic == PT_ATTR_MAGIC))) + nattr = *attr; + else + return EINVAL; + + + self = pthread__self(); + + pthread_spinlock(self, &pthread__deadqueue_lock); + if (!PTQ_EMPTY(&pthread__deadqueue)) { + newthread= PTQ_FIRST(&pthread__deadqueue); + PTQ_REMOVE(&pthread__deadqueue, newthread, pt_allq); + pthread_spinunlock(self, &pthread__deadqueue_lock); + } else { + pthread_spinunlock(self, &pthread__deadqueue_lock); + /* Set up a stack and allocate space for a pthread_st. */ + ret = pthread__stackalloc(&newthread); + if (ret != 0) + return ret; + } + + /* 2. Set up state. */ + pthread__initthread(self, newthread); + newthread->pt_flags = nattr.pta_flags; + newthread->pt_sigmask = self->pt_sigmask; + + /* + * 3. Set up context. + * + * The pt_uc pointer points to a location safely below the + * stack start; this is arranged by pthread__stackalloc(). + */ + _INITCONTEXT_U(newthread->pt_uc); + newthread->pt_uc->uc_stack = newthread->pt_stack; + newthread->pt_uc->uc_link = NULL; + makecontext(newthread->pt_uc, pthread__create_tramp, 2, + startfunc, arg); + + /* 4. Add to list of all threads. */ + pthread_spinlock(self, &pthread__allqueue_lock); + PTQ_INSERT_HEAD(&pthread__allqueue, newthread, pt_allq); + nthreads++; + pthread_spinunlock(self, &pthread__allqueue_lock); + + SDPRINTF(("(pthread_create %p) Created new thread %p.\n", self, newthread)); + /* 5. Put on run queue. */ + pthread__sched(self, newthread); + + *thread = newthread; + + return 0; +} + + +static void +pthread__create_tramp(void *(*start)(void *), void *arg) +{ + void *retval; + + retval = start(arg); + + pthread_exit(retval); + + /* NOTREACHED */ + assert(0); +} + + +/* + * Other threads will switch to the idle thread so that they + * can dispose of any awkward locks or recycle upcall state. + */ +void +pthread__idle(void) +{ + pthread_t self; + + PTHREADD_ADD(PTHREADD_IDLE); + self = pthread__self(); + SDPRINTF(("(pthread__idle %p).\n", self)); + + /* + * The drill here is that we want to yield the processor, + * but for the thread itself to be recovered, we need to be on + * a list somewhere for the thread system to know about us. + */ + pthread_spinlock(self, &pthread__deadqueue_lock); + PTQ_INSERT_TAIL(&pthread__reidlequeue, self, pt_runq); + self->pt_flags |= PT_FLAG_IDLED; + pthread_spinunlock(self, &pthread__deadqueue_lock); + + /* + * If we get to run this, then no preemption has happened + * (because the upcall handler will not continue an idle thread with + * PT_FLAG_IDLED set), and so we can yield the processor safely. + */ + SDPRINTF(("(pthread__idle %p) yielding.\n", self)); + sa_yield(); + + /* NOTREACHED */ + self->pt_spinlocks++; /* XXX make sure we get to finish the assert! */ + SDPRINTF(("(pthread__idle %p) Returned! Error.\n", self)); + assert(0); +} + + +void +pthread_exit(void *retval) +{ + pthread_t self, joiner; + struct pt_clean_t *cleanup; + int nt; + + self = pthread__self(); + SDPRINTF(("(pthread_exit %p) Exiting.\n", self)); + + /* Disable cancellability. */ + self->pt_flags |= PT_FLAG_CS_DISABLED; + + /* Call any cancellation cleanup handlers */ + while (!PTQ_EMPTY(&self->pt_cleanup_stack)) { + cleanup = PTQ_FIRST(&self->pt_cleanup_stack); + PTQ_REMOVE(&self->pt_cleanup_stack, cleanup, ptc_next); + (*cleanup->ptc_cleanup)(cleanup->ptc_arg); + } + + /* Perform cleanup of thread-specific data */ + pthread__destroy_tsd(self); + + self->pt_exitval = retval; + + pthread_spinlock(self, &self->pt_join_lock); + if (self->pt_flags & PT_FLAG_DETACHED) { + pthread_spinunlock(self, &self->pt_join_lock); + + pthread_spinlock(self, &pthread__allqueue_lock); + PTQ_REMOVE(&pthread__allqueue, self, pt_allq); + nthreads--; + nt = nthreads; + pthread_spinunlock(self, &pthread__allqueue_lock); + + self->pt_state = PT_STATE_DEAD; + if (nt == 0) { + /* Whoah, we're the last one. Time to go. */ + exit(0); + } + + /* Yeah, yeah, doing work while we're dead is tacky. */ + pthread_spinlock(self, &pthread__deadqueue_lock); + PTQ_INSERT_HEAD(&pthread__deadqueue, self, pt_allq); + pthread__block(self, &pthread__deadqueue_lock); + } else { + pthread_spinlock(self, &pthread__allqueue_lock); + nthreads--; + nt = nthreads; + self->pt_state = PT_STATE_ZOMBIE; + pthread_spinunlock(self, &pthread__allqueue_lock); + if (nt == 0) { + /* Whoah, we're the last one. Time to go. */ + exit(0); + } + /* Wake up all the potential joiners. Only one can win. + * (Can you say "Thundering Herd"? I knew you could.) + */ + PTQ_FOREACH(joiner, &self->pt_joiners, pt_sleep) + pthread__sched(self, joiner); + pthread__block(self, &self->pt_join_lock); + } + + /* NOTREACHED */ + assert(0); + exit(1); +} + + +int +pthread_join(pthread_t thread, void **valptr) +{ + pthread_t self; + + self = pthread__self(); + SDPRINTF(("(pthread_join %p) Joining %p.\n", self, thread)); + + if (pthread__find(self, thread) != 0) + return ESRCH; + + if (thread->pt_magic != PT_MAGIC) + return EINVAL; + + if (thread == self) + return EDEADLK; + + pthread_spinlock(self, &thread->pt_join_lock); + + if (thread->pt_flags & PT_FLAG_DETACHED) { + pthread_spinunlock(self, &thread->pt_join_lock); + return EINVAL; + } + + if ((thread->pt_state != PT_STATE_ZOMBIE) && + (thread->pt_state != PT_STATE_DEAD)) { + /* + * "I'm not dead yet!" + * "You will be soon enough." + */ + pthread_spinlock(self, &self->pt_statelock); + if (self->pt_cancel) { + pthread_spinunlock(self, &self->pt_statelock); + pthread_spinunlock(self, &thread->pt_join_lock); + pthread_exit(PTHREAD_CANCELED); + } + self->pt_state = PT_STATE_BLOCKED_QUEUE; + self->pt_sleepobj = thread; + self->pt_sleepq = &thread->pt_joiners; + self->pt_sleeplock = &thread->pt_join_lock; + pthread_spinunlock(self, &self->pt_statelock); + + PTQ_INSERT_TAIL(&thread->pt_joiners, self, pt_sleep); + pthread__block(self, &thread->pt_join_lock); + pthread_spinlock(self, &thread->pt_join_lock); + } + + if ((thread->pt_state == PT_STATE_DEAD) || + (thread->pt_flags & PT_FLAG_DETACHED)) { + /* Someone beat us to the join, or called pthread_detach(). */ + pthread_spinunlock(self, &thread->pt_join_lock); + return ESRCH; + } + + /* All ours. */ + thread->pt_state = PT_STATE_DEAD; + pthread_spinunlock(self, &thread->pt_join_lock); + + if (valptr != NULL) + *valptr = thread->pt_exitval; + + SDPRINTF(("(pthread_join %p) Joined %p.\n", self, thread)); + + /* Cleanup time. Move the dead thread from allqueue to the deadqueue */ + pthread_spinlock(self, &pthread__allqueue_lock); + PTQ_REMOVE(&pthread__allqueue, thread, pt_allq); + pthread_spinunlock(self, &pthread__allqueue_lock); + + pthread_spinlock(self, &pthread__deadqueue_lock); + PTQ_INSERT_HEAD(&pthread__deadqueue, thread, pt_allq); + pthread_spinunlock(self, &pthread__deadqueue_lock); + + return 0; +} + + +int +pthread_equal(pthread_t t1, pthread_t t2) +{ + + /* Nothing special here. */ + return (t1 == t2); +} + + +int +pthread_detach(pthread_t thread) +{ + pthread_t self, joiner; + + self = pthread__self(); + + if (pthread__find(self, thread) != 0) + return ESRCH; + + if (thread->pt_magic != PT_MAGIC) + return EINVAL; + + pthread_spinlock(self, &thread->pt_join_lock); + + if (thread->pt_flags & PT_FLAG_DETACHED) { + pthread_spinunlock(self, &thread->pt_join_lock); + return EINVAL; + } + + thread->pt_flags |= PT_FLAG_DETACHED; + + /* Any joiners have to be punted now. */ + PTQ_FOREACH(joiner, &thread->pt_joiners, pt_sleep) + pthread__sched(self, joiner); + + pthread_spinunlock(self, &thread->pt_join_lock); + + return 0; +} + + +int +pthread_attr_init(pthread_attr_t *attr) +{ + + attr->pta_magic = PT_ATTR_MAGIC; + attr->pta_flags = 0; + + return 0; +} + + +int +pthread_attr_destroy(pthread_attr_t *attr) +{ + + return 0; +} + + +int +pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate) +{ + + if ((attr == NULL) || (attr->pta_magic != PT_ATTR_MAGIC)) + return EINVAL; + + *detachstate = (attr->pta_flags & PT_FLAG_DETACHED); + + return 0; +} + + +int +pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) +{ + if ((attr == NULL) || (attr->pta_magic != PT_ATTR_MAGIC)) + return EINVAL; + + switch (detachstate) { + case PTHREAD_CREATE_JOINABLE: + attr->pta_flags &= ~PT_FLAG_DETACHED; + break; + case PTHREAD_CREATE_DETACHED: + attr->pta_flags |= PT_FLAG_DETACHED; + break; + default: + return EINVAL; + } + + return 0; +} + + +int +pthread_attr_setschedparam(pthread_attr_t *attr, + const struct sched_param *param) +{ + + if ((attr == NULL) || (attr->pta_magic != PT_ATTR_MAGIC)) + return EINVAL; + + if (param == NULL) + return EINVAL; + + if (param->sched_priority != 0) + return EINVAL; + + return 0; +} + + +int +pthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *param) +{ + + if ((attr == NULL) || (attr->pta_magic != PT_ATTR_MAGIC)) + return EINVAL; + + if (param == NULL) + return EINVAL; + + param->sched_priority = 0; + + return 0; +} + + +/* + * XXX There should be a way for applications to use the efficent + * inline version, but there are opacity/namespace issues. + */ +pthread_t +pthread_self(void) +{ + + return pthread__self(); +} + + +int +pthread_cancel(pthread_t thread) +{ + pthread_t self; + int flags; + + if (!(thread->pt_state == PT_STATE_RUNNING || + thread->pt_state == PT_STATE_RUNNABLE || + thread->pt_state == PT_STATE_BLOCKED_QUEUE || + thread->pt_state == PT_STATE_BLOCKED_SYS)) + return ESRCH; + + self = pthread__self(); + flags = thread->pt_flags; + + flags |= PT_FLAG_CS_PENDING; + if ((flags & PT_FLAG_CS_DISABLED) == 0) { + thread->pt_cancel = 1; + pthread_spinlock(self, &thread->pt_statelock); + if (thread->pt_state == PT_STATE_BLOCKED_SYS) { + /* + * It's sleeping in the kernel. If we can wake + * it up, it will notice the cancellation when + * it returns. If it doesn't wake up when we + * make this call, then it's blocked + * uninterruptably in the kernel, and there's + * not much to be done about it. + */ + _lwp_wakeup(thread->pt_blockedlwp); + } else if (thread->pt_state == PT_STATE_BLOCKED_QUEUE) { + /* + * We're blocked somewhere (pthread__block() + * was called. Cause it to wake up and the + * caller will check for the cancellation. + */ + pthread_spinlock(self, thread->pt_sleeplock); + PTQ_REMOVE(thread->pt_sleepq, thread, + pt_sleep); + pthread_spinunlock(self, thread->pt_sleeplock); + pthread__sched(self, thread); + } else { + /* + * Nothing. The target thread is running and will + * notice at the next deferred cancellation point. + */ + } + pthread_spinunlock(self, &thread->pt_statelock); + } + + thread->pt_flags = flags; + + return 0; +} + + +int +pthread_setcancelstate(int state, int *oldstate) +{ + pthread_t self; + int flags; + + self = pthread__self(); + flags = self->pt_flags; + + if (oldstate != NULL) { + if (flags & PT_FLAG_CS_DISABLED) + *oldstate = PTHREAD_CANCEL_DISABLE; + else + *oldstate = PTHREAD_CANCEL_ENABLE; + } + + if (state == PTHREAD_CANCEL_DISABLE) + flags |= PT_FLAG_CS_DISABLED; + else if (state == PTHREAD_CANCEL_ENABLE) { + flags &= ~PT_FLAG_CS_DISABLED; + /* + * If a cancellation was requested while cancellation + * was disabled, note that fact for future + * cancellation tests. + */ + if (flags & PT_FLAG_CS_PENDING) { + self->pt_cancel = 1; + /* This is not a deferred cancellation point. */ + if (flags & PT_FLAG_CS_ASYNC) + pthread_exit(PTHREAD_CANCELED); + } + } else + return EINVAL; + + self->pt_flags = flags; + + return 0; +} + + +int +pthread_setcanceltype(int type, int *oldtype) +{ + pthread_t self; + int flags; + + self = pthread__self(); + flags = self->pt_flags; + + if (oldtype != NULL) { + if (flags & PT_FLAG_CS_ASYNC) + *oldtype = PTHREAD_CANCEL_ASYNCHRONOUS; + else + *oldtype = PTHREAD_CANCEL_DEFERRED; + } + + if (type == PTHREAD_CANCEL_ASYNCHRONOUS) { + flags |= PT_FLAG_CS_ASYNC; + if (self->pt_cancel) + pthread_exit(PTHREAD_CANCELED); + } else if (type == PTHREAD_CANCEL_DEFERRED) + flags &= ~PT_FLAG_CS_ASYNC; + else + return EINVAL; + + self->pt_flags = flags; + + return 0; +} + + +void +pthread_testcancel() +{ + pthread_t self; + + self = pthread__self(); + if (self->pt_cancel) + pthread_exit(PTHREAD_CANCELED); +} + + +/* + * POSIX requires that certain functions return an error rather than + * invoking undefined behavior even when handed completely bogus + * pthread_t values, e.g. stack garbage or (pthread_t)666. This + * utility routine searches the list of threads for the pthread_t + * value without dereferencing it. + */ +int +pthread__find(pthread_t self, pthread_t id) +{ + pthread_t target; + + pthread_spinlock(self, &pthread__allqueue_lock); + PTQ_FOREACH(target, &pthread__allqueue, pt_allq) + if (target == id) + break; + pthread_spinunlock(self, &pthread__allqueue_lock); + + if (target == NULL) + return ESRCH; + + return 0; +} + + +void +pthread__testcancel(pthread_t self) +{ + + if (self->pt_cancel) + pthread_exit(PTHREAD_CANCELED); +} + + +void +pthread__cleanup_push(void (*cleanup)(void *), void *arg, void *store) +{ + pthread_t self; + struct pt_clean_t *entry; + + self = pthread__self(); + entry = store; + entry->ptc_cleanup = cleanup; + entry->ptc_arg = arg; + PTQ_INSERT_HEAD(&self->pt_cleanup_stack, entry, ptc_next); +} + + +void +pthread__cleanup_pop(int ex, void *store) +{ + pthread_t self; + struct pt_clean_t *entry; + + self = pthread__self(); + entry = store; + + PTQ_REMOVE(&self->pt_cleanup_stack, entry, ptc_next); + if (ex) + (*entry->ptc_cleanup)(entry->ptc_arg); +} + + +int * +pthread__errno(void) +{ + pthread_t self; + + self = pthread__self(); + + return &(self->pt_errno); +} |
