/* $NetBSD: kern_condvar.c,v 1.16 2008/03/17 16:54:51 ad Exp $ */ /*- * Copyright (c) 2006, 2007, 2008 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 __KERNEL_RCSID(0, "$NetBSD: kern_condvar.c,v 1.16 2008/03/17 16:54:51 ad Exp $"); #include #include #include #include #include #include static u_int cv_unsleep(lwp_t *, bool); static syncobj_t cv_syncobj = { SOBJ_SLEEPQ_SORTED, cv_unsleep, sleepq_changepri, sleepq_lendpri, syncobj_noowner, }; static const char deadcv[] = "deadcv"; /* * 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_is_valid(cv)); cv->cv_wmesg = deadcv; cv->cv_waiters = -3; #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, lwp_t *l) { sleepq_t *sq; KASSERT(cv_is_valid(cv)); KASSERT((l->l_pflag & LP_INTR) == 0 || panicstr != NULL); l->l_cv_signalled = 0; l->l_kpriority = true; sq = sleeptab_lookup(&sleeptab, cv); cv->cv_waiters++; sleepq_enter(sq, l); sleepq_enqueue(sq, cv, cv->cv_wmesg, &cv_syncobj); mutex_exit(mtx); return sq; } /* * cv_exit: * * After resuming execution, check to see if we have been restarted * as a result of cv_signal(). If we have, but cannot take the * wakeup (because of eg a pending Unix signal or timeout) then try * to ensure that another LWP sees it. This is necessary because * there may be multiple waiters, and at least one should take the * wakeup if possible. */ static inline int cv_exit(kcondvar_t *cv, kmutex_t *mtx, lwp_t *l, const int error) { mutex_enter(mtx); if (__predict_false(error != 0) && l->l_cv_signalled != 0) cv_signal(cv); KASSERT(cv_is_valid(cv)); return error; } /* * 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 u_int cv_unsleep(lwp_t *l, bool cleanup) { kcondvar_t *cv; cv = (kcondvar_t *)(uintptr_t)l->l_wchan; KASSERT(l->l_wchan != NULL); KASSERT(lwp_locked(l, l->l_sleepq->sq_mutex)); KASSERT(cv_is_valid(cv)); KASSERT(cv->cv_waiters > 0); cv->cv_waiters--; return sleepq_unsleep(l, cleanup); } /* * cv_wait: * * Wait non-interruptably on a condition variable until awoken. */ void cv_wait(kcondvar_t *cv, kmutex_t *mtx) { lwp_t *l = curlwp; sleepq_t *sq; KASSERT(mutex_owned(mtx)); if (sleepq_dontsleep(l)) { (void)sleepq_abort(mtx, 0); return; } sq = cv_enter(cv, mtx, l); (void)sleepq_block(0, false); (void)cv_exit(cv, mtx, l, 0); } /* * 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) { lwp_t *l = curlwp; sleepq_t *sq; int error; KASSERT(mutex_owned(mtx)); if (sleepq_dontsleep(l)) return sleepq_abort(mtx, 0); sq = cv_enter(cv, mtx, l); error = sleepq_block(0, true); return cv_exit(cv, mtx, l, 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) { lwp_t *l = curlwp; sleepq_t *sq; int error; KASSERT(mutex_owned(mtx)); if (sleepq_dontsleep(l)) return sleepq_abort(mtx, 0); sq = cv_enter(cv, mtx, l); error = sleepq_block(timo, false); return cv_exit(cv, mtx, l, 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) { lwp_t *l = curlwp; sleepq_t *sq; int error; KASSERT(mutex_owned(mtx)); if (sleepq_dontsleep(l)) return sleepq_abort(mtx, 0); sq = cv_enter(cv, mtx, l); error = sleepq_block(timo, true); return cv_exit(cv, mtx, l, 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) { lwp_t *l; sleepq_t *sq; KASSERT(cv_is_valid(cv)); 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--; l = sleepq_wake(sq, cv, 1); l->l_cv_signalled = 1; } else sleepq_unlock(sq); KASSERT(cv_is_valid(cv)); } /* * 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; KASSERT(cv_is_valid(cv)); 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); KASSERT(cv_is_valid(cv)); } /* * cv_wakeup: * * Wake all LWPs waiting on a condition variable. For cases * where the address may be waited on by mtsleep()/tsleep(). * Not a documented call. */ void cv_wakeup(kcondvar_t *cv) { sleepq_t *sq; KASSERT(cv_is_valid(cv)); sq = sleeptab_lookup(&sleeptab, cv); cv->cv_waiters = 0; sleepq_wake(sq, cv, (u_int)-1); KASSERT(cv_is_valid(cv)); } /* * cv_has_waiters: * * For diagnostic assertions: return non-zero if a condition * variable has waiters. */ bool cv_has_waiters(kcondvar_t *cv) { /* No need to interlock here */ return cv->cv_waiters != 0; } /* * cv_is_valid: * * For diagnostic assertions: return non-zero if a condition * variable appears to be valid. No locks need be held. */ bool cv_is_valid(kcondvar_t *cv) { if (cv->cv_wmesg == deadcv || cv->cv_wmesg == NULL) return false; if ((cv->cv_waiters & 0xff000000) != 0) { /* Arbitrary: invalid number of waiters. */ return false; } return cv->cv_waiters >= 0; }