mirror of https://github.com/dzavalishin/oskit/
589 lines
14 KiB
C
Executable File
589 lines
14 KiB
C
Executable File
/*
|
|
* Copyright (c) 1998, 1999, 2000 University of Utah and the Flux Group.
|
|
* All rights reserved.
|
|
*
|
|
* This file is part of the Flux OSKit. The OSKit is free software, also known
|
|
* as "open source;" you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License (GPL), version 2, as published by the Free
|
|
* Software Foundation (FSF). To explore alternate licensing terms, contact
|
|
* the University of Utah at csl-dist@cs.utah.edu or +1-801-585-3271.
|
|
*
|
|
* The OSKit is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GPL for more details. You should have
|
|
* received a copy of the GPL along with the OSKit; see the file COPYING. If
|
|
* not, write to the FSF, 59 Temple Place #330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include <malloc.h>
|
|
#include <threads/pthread_internal.h>
|
|
#include "pthread_cond.h"
|
|
|
|
/*
|
|
* Create and initialize a new condition variable
|
|
*/
|
|
int
|
|
pthread_cond_init(pthread_cond_t *c, const pthread_condattr_t *attr)
|
|
{
|
|
struct pthread_cond_impl *pimpl;
|
|
|
|
if (c == NULL)
|
|
return EINVAL;
|
|
|
|
if (! attr)
|
|
attr = &pthread_condattr_default;
|
|
|
|
if ((pimpl =
|
|
(struct pthread_cond_impl *) smalloc(sizeof(*pimpl))) == NULL)
|
|
return ENOMEM;
|
|
|
|
queue_init(&(pimpl->waiters));
|
|
pthread_lock_init(&(pimpl->lock));
|
|
c->impl = pimpl;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Destroy a condition variable by deallocating the implementation.
|
|
*/
|
|
int
|
|
pthread_cond_destroy(pthread_cond_t *c)
|
|
{
|
|
struct pthread_cond_impl *pimpl;
|
|
int enabled;
|
|
|
|
if (c == NULL || ((pimpl = c->impl) == NULL))
|
|
return EINVAL;
|
|
|
|
save_preemption_enable(enabled);
|
|
disable_preemption();
|
|
|
|
pthread_lock(&(pimpl->lock));
|
|
|
|
if (! queue_empty(&(pimpl->waiters))) {
|
|
printf("pthread_cond_destroy: "
|
|
"condition variable %p/%p still in use\n", c, pimpl);
|
|
pthread_unlock(&(pimpl->lock));
|
|
restore_preemption_enable(enabled);
|
|
return EINVAL;
|
|
}
|
|
|
|
sfree(pimpl, sizeof(*pimpl));
|
|
c->impl = NULL;
|
|
restore_preemption_enable(enabled);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Wait on a condition. The timedwait version is below. Because of the
|
|
* timer, conditions have to be manipulated at splhigh to avoid deadlock
|
|
* with the timer interrupt, which needs to lock both the thread and the
|
|
* condition so it can remove the thread from the waitlist and restart
|
|
* it. The length of time at splhigh is unfortunate.
|
|
*/
|
|
int
|
|
pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m)
|
|
{
|
|
pthread_thread_t *pthread = CURPTHREAD();
|
|
struct pthread_cond_impl *pimpl;
|
|
int enabled, rc;
|
|
|
|
pthread_testcancel();
|
|
|
|
if (c->impl == NULL && ((rc = pthread_cond_init(c, NULL))))
|
|
return rc;
|
|
|
|
save_preemption_enable(enabled);
|
|
disable_preemption();
|
|
|
|
pimpl = c->impl;
|
|
|
|
/* Must block interrupts before taking condvar lock or waitlock */
|
|
assert_interrupts_enabled();
|
|
disable_interrupts();
|
|
|
|
pthread_lock(&(pimpl->lock));
|
|
|
|
/* place ourself on the queue */
|
|
queue_check(&(pimpl->waiters), pthread);
|
|
queue_enter(&(pimpl->waiters), pthread, pthread_thread_t *, chain);
|
|
|
|
/* unlock mutex */
|
|
pthread_mutex_unlock(m);
|
|
|
|
pthread_lock(&pthread->waitlock);
|
|
pthread_unlock(&(pimpl->lock));
|
|
|
|
pthread->waitflags |= THREAD_WS_CONDWAIT;
|
|
pthread->waitcond = c;
|
|
|
|
/* and block */
|
|
pthread_sched_reschedule(RESCHED_BLOCK, &pthread->waitlock);
|
|
|
|
assert_interrupts_disabled();
|
|
enable_interrupts();
|
|
restore_preemption_enable(enabled);
|
|
|
|
/* grab mutex before returning */
|
|
pthread_mutex_lock(m);
|
|
|
|
/* Look for a cancelation point */
|
|
pthread_testcancel();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Internal (safe) version that does not call pthread_testcancel.
|
|
*/
|
|
int
|
|
pthread_cond_wait_safe(pthread_cond_t *c, pthread_mutex_t *m)
|
|
{
|
|
pthread_thread_t *pthread = CURPTHREAD();
|
|
struct pthread_cond_impl *pimpl;
|
|
int enabled, preemptable;
|
|
|
|
assert(c && c->impl);
|
|
|
|
save_preemption_enable(preemptable);
|
|
disable_preemption();
|
|
|
|
pimpl = c->impl;
|
|
|
|
/* Must block interrupts before taking condvar lock or waitlock */
|
|
enabled = save_disable_interrupts();
|
|
|
|
pthread_lock(&(pimpl->lock));
|
|
|
|
/* place ourself on the queue */
|
|
queue_check(&(pimpl->waiters), pthread);
|
|
queue_enter(&(pimpl->waiters), pthread, pthread_thread_t *, chain);
|
|
|
|
/* unlock mutex */
|
|
pthread_mutex_unlock(m);
|
|
|
|
pthread_lock(&pthread->waitlock);
|
|
pthread_unlock(&(pimpl->lock));
|
|
|
|
pthread->waitflags |= THREAD_WS_CONDWAIT;
|
|
pthread->waitcond = c;
|
|
|
|
#ifdef THREADS_DEBUG
|
|
pthread_lock(&threads_sleepers_lock);
|
|
threads_sleepers++;
|
|
pthread_unlock(&threads_sleepers_lock);
|
|
#endif
|
|
/* and block */
|
|
pthread_sched_reschedule(RESCHED_BLOCK, &pthread->waitlock);
|
|
|
|
#ifdef THREADS_DEBUG
|
|
pthread_lock(&threads_sleepers_lock);
|
|
threads_sleepers--;
|
|
pthread_unlock(&threads_sleepers_lock);
|
|
#endif
|
|
restore_interrupt_enable(enabled);
|
|
restore_preemption_enable(preemptable);
|
|
|
|
/* grab mutex before returning */
|
|
pthread_mutex_lock(m);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The timed version. Time is given as an absolute time in the future.
|
|
*/
|
|
int
|
|
pthread_cond_timedwait(pthread_cond_t *c, pthread_mutex_t *m,
|
|
oskit_timespec_t *abstime)
|
|
{
|
|
pthread_thread_t *pthread = CURPTHREAD();
|
|
struct pthread_cond_impl *pimpl;
|
|
int enabled, error = 0;
|
|
oskit_timespec_t now;
|
|
struct oskit_itimerspec ts = {{ 0, 0 }, { 0, 0 }};
|
|
extern oskit_clock_t *oskit_system_clock; /* XXX */
|
|
|
|
pthread_testcancel();
|
|
|
|
if (c->impl == NULL && ((error = pthread_cond_init(c, NULL))))
|
|
return error;
|
|
|
|
save_preemption_enable(enabled);
|
|
disable_preemption();
|
|
|
|
pimpl = c->impl;
|
|
|
|
/* Must block interrupts before taking condvar lock or waitlock */
|
|
assert_interrupts_enabled();
|
|
disable_interrupts();
|
|
|
|
pthread_lock(&(pimpl->lock));
|
|
|
|
/*
|
|
* Oh, this is such a dumb interface! Now must check that the abstime
|
|
* has not passed, which means another check of the system clock,
|
|
* which is what the caller just did. But since the caller might not
|
|
* have disabled interrupts, the time could have passed by this point.
|
|
*/
|
|
oskit_clock_gettime(oskit_system_clock, &now);
|
|
if (now.tv_sec > abstime->tv_sec ||
|
|
(now.tv_sec == abstime->tv_sec &&
|
|
now.tv_nsec >= abstime->tv_nsec)) {
|
|
error = ETIMEDOUT;
|
|
pthread_unlock(&(pimpl->lock));
|
|
/* unlock mutex */
|
|
pthread_mutex_unlock(m);
|
|
goto skip;
|
|
}
|
|
|
|
/* place ourself on the queue */
|
|
queue_check(&(pimpl->waiters), pthread);
|
|
queue_enter(&(pimpl->waiters), pthread, pthread_thread_t *, chain);
|
|
|
|
/* unlock mutex */
|
|
pthread_mutex_unlock(m);
|
|
|
|
pthread_lock(&pthread->waitlock);
|
|
pthread_unlock(&(pimpl->lock));
|
|
|
|
/* Start the timer */
|
|
ts.it_value.tv_sec = abstime->tv_sec;
|
|
ts.it_value.tv_nsec = abstime->tv_nsec;
|
|
oskit_timer_settime(pthread->condtimer, OSKIT_TIMER_ABSTIME, &ts);
|
|
|
|
pthread->waitflags |= THREAD_WS_CONDWAIT;
|
|
pthread->waitcond = c;
|
|
|
|
#ifdef THREADS_DEBUG
|
|
pthread_lock(&threads_sleepers_lock);
|
|
threads_sleepers++;
|
|
pthread_unlock(&threads_sleepers_lock);
|
|
#endif
|
|
/* and block */
|
|
pthread_sched_reschedule(RESCHED_BLOCK, &pthread->waitlock);
|
|
|
|
#ifdef THREADS_DEBUG
|
|
pthread_lock(&threads_sleepers_lock);
|
|
threads_sleepers--;
|
|
pthread_unlock(&threads_sleepers_lock);
|
|
#endif
|
|
/*
|
|
* Look for timeout.
|
|
*/
|
|
pthread_lock(&pthread->waitlock);
|
|
|
|
if (pthread->waitflags & THREAD_WS_TIMEDOUT) {
|
|
pthread->waitflags &= ~THREAD_WS_TIMEDOUT;
|
|
error = ETIMEDOUT;
|
|
}
|
|
else {
|
|
error = 0;
|
|
|
|
/* disarm the timer. */
|
|
ts.it_value.tv_sec = 0;
|
|
ts.it_value.tv_nsec = 0;
|
|
oskit_timer_settime(pthread->condtimer, 0, &ts);
|
|
}
|
|
|
|
pthread_unlock(&pthread->waitlock);
|
|
|
|
skip:
|
|
enable_interrupts();
|
|
restore_preemption_enable(enabled);
|
|
|
|
/* grab mutex before returning */
|
|
pthread_mutex_lock(m);
|
|
|
|
/* Look for a cancelation point */
|
|
pthread_testcancel();
|
|
|
|
return error;
|
|
}
|
|
|
|
#ifdef CPU_INHERIT
|
|
/*
|
|
* The donating version.
|
|
*/
|
|
int
|
|
pthread_cond_donate_wait(pthread_cond_t *c, pthread_mutex_t *m,
|
|
oskit_timespec_t *abstime, pthread_t donee_tid)
|
|
{
|
|
pthread_thread_t *pthread = CURPTHREAD();
|
|
pthread_thread_t *donee;
|
|
struct pthread_cond_impl *pimpl;
|
|
int enabled, error = 0;
|
|
oskit_timespec_t now;
|
|
struct oskit_itimerspec ts = {{ 0, 0 }, { 0, 0 }};
|
|
extern oskit_clock_t *oskit_system_clock; /* XXX */
|
|
|
|
pthread_testcancel();
|
|
|
|
if (c->impl == NULL && ((error = pthread_cond_init(c, NULL))))
|
|
return error;
|
|
|
|
save_preemption_enable(enabled);
|
|
disable_preemption();
|
|
|
|
if ((donee = tidtothread(donee_tid)) == NULL_THREADPTR) {
|
|
restore_preemption_enable(enabled);
|
|
return EINVAL;
|
|
}
|
|
|
|
pimpl = c->impl;
|
|
|
|
/* Must block interrupts before taking condvar lock or waitlock */
|
|
assert_interrupts_enabled();
|
|
disable_interrupts();
|
|
|
|
pthread_lock(&(pimpl->lock));
|
|
|
|
/*
|
|
* Oh, this is such a dumb interface! Now must check that the abstime
|
|
* has not passed, which means another check of the system clock,
|
|
* which is what the caller just did. But since the caller might not
|
|
* have disabled interrupts, the time could have passed by this point.
|
|
*/
|
|
if (abstime) {
|
|
oskit_clock_gettime(oskit_system_clock, &now);
|
|
if (now.tv_sec > abstime->tv_sec ||
|
|
(now.tv_sec == abstime->tv_sec &&
|
|
now.tv_nsec >= abstime->tv_nsec)) {
|
|
error = ETIMEDOUT;
|
|
pthread_unlock(&(pimpl->lock));
|
|
/* unlock mutex */
|
|
pthread_mutex_unlock(m);
|
|
goto skip;
|
|
}
|
|
}
|
|
|
|
/* place ourself on the queue */
|
|
queue_check(&(pimpl->waiters), pthread);
|
|
queue_enter(&(pimpl->waiters), pthread, pthread_thread_t *, chain);
|
|
|
|
/* unlock mutex */
|
|
pthread_mutex_unlock(m);
|
|
|
|
pthread_lock(&pthread->waitlock);
|
|
pthread_unlock(&(pimpl->lock));
|
|
|
|
/* Start the timer */
|
|
if (abstime) {
|
|
ts.it_value.tv_sec = abstime->tv_sec;
|
|
ts.it_value.tv_nsec = abstime->tv_nsec;
|
|
oskit_timer_settime(pthread->condtimer,
|
|
OSKIT_TIMER_ABSTIME, &ts);
|
|
}
|
|
|
|
pthread->waitflags |= THREAD_WS_CONDWAIT;
|
|
pthread->waitcond = c;
|
|
|
|
#ifdef THREADS_DEBUG
|
|
pthread_lock(&threads_sleepers_lock);
|
|
threads_sleepers++;
|
|
pthread_unlock(&threads_sleepers_lock);
|
|
#endif
|
|
/* and donate/block */
|
|
pthread_sched_thread_wait(donee,
|
|
(queue_head_t *) 0, &pthread->waitlock);
|
|
#ifdef THREADS_DEBUG
|
|
pthread_lock(&threads_sleepers_lock);
|
|
threads_sleepers--;
|
|
pthread_unlock(&threads_sleepers_lock);
|
|
#endif
|
|
/*
|
|
* Look for timeout.
|
|
*/
|
|
if (abstime) {
|
|
pthread_lock(&pthread->waitlock);
|
|
|
|
if (pthread->waitflags & THREAD_WS_TIMEDOUT) {
|
|
pthread->waitflags &= ~THREAD_WS_TIMEDOUT;
|
|
error = ETIMEDOUT;
|
|
}
|
|
else {
|
|
error = 0;
|
|
|
|
/* disarm the timer. */
|
|
ts.it_value.tv_sec = 0;
|
|
ts.it_value.tv_nsec = 0;
|
|
oskit_timer_settime(pthread->condtimer, 0, &ts);
|
|
}
|
|
|
|
pthread_unlock(&pthread->waitlock);
|
|
}
|
|
|
|
skip:
|
|
enable_interrupts();
|
|
restore_preemption_enable(enabled);
|
|
|
|
/* grab mutex before returning */
|
|
pthread_mutex_lock(m);
|
|
|
|
/* Look for a cancelation point */
|
|
pthread_testcancel();
|
|
|
|
return error;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Timer expiration. Note that two locks are taken at interrupt level;
|
|
* the condition lock and the pthread waitlock.
|
|
*/
|
|
oskit_error_t
|
|
pthread_condwait_timeout(struct oskit_iunknown *listener, void *arg)
|
|
{
|
|
pthread_thread_t *pthread = arg;
|
|
pthread_cond_t *c;
|
|
struct pthread_cond_impl *pimpl;
|
|
int enabled;
|
|
|
|
/* Just in case some device driver renabled interrupts! */
|
|
enabled = save_disable_interrupts();
|
|
|
|
pthread_lock(&pthread->waitlock);
|
|
|
|
DPRINTF("%p %d %p 0x%x\n",
|
|
CURPTHREAD(), enabled, pthread, (int) pthread->waitflags);
|
|
|
|
/*
|
|
* Already woken up? Maybe from kill?
|
|
*/
|
|
if (! (pthread->waitflags & THREAD_WS_CONDWAIT)) {
|
|
pthread_unlock(&pthread->waitlock);
|
|
restore_interrupt_enable(enabled);
|
|
return 0;
|
|
}
|
|
|
|
c = pthread->waitcond;
|
|
assert(c && c->impl);
|
|
pimpl = c->impl;
|
|
|
|
/*
|
|
* Otherwise, lock the condition and remove the thread from
|
|
* the condition queue.
|
|
*/
|
|
pthread_lock(&(pimpl->lock));
|
|
queue_remove(&(pimpl->waiters), pthread, pthread_thread_t *, chain);
|
|
pthread_unlock(&(pimpl->lock));
|
|
|
|
pthread->waitflags |= THREAD_WS_TIMEDOUT;
|
|
pthread->waitflags &= ~THREAD_WS_CONDWAIT;
|
|
pthread_unlock(&pthread->waitlock);
|
|
|
|
/*
|
|
* And place it back on the runq. Request an AST if the current
|
|
* thread is no longer the thread that should be running.
|
|
*/
|
|
if (pthread_sched_setrunnable(pthread))
|
|
softint_request(SOFTINT_ASYNCREQ);
|
|
|
|
DPRINTF("ends ...\n");
|
|
|
|
restore_interrupt_enable(enabled);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Signal a condition, waking up the highest priority thread waiting.
|
|
*/
|
|
int
|
|
pthread_cond_signal(pthread_cond_t *c)
|
|
{
|
|
struct pthread_cond_impl *pimpl;
|
|
pthread_thread_t *pnext;
|
|
int enabled, preemptable;
|
|
|
|
if (c == NULL || ((pimpl = c->impl) == NULL))
|
|
return EINVAL;
|
|
|
|
save_preemption_enable(preemptable);
|
|
disable_preemption();
|
|
|
|
/* Must block interrupts before taking condvar lock or waitlock */
|
|
enabled = save_disable_interrupts();
|
|
|
|
pthread_lock(&(pimpl->lock));
|
|
|
|
if (queue_empty(&(pimpl->waiters))) {
|
|
pthread_unlock(&(pimpl->lock));
|
|
restore_interrupt_enable(enabled);
|
|
restore_preemption_enable(preemptable);
|
|
return 0;
|
|
}
|
|
|
|
pnext = pthread_dequeue_fromQ(&(pimpl->waiters));
|
|
pthread_unlock(&(pimpl->lock));
|
|
|
|
/*
|
|
* Note race condition. Someone can try to cancel this thread
|
|
* between the time it comes off the queue, but before the state
|
|
* gets changed. pthread_cancel will have to deal with this.
|
|
*/
|
|
pthread_lock(&pnext->waitlock);
|
|
pnext->waitflags &= ~THREAD_WS_CONDWAIT;
|
|
pnext->waitcond = 0;
|
|
pthread_unlock(&pnext->waitlock);
|
|
pthread_sched_setrunnable(pnext);
|
|
|
|
restore_interrupt_enable(enabled);
|
|
restore_preemption_enable(preemptable);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Wake up all threads waiting on a condition.
|
|
*/
|
|
int
|
|
pthread_cond_broadcast(pthread_cond_t *c)
|
|
{
|
|
struct pthread_cond_impl *pimpl;
|
|
pthread_thread_t *pnext;
|
|
int enabled, preemptable;
|
|
|
|
if (c == NULL || ((pimpl = c->impl) == NULL))
|
|
return EINVAL;
|
|
|
|
save_preemption_enable(preemptable);
|
|
disable_preemption();
|
|
|
|
/* Must block interrupts before taking condvar lock or waitlock */
|
|
enabled = save_disable_interrupts();
|
|
|
|
pthread_lock(&(pimpl->lock));
|
|
|
|
if (queue_empty(&(pimpl->waiters))) {
|
|
pthread_unlock(&(pimpl->lock));
|
|
restore_interrupt_enable(enabled);
|
|
restore_preemption_enable(preemptable);
|
|
return 0;
|
|
}
|
|
|
|
queue_iterate(&(pimpl->waiters), pnext, pthread_thread_t *, chain) {
|
|
if (pnext == CURPTHREAD())
|
|
panic("pthread_cond_broadcast: Bad queue!");
|
|
|
|
/*
|
|
* Note race condition. Someone can try to cancel this
|
|
* thread between the time it comes off the queue, but
|
|
* before the state gets changed. pthread_cancel will
|
|
* have to deal with this.
|
|
*/
|
|
pthread_lock(&pnext->waitlock);
|
|
pnext->waitflags &= ~THREAD_WS_CONDWAIT;
|
|
pnext->waitcond = 0;
|
|
pthread_unlock(&pnext->waitlock);
|
|
pthread_sched_setrunnable(pnext);
|
|
}
|
|
queue_init(&(pimpl->waiters));
|
|
pthread_unlock(&(pimpl->lock));
|
|
|
|
restore_interrupt_enable(enabled);
|
|
restore_preemption_enable(preemptable);
|
|
|
|
return 0;
|
|
}
|