version 1.3.4.1, 2007/02/11 15:41:53 |
version 1.3.4.2, 2007/02/26 09:11:04 |
|
|
|
/* $NetBSD$ */ |
|
|
|
/*- |
|
* Copyright (c) 2006, 2007 The NetBSD Foundation, Inc. |
|
* All rights reserved. |
|
* |
|
* This code is derived from software contributed to The NetBSD Foundation |
|
* by Andrew Doran. |
|
* |
|
* 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. |
|
*/ |
|
|
|
/* |
|
* Kernel condition variable implementation, modeled after those found in |
|
* Solaris, a description of which can be found in: |
|
* |
|
* Solaris Internals: Core Kernel Architecture, Jim Mauro and |
|
* Richard McDougall. |
|
*/ |
|
|
|
#include <sys/cdefs.h> |
|
__KERNEL_RCSID(0, "$NetBSD$"); |
|
|
|
#include <sys/param.h> |
|
#include <sys/proc.h> |
|
#include <sys/sched.h> |
|
#include <sys/systm.h> |
|
#include <sys/condvar.h> |
|
#include <sys/sleepq.h> |
|
|
|
static void cv_unsleep(struct lwp *); |
|
static void cv_changepri(struct lwp *, int); |
|
|
|
syncobj_t cv_syncobj = { |
|
SOBJ_SLEEPQ_SORTED, |
|
cv_unsleep, |
|
cv_changepri, |
|
}; |
|
|
|
/* |
|
* cv_init: |
|
* |
|
* Initialize a condition variable for use. |
|
*/ |
|
void |
|
cv_init(kcondvar_t *cv, const char *wmesg) |
|
{ |
|
|
|
KASSERT(wmesg != NULL); |
|
|
|
cv->cv_wmesg = wmesg; |
|
cv->cv_waiters = 0; |
|
} |
|
|
|
/* |
|
* cv_destroy: |
|
* |
|
* Tear down a condition variable. |
|
*/ |
|
void |
|
cv_destroy(kcondvar_t *cv) |
|
{ |
|
|
|
#ifdef DIAGNOSTIC |
|
KASSERT(cv->cv_waiters == 0 && cv->cv_wmesg != NULL); |
|
cv->cv_wmesg = NULL; |
|
#endif |
|
} |
|
|
|
/* |
|
* cv_enter: |
|
* |
|
* Look up and lock the sleep queue corresponding to the given |
|
* condition variable, and increment the number of waiters. |
|
*/ |
|
static inline sleepq_t * |
|
cv_enter(kcondvar_t *cv, kmutex_t *mtx, struct lwp *l) |
|
{ |
|
sleepq_t *sq; |
|
|
|
KASSERT(cv->cv_wmesg != NULL); |
|
|
|
sq = sleeptab_lookup(&sleeptab, cv); |
|
cv->cv_waiters++; |
|
sleepq_enter(sq, l); |
|
mutex_exit(mtx); |
|
|
|
return sq; |
|
} |
|
|
|
/* |
|
* cv_unsleep: |
|
* |
|
* Remove an LWP from the condition variable and sleep queue. This |
|
* is called when the LWP has not been awoken normally but instead |
|
* interrupted: for example, when a signal is received. Must be |
|
* called with the LWP locked, and must return it unlocked. |
|
*/ |
|
static void |
|
cv_unsleep(struct lwp *l) |
|
{ |
|
uintptr_t addr; |
|
|
|
KASSERT(l->l_wchan != NULL); |
|
LOCK_ASSERT(lwp_locked(l, l->l_sleepq->sq_mutex)); |
|
|
|
addr = (uintptr_t)l->l_wchan; |
|
((kcondvar_t *)addr)->cv_waiters--; |
|
|
|
sleepq_unsleep(l); |
|
} |
|
|
|
/* |
|
* cv_changepri: |
|
* |
|
* Adjust the real (user) priority of an LWP blocked on a CV. |
|
*/ |
|
static void |
|
cv_changepri(struct lwp *l, int pri) |
|
{ |
|
sleepq_t *sq = l->l_sleepq; |
|
int opri; |
|
|
|
KASSERT(lwp_locked(l, sq->sq_mutex)); |
|
|
|
opri = l->l_priority; |
|
l->l_usrpri = pri; |
|
l->l_priority = sched_kpri(l); |
|
|
|
if (l->l_priority != opri) { |
|
TAILQ_REMOVE(&sq->sq_queue, l, l_sleepchain); |
|
sleepq_insert(sq, l, pri, l->l_syncobj); |
|
} |
|
} |
|
|
|
/* |
|
* cv_wait: |
|
* |
|
* Wait non-interruptably on a condition variable until awoken. |
|
*/ |
|
void |
|
cv_wait(kcondvar_t *cv, kmutex_t *mtx) |
|
{ |
|
struct lwp *l = curlwp; |
|
sleepq_t *sq; |
|
|
|
LOCK_ASSERT(mutex_owned(mtx)); |
|
|
|
if (sleepq_dontsleep(l)) { |
|
(void)sleepq_abort(mtx, 0); |
|
return; |
|
} |
|
|
|
sq = cv_enter(cv, mtx, l); |
|
sleepq_block(sq, sched_kpri(l), cv, cv->cv_wmesg, 0, 0, &cv_syncobj); |
|
(void)sleepq_unblock(0, 0); |
|
mutex_enter(mtx); |
|
} |
|
|
|
/* |
|
* cv_wait_sig: |
|
* |
|
* Wait on a condition variable until a awoken or a signal is received. |
|
* Will also return early if the process is exiting. Returns zero if |
|
* awoken normallly, ERESTART if a signal was received and the system |
|
* call is restartable, or EINTR otherwise. |
|
*/ |
|
int |
|
cv_wait_sig(kcondvar_t *cv, kmutex_t *mtx) |
|
{ |
|
struct lwp *l = curlwp; |
|
sleepq_t *sq; |
|
int error; |
|
|
|
LOCK_ASSERT(mutex_owned(mtx)); |
|
|
|
if (sleepq_dontsleep(l)) |
|
return sleepq_abort(mtx, 0); |
|
|
|
sq = cv_enter(cv, mtx, l); |
|
sleepq_block(sq, sched_kpri(l), cv, cv->cv_wmesg, 0, 1, &cv_syncobj); |
|
error = sleepq_unblock(0, 1); |
|
mutex_enter(mtx); |
|
|
|
return error; |
|
} |
|
|
|
/* |
|
* cv_timedwait: |
|
* |
|
* Wait on a condition variable until awoken or the specified timeout |
|
* expires. Returns zero if awoken normally or EWOULDBLOCK if the |
|
* timeout expired. |
|
*/ |
|
int |
|
cv_timedwait(kcondvar_t *cv, kmutex_t *mtx, int timo) |
|
{ |
|
struct lwp *l = curlwp; |
|
sleepq_t *sq; |
|
int error; |
|
|
|
LOCK_ASSERT(mutex_owned(mtx)); |
|
|
|
if (sleepq_dontsleep(l)) |
|
return sleepq_abort(mtx, 0); |
|
|
|
sq = cv_enter(cv, mtx, l); |
|
sleepq_block(sq, sched_kpri(l), cv, cv->cv_wmesg, timo, 0, &cv_syncobj); |
|
error = sleepq_unblock(timo, 0); |
|
mutex_enter(mtx); |
|
|
|
return error; |
|
} |
|
|
|
/* |
|
* cv_timedwait_sig: |
|
* |
|
* Wait on a condition variable until a timeout expires, awoken or a |
|
* signal is received. Will also return early if the process is |
|
* exiting. Returns zero if awoken normallly, EWOULDBLOCK if the |
|
* timeout expires, ERESTART if a signal was received and the system |
|
* call is restartable, or EINTR otherwise. |
|
*/ |
|
int |
|
cv_timedwait_sig(kcondvar_t *cv, kmutex_t *mtx, int timo) |
|
{ |
|
struct lwp *l = curlwp; |
|
sleepq_t *sq; |
|
int error; |
|
|
|
LOCK_ASSERT(mutex_owned(mtx)); |
|
|
|
if (sleepq_dontsleep(l)) |
|
return sleepq_abort(mtx, 0); |
|
|
|
sq = cv_enter(cv, mtx, l); |
|
sleepq_block(sq, sched_kpri(l), cv, cv->cv_wmesg, timo, 1, &cv_syncobj); |
|
error = sleepq_unblock(timo, 1); |
|
mutex_enter(mtx); |
|
|
|
return error; |
|
} |
|
|
|
/* |
|
* cv_signal: |
|
* |
|
* Wake the highest priority LWP waiting on a condition variable. |
|
* Must be called with the interlocking mutex held. |
|
*/ |
|
void |
|
cv_signal(kcondvar_t *cv) |
|
{ |
|
sleepq_t *sq; |
|
|
|
if (cv->cv_waiters == 0) |
|
return; |
|
|
|
/* |
|
* cv->cv_waiters may be stale and have dropped to zero, but |
|
* while holding the interlock (the mutex passed to cv_wait() |
|
* and similar) we will see non-zero values when it matters. |
|
*/ |
|
|
|
sq = sleeptab_lookup(&sleeptab, cv); |
|
if (cv->cv_waiters != 0) { |
|
cv->cv_waiters--; |
|
sleepq_wake(sq, cv, 1); |
|
} else |
|
sleepq_unlock(sq); |
|
} |
|
|
|
/* |
|
* cv_broadcast: |
|
* |
|
* Wake all LWPs waiting on a condition variable. Must be called |
|
* with the interlocking mutex held. |
|
*/ |
|
void |
|
cv_broadcast(kcondvar_t *cv) |
|
{ |
|
sleepq_t *sq; |
|
u_int cnt; |
|
|
|
if (cv->cv_waiters == 0) |
|
return; |
|
|
|
sq = sleeptab_lookup(&sleeptab, cv); |
|
if ((cnt = cv->cv_waiters) != 0) { |
|
cv->cv_waiters = 0; |
|
sleepq_wake(sq, cv, cnt); |
|
} else |
|
sleepq_unlock(sq); |
|
} |
|
|
|
/* |
|
* cv_wakeup: |
|
* |
|
* Wake all LWPs waiting on a condition variable. The interlock |
|
* need not be held, but it is the caller's responsibility to |
|
* ensure correct synchronization. |
|
*/ |
|
void |
|
cv_wakeup(kcondvar_t *cv) |
|
{ |
|
sleepq_t *sq; |
|
u_int cnt; |
|
|
|
sq = sleeptab_lookup(&sleeptab, cv); |
|
if ((cnt = cv->cv_waiters) != 0) { |
|
cv->cv_waiters = 0; |
|
sleepq_wake(sq, cv, cnt); |
|
} else |
|
sleepq_unlock(sq); |
|
} |
|
|
|
/* |
|
* cv_has_waiters: |
|
* |
|
* For diagnostic assertions: return non-zero if a condition |
|
* variable has waiters. |
|
*/ |
|
int |
|
cv_has_waiters(kcondvar_t *cv) |
|
{ |
|
|
|
/* No need to interlock here */ |
|
return (int)cv->cv_waiters; |
|
} |