diff options
author | jasone <jasone@FreeBSD.org> | 2001-01-16 01:00:43 +0000 |
---|---|---|
committer | jasone <jasone@FreeBSD.org> | 2001-01-16 01:00:43 +0000 |
commit | 20a8a23d2b99a3392aeb543a6d96167366f82e2a (patch) | |
tree | 6587dd7c0fd480a2d91b5a2c16e0d3ea150e4dce | |
parent | 437df10fdc957adbcfb372d6fc7638929d3df25e (diff) | |
download | FreeBSD-src-20a8a23d2b99a3392aeb543a6d96167366f82e2a.zip FreeBSD-src-20a8a23d2b99a3392aeb543a6d96167366f82e2a.tar.gz |
Implement condition variables.
-rw-r--r-- | share/man/man9/Makefile | 13 | ||||
-rw-r--r-- | share/man/man9/condvar.9 | 196 | ||||
-rw-r--r-- | sys/conf/files | 1 | ||||
-rw-r--r-- | sys/conf/options | 1 | ||||
-rw-r--r-- | sys/kern/kern_condvar.c | 547 | ||||
-rw-r--r-- | sys/kern/kern_sig.c | 9 | ||||
-rw-r--r-- | sys/kern/kern_synch.c | 14 | ||||
-rw-r--r-- | sys/sys/condvar.h | 75 | ||||
-rw-r--r-- | sys/sys/proc.h | 4 |
9 files changed, 851 insertions, 9 deletions
diff --git a/share/man/man9/Makefile b/share/man/man9/Makefile index 84b7872..9f15f79 100644 --- a/share/man/man9/Makefile +++ b/share/man/man9/Makefile @@ -13,7 +13,7 @@ MAN9= CONDSPLASSERT.9 DELAY.9 KASSERT.9 MD5.9 SPLASSERT.9 \ VOP_STRATEGY.9 \ accf_data.9 accf_http.9 at_exit.9 at_fork.9 atomic.9 \ bios.9 boot.9 buf.9 \ - cd.9 copy.9 \ + cd.9 condvar.9 copy.9 \ devfs_add_devswf.9 devfs_link.9 devfs_remove_dev.9 devstat.9 \ devtoname.9 \ fetch.9 \ @@ -77,6 +77,17 @@ MLINKS+=atomic.9 atomic_subtract.9 MLINKS+=atomic.9 atomic_store.9 MLINKS+=at_exit.9 rm_at_exit.9 MLINKS+=at_fork.9 rm_at_fork.9 +MLINKS+=condvar.9 cv_init.9 +MLINKS+=condvar.9 cv_destroy.9 +MLINKS+=condvar.9 cv_wait.9 +MLINKS+=condvar.9 cv_wait_sig.9 +MLINKS+=condvar.9 cv_timedwait.9 +MLINKS+=condvar.9 cv_timedwait_sig.9 +MLINKS+=condvar.9 cv_signal.9 +MLINKS+=condvar.9 cv_broadcast.9 +MLINKS+=condvar.9 cv_waitq_remove.9 +MLINKS+=condvar.9 cv_waitq_empty.9 +MLINKS+=condvar.9 cv_wmesg.9 MLINKS+=copy.9 copyin.9 copy.9 copyinstr.9 copy.9 copyout.9 copy.9 copystr.9 MLINKS+=devfs_link.9 devfs_makelink.9 MLINKS+=devstat.9 devicestat.9 devstat.9 devstat_add_entry.9 diff --git a/share/man/man9/condvar.9 b/share/man/man9/condvar.9 new file mode 100644 index 0000000..6e483c7 --- /dev/null +++ b/share/man/man9/condvar.9 @@ -0,0 +1,196 @@ +.\" +.\" Copyright (C) 2000 Jason Evans <jasone@freebsd.org>. All rights reserved. +.\" +.\" 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(s), this list of conditions and the following disclaimer as +.\" the first lines of this file unmodified other than the possible +.\" addition of one or more copyright notices. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice(s), this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``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 COPYRIGHT HOLDER(S) 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. +.\" +.\" $FreeBSD$ +.\" +.Dd December 11, 2000 +.Dt CONDVAR 9 +.Os +.Sh NAME +.Nm condvar , +.Nm cv_init , +.Nm cv_destroy , +.Nm cv_wait , +.Nm cv_wait_sig , +.Nm cv_timedwait , +.Nm cv_timedwait_sig , +.Nm cv_signal , +.Nm cv_broadcast , +.Nm cv_waitq_remove , +.Nm cv_waitq_empty , +.Nm cv_wmesg +.Nd kernel condition variable +.Sh SYNOPSIS +.Fd #include <sys/condvar.h> +.Ft void +.Fn cv_init "struct cv *cvp" "const char *desc" +.Ft void +.Fn cv_destroy "struct cv *cvp" +.Ft void +.Fn cv_wait "struct cv *cvp" "struct mtx *mp" +.Ft int +.Fn cv_wait_sig "struct cv *cvp" "struct mtx *mp" +.Ft int +.Fn cv_timedwait "struct cv *cvp" "struct mtx *mp" "int timo" +.Ft int +.Fn cv_timedwait_sig "struct cv *cvp" "struct mtx *mp" "int timo" +.Ft void +.Fn cv_signal "struct cv *cvp" +.Ft void +.Fn cv_broadcast "struct cv *cvp" +.Ft void +.Fn cv_waitq_remove "struct proc *p" +.Ft int +.Fn cv_waitq_empty "struct cv *cvp" +.Ft const char * +.Fn cv_wmesg "struct cv *cvp" +.Sh DESCRIPTION +Condition variables are used in conjunction with mutexes to wait for conditions +to occur. +Condition variables are created with +.Fn cv_init , +where +.Ar cvp +is a pointer to space for a +.Dv struct cv , +and +.Ar desc +is a pointer to a null-terminated character string that describes the condition +variable. +Condition variables are destroyed with +.Fn cv_destroy . +Threads wait on condition variables by calling +.Fn cv_wait , +.Fn cv_wait_sig , +.Fn cv_timedwait , +or +.Fn cv_timedwait_sig . +Threads unblock waiters by calling +.Fn cv_signal +to unblock one waiter, or +.Fn cv_broadcast +to unblock all waiters. +.Fn cv_waitq_remove +removes a waiting thread from a condition variable wait queue, if it is on one. +.Fn cv_waitq_empty +reports whether there are any waiters on +.Ar cvp . +.Fn cv_wmesg +returns the description string of +.Ar cvp , +as set by the initial call to +.Fn cv_init . +.Pp +A thread must hold +.Ar mp +before calling +.Fn cv_wait , +.Fn cv_wait_sig , +.Fn cv_timedwait , +or +.Fn cv_timedwait_sig . +When a thread waits on a condition, +.Ar mp +is atomically released before the thread is blocked, then atomically reacquired +before the function call returns. +All waiters must pass the same +.Ar mp +in conjunction with +.Ar cvp . +A thread must hold +.Ar mp +while calling +.Fn cv_signal +or +.Fn cv_broadcast , +even though it isn't passed as an argument. +.Pp +When +.Fn cv_wait , +.Fn cv_wait_sig , +.Fn cv_timedwait , +and +.Fn cv_timedwait_sig +unblock, their calling threads are made runnable. +.Fn cv_timedwait +and +.Fn cv_timedwait_sig +wait for at most +.Ar timo +/ +.Dv HZ +seconds before being unblocked and returning +.Er EWOULDBLOCK ; +otherwise, they return 0. +.Fn cv_wait_sig +and +.Fn cv_timedwait_sig +return prematurely with a value of +.Er EINTR +or +.Er ERESTART +if a signal is caught, or 0 if signaled via +.Fn cv_signal +or +.Fn cv_broadcast . +.Sh RETURN VALUES +If successful, +.Fn cv_wait_sig , +.Fn cv_timedwait , +and +.Fn cv_timedwait_sig +return 0. +Otherwise, a non-zero error code is returned. +.Pp +.Fn cv_waitq_empty +returns non-zero if there are no threads on the wait queue; 0 otherwise. +.Pp +.Fn cv_wmesg +returns the description string that was passed to +.Fn cv_init . +.Sh ERRORS +.Fn cv_wait_sig +and +.Fn cv_timedwait_sig +will fail if: +.Bl -tag -width Er +.It Bq Er EINTR +An unmasked signal was caught. +.It Bq Er ERESTART +A masked signal was caught. +.El +.Pp +.Fn cv_timedwait +and +.Fn cv_timedwait_sig +will fail if: +.Bl -tag -width Er +.It Bq Er EWOULDBLOCK +Timeout expired. +.El +.Sh SEE ALSO +.Xr msleep 9 , +.Xr mutex 9 diff --git a/sys/conf/files b/sys/conf/files index 96b47bb..6aed07e 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -618,6 +618,7 @@ kern/kern_acct.c standard kern/kern_acl.c standard kern/kern_cap.c standard kern/kern_clock.c standard +kern/kern_condvar.c standard kern/kern_conf.c standard kern/kern_descrip.c standard kern/kern_environment.c standard diff --git a/sys/conf/options b/sys/conf/options index 3b69aad..1a31bab 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -471,6 +471,7 @@ KTR_ENTRIES opt_global.h KTR_EXTEND opt_global.h KTR_VERBOSE opt_ktr.h MUTEX_DEBUG opt_global.h +CV_DEBUG opt_global.h WITNESS opt_global.h WITNESS_DDB opt_witness.h WITNESS_SKIPSPIN opt_witness.h diff --git a/sys/kern/kern_condvar.c b/sys/kern/kern_condvar.c new file mode 100644 index 0000000..2adee73 --- /dev/null +++ b/sys/kern/kern_condvar.c @@ -0,0 +1,547 @@ +/*- + * Copyright (c) 2000 Jake Burkholder <jake@freebsd.org>. + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + * + * $FreeBSD$ + */ + +#include "opt_ktrace.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/proc.h> +#include <sys/kernel.h> +#include <sys/ktr.h> +#include <sys/condvar.h> +#include <sys/mutex.h> +#include <sys/signalvar.h> +#include <sys/resourcevar.h> +#ifdef KTRACE +#include <sys/uio.h> +#include <sys/ktrace.h> +#endif + +/* + * Common sanity checks for cv_wait* functions. + */ +#define CV_ASSERT(cvp, mp, p) do { \ + KASSERT((p) != NULL, ("%s: curproc NULL", __FUNCTION__)); \ + KASSERT((p)->p_stat == SRUN, ("%s: not SRUN", __FUNCTION__)); \ + KASSERT((cvp) != NULL, ("%s: cvp NULL", __FUNCTION__)); \ + KASSERT((mp) != NULL, ("%s: mp NULL", __FUNCTION__)); \ + mtx_assert((mp), MA_OWNED | MA_NOTRECURSED); \ +} while (0) + +#ifdef CV_DEBUG +#define CV_WAIT_VALIDATE(cvp, mp) do { \ + if (TAILQ_EMPTY(&(cvp)->cv_waitq)) { \ + /* Only waiter. */ \ + (cvp)->cv_mtx = (mp); \ + } else { \ + /* \ + * Other waiter; assert that we're using the \ + * same mutex. \ + */ \ + KASSERT((cvp)->cv_mtx == (mp), \ + ("%s: Multiple mutexes", __FUNCTION__)); \ + } \ +} while (0) +#define CV_SIGNAL_VALIDATE(cvp) do { \ + if (!TAILQ_EMPTY(&(cvp)->cv_waitq)) { \ + KASSERT(mtx_owned((cvp)->cv_mtx), \ + ("%s: Mutex not owned", __FUNCTION__)); \ + } \ +} while (0) +#else +#define CV_WAIT_VALIDATE(cvp, mp) +#define CV_SIGNAL_VALIDATE(cvp) +#endif + +static void cv_timedwait_end(void *arg); + +/* + * Initialize a condition variable. Must be called before use. + */ +void +cv_init(struct cv *cvp, const char *desc) +{ + + TAILQ_INIT(&cvp->cv_waitq); + cvp->cv_mtx = NULL; + cvp->cv_description = desc; +} + +/* + * Destroy a condition variable. The condition variable must be re-initialized + * in order to be re-used. + */ +void +cv_destroy(struct cv *cvp) +{ + + KASSERT(cv_waitq_empty(cvp), ("%s: cv_waitq non-empty", __FUNCTION__)); +} + +/* + * Common code for cv_wait* functions. All require sched_lock. + */ + +/* + * Switch context. + */ +static __inline void +cv_switch(struct proc *p) +{ + + p->p_stat = SSLEEP; + p->p_stats->p_ru.ru_nvcsw++; + mi_switch(); + CTR3(KTR_PROC, "cv_switch: resume proc %p (pid %d, %s)", p, p->p_pid, + p->p_comm); +} + +/* + * Switch context, catching signals. + */ +static __inline int +cv_switch_catch(struct proc *p) +{ + int sig; + + /* + * We put ourselves on the sleep queue and start our timeout before + * calling CURSIG, as we could stop there, and a wakeup or a SIGCONT (or + * both) could occur while we were stopped. A SIGCONT would cause us to + * be marked as SSLEEP without resuming us, thus we must be ready for + * sleep when CURSIG is called. If the wakeup happens while we're + * stopped, p->p_wchan will be 0 upon return from CURSIG. + */ + p->p_flag |= P_SINTR; + mtx_exit(&sched_lock, MTX_SPIN); + /* proc_lock(p); */ + sig = CURSIG(p); + mtx_enter(&sched_lock, MTX_SPIN); + /* proc_unlock_noswitch(p); */ + if (sig != 0) { + if (p->p_wchan != NULL) + cv_waitq_remove(p); + p->p_stat = SRUN; + } else if (p->p_wchan != NULL) { + cv_switch(p); + } + p->p_flag &= ~P_SINTR; + + return sig; +} + +/* + * Add a process to the wait queue of a condition variable. + */ +static __inline void +cv_waitq_add(struct cv *cvp, struct proc *p) +{ + + /* + * Process may be sitting on a slpque if asleep() was called, remove it + * before re-adding. + */ + if (p->p_wchan != NULL) + unsleep(p); + + p->p_flag |= P_CVWAITQ; + p->p_wchan = cvp; + p->p_wmesg = cvp->cv_description; + p->p_slptime = 0; + p->p_nativepri = p->p_priority; + CTR3(KTR_PROC, "cv_waitq_add: proc %p (pid %d, %s)", p, p->p_pid, + p->p_comm); + TAILQ_INSERT_TAIL(&cvp->cv_waitq, p, p_slpq); +} + +/* + * Wait on a condition variable. The current process is placed on the condition + * variable's wait queue and suspended. A cv_signal or cv_broadcast on the same + * condition variable will resume the process. The mutex is released before + * sleeping and will be held on return. It is recommended that the mutex be + * held when cv_signal or cv_broadcast are called. + */ +void +cv_wait(struct cv *cvp, struct mtx *mp) +{ + struct proc *p; + WITNESS_SAVE_DECL(mp); + + p = CURPROC; +#ifdef KTRACE + if (p && KTRPOINT(p, KTR_CSW)) + ktrcsw(p->p_tracep, 1, 0); +#endif + CV_ASSERT(cvp, mp, p); + WITNESS_SLEEP(0, mp); + WITNESS_SAVE(mp, mp); + + mtx_enter(&sched_lock, MTX_SPIN); + if (cold || panicstr) { + /* + * After a panic, or during autoconfiguration, just give + * interrupts a chance, then just return; don't run any other + * procs or panic below, in case this is the idle process and + * already asleep. + */ + mtx_exit(&sched_lock, MTX_SPIN); + return; + } + CV_WAIT_VALIDATE(cvp, mp); + + DROP_GIANT_NOSWITCH(); + mtx_exit(mp, MTX_DEF | MTX_NOSWITCH); + + cv_waitq_add(cvp, p); + cv_switch(p); + curpriority = p->p_usrpri; + + mtx_exit(&sched_lock, MTX_SPIN); +#ifdef KTRACE + if (KTRPOINT(p, KTR_CSW)) + ktrcsw(p->p_tracep, 0, 0); +#endif + PICKUP_GIANT(); + mtx_enter(mp, MTX_DEF); + WITNESS_RESTORE(mp, mp); +} + +/* + * Wait on a condition variable, allowing interruption by signals. Return 0 if + * the process was resumed with cv_signal or cv_broadcast, EINTR or ERESTART if + * a signal was caught. If ERESTART is returned the system call should be + * restarted if possible. + */ +int +cv_wait_sig(struct cv *cvp, struct mtx *mp) +{ + struct proc *p; + int rval; + int sig; + WITNESS_SAVE_DECL(mp); + + p = CURPROC; + rval = 0; +#ifdef KTRACE + if (p && KTRPOINT(p, KTR_CSW)) + ktrcsw(p->p_tracep, 1, 0); +#endif + CV_ASSERT(cvp, mp, p); + WITNESS_SLEEP(0, mp); + WITNESS_SAVE(mp, mp); + + mtx_enter(&sched_lock, MTX_SPIN); + if (cold || panicstr) { + /* + * After a panic, or during autoconfiguration, just give + * interrupts a chance, then just return; don't run any other + * procs or panic below, in case this is the idle process and + * already asleep. + */ + mtx_exit(&sched_lock, MTX_SPIN); + return 0; + } + CV_WAIT_VALIDATE(cvp, mp); + + DROP_GIANT_NOSWITCH(); + mtx_exit(mp, MTX_DEF | MTX_NOSWITCH); + + cv_waitq_add(cvp, p); + sig = cv_switch_catch(p); + curpriority = p->p_usrpri; + + mtx_exit(&sched_lock, MTX_SPIN); + PICKUP_GIANT(); + + /* proc_lock(p); */ + if (sig == 0) + sig = CURSIG(p); + if (sig != 0) { + if (SIGISMEMBER(p->p_sigacts->ps_sigintr, sig)) + rval = EINTR; + else + rval = ERESTART; + } + /* proc_unlock(p); */ + +#ifdef KTRACE + if (KTRPOINT(p, KTR_CSW)) + ktrcsw(p->p_tracep, 0, 0); +#endif + mtx_enter(mp, MTX_DEF); + WITNESS_RESTORE(mp, mp); + + return (rval); +} + +/* + * Wait on a condition variable for at most timo/hz seconds. Returns 0 if the + * process was resumed by cv_signal or cv_broadcast, EWOULDBLOCK if the timeout + * expires. + */ +int +cv_timedwait(struct cv *cvp, struct mtx *mp, int timo) +{ + struct proc *p; + int rval; + WITNESS_SAVE_DECL(mp); + + p = CURPROC; + rval = 0; +#ifdef KTRACE + if (p && KTRPOINT(p, KTR_CSW)) + ktrcsw(p->p_tracep, 1, 0); +#endif + CV_ASSERT(cvp, mp, p); + WITNESS_SLEEP(0, mp); + WITNESS_SAVE(mp, mp); + + mtx_enter(&sched_lock, MTX_SPIN); + if (cold || panicstr) { + /* + * After a panic, or during autoconfiguration, just give + * interrupts a chance, then just return; don't run any other + * procs or panic below, in case this is the idle process and + * already asleep. + */ + mtx_exit(&sched_lock, MTX_SPIN); + return 0; + } + CV_WAIT_VALIDATE(cvp, mp); + + DROP_GIANT_NOSWITCH(); + mtx_exit(mp, MTX_DEF | MTX_NOSWITCH); + + cv_waitq_add(cvp, p); + callout_reset(&p->p_slpcallout, timo, cv_timedwait_end, p); + cv_switch(p); + curpriority = p->p_usrpri; + + if (p->p_flag & P_TIMEOUT) { + p->p_flag &= ~P_TIMEOUT; + rval = EWOULDBLOCK; + } else + callout_stop(&p->p_slpcallout); + + mtx_exit(&sched_lock, MTX_SPIN); +#ifdef KTRACE + if (KTRPOINT(p, KTR_CSW)) + ktrcsw(p->p_tracep, 0, 0); +#endif + PICKUP_GIANT(); + mtx_enter(mp, MTX_DEF); + WITNESS_RESTORE(mp, mp); + + return (rval); +} + +/* + * Wait on a condition variable for at most timo/hz seconds, allowing + * interruption by signals. Returns 0 if the process was resumed by cv_signal + * or cv_broadcast, EWOULDBLOCK if the timeout expires, and EINTR or ERESTART if + * a signal was caught. + */ +int +cv_timedwait_sig(struct cv *cvp, struct mtx *mp, int timo) +{ + struct proc *p; + int rval; + int sig; + WITNESS_SAVE_DECL(mp); + + p = CURPROC; + rval = 0; +#ifdef KTRACE + if (p && KTRPOINT(p, KTR_CSW)) + ktrcsw(p->p_tracep, 1, 0); +#endif + CV_ASSERT(cvp, mp, p); + WITNESS_SLEEP(0, mp); + WITNESS_SAVE(mp, mp); + + mtx_enter(&sched_lock, MTX_SPIN); + if (cold || panicstr) { + /* + * After a panic, or during autoconfiguration, just give + * interrupts a chance, then just return; don't run any other + * procs or panic below, in case this is the idle process and + * already asleep. + */ + mtx_exit(&sched_lock, MTX_SPIN); + return 0; + } + CV_WAIT_VALIDATE(cvp, mp); + + DROP_GIANT_NOSWITCH(); + mtx_exit(mp, MTX_DEF | MTX_NOSWITCH); + + cv_waitq_add(cvp, p); + callout_reset(&p->p_slpcallout, timo, cv_timedwait_end, p); + sig = cv_switch_catch(p); + curpriority = p->p_usrpri; + + if (p->p_flag & P_TIMEOUT) { + p->p_flag &= ~P_TIMEOUT; + rval = EWOULDBLOCK; + } else + callout_stop(&p->p_slpcallout); + + mtx_exit(&sched_lock, MTX_SPIN); + PICKUP_GIANT(); + + /* proc_lock(p); */ + if (sig == 0) + sig = CURSIG(p); + if (sig != 0) { + if (SIGISMEMBER(p->p_sigacts->ps_sigintr, sig)) + rval = EINTR; + else + rval = ERESTART; + } + /* proc_unlock(p); */ + +#ifdef KTRACE + if (KTRPOINT(p, KTR_CSW)) + ktrcsw(p->p_tracep, 0, 0); +#endif + mtx_enter(mp, MTX_DEF); + WITNESS_RESTORE(mp, mp); + + return (rval); +} + +/* + * Common code for signal and broadcast. Assumes waitq is not empty. Must be + * called with sched_lock held. + */ +static __inline void +cv_wakeup(struct cv *cvp) +{ + struct proc *p; + + p = TAILQ_FIRST(&cvp->cv_waitq); + KASSERT(p->p_wchan == cvp, ("%s: bogus wchan", __FUNCTION__)); + KASSERT(p->p_flag & P_CVWAITQ, ("%s: not on waitq", __FUNCTION__)); + TAILQ_REMOVE(&cvp->cv_waitq, p, p_slpq); + p->p_flag &= ~P_CVWAITQ; + p->p_wchan = 0; + if (p->p_stat == SSLEEP) { + /* OPTIMIZED EXPANSION OF setrunnable(p); */ + CTR3(KTR_PROC, "cv_signal: proc %p (pid %d, %s)", + p, p->p_pid, p->p_comm); + if (p->p_slptime > 1) + updatepri(p); + p->p_slptime = 0; + p->p_stat = SRUN; + if (p->p_flag & P_INMEM) { + setrunqueue(p); + maybe_resched(p); + } else { + p->p_flag |= P_SWAPINREQ; + wakeup(&proc0); + } + /* END INLINE EXPANSION */ + } +} + +/* + * Signal a condition variable, wakes up one waiting process. Will also wakeup + * the swapper if the process is not in memory, so that it can bring the + * sleeping process in. Note that this may also result in additional processes + * being made runnable. Should be called with the same mutex as was passed to + * cv_wait held. + */ +void +cv_signal(struct cv *cvp) +{ + + KASSERT(cvp != NULL, ("%s: cvp NULL", __FUNCTION__)); + mtx_enter(&sched_lock, MTX_SPIN); + if (!TAILQ_EMPTY(&cvp->cv_waitq)) { + CV_SIGNAL_VALIDATE(cvp); + cv_wakeup(cvp); + } + mtx_exit(&sched_lock, MTX_SPIN); +} + +/* + * Broadcast a signal to a condition variable. Wakes up all waiting processes. + * Should be called with the same mutex as was passed to cv_wait held. + */ +void +cv_broadcast(struct cv *cvp) +{ + + KASSERT(cvp != NULL, ("%s: cvp NULL", __FUNCTION__)); + mtx_enter(&sched_lock, MTX_SPIN); + CV_SIGNAL_VALIDATE(cvp); + while (!TAILQ_EMPTY(&cvp->cv_waitq)) + cv_wakeup(cvp); + mtx_exit(&sched_lock, MTX_SPIN); +} + +/* + * Remove a process from the wait queue of its condition variable. This may be + * called externally. + */ +void +cv_waitq_remove(struct proc *p) +{ + struct cv *cvp; + + mtx_enter(&sched_lock, MTX_SPIN); + if ((cvp = p->p_wchan) != NULL && p->p_flag & P_CVWAITQ) { + TAILQ_REMOVE(&cvp->cv_waitq, p, p_slpq); + p->p_flag &= ~P_CVWAITQ; + p->p_wchan = NULL; + } + mtx_exit(&sched_lock, MTX_SPIN); +} + +/* + * Timeout function for cv_timedwait. Put the process on the runqueue and set + * its timeout flag. + */ +static void +cv_timedwait_end(void *arg) +{ + struct proc *p; + + p = arg; + CTR3(KTR_PROC, "cv_timedwait_end: proc %p (pid %d, %s)", p, p->p_pid, + p->p_comm); + mtx_enter(&sched_lock, MTX_SPIN); + if (p->p_wchan != NULL) { + if (p->p_stat == SSLEEP) + setrunnable(p); + else + cv_waitq_remove(p); + p->p_flag |= P_TIMEOUT; + } + mtx_exit(&sched_lock, MTX_SPIN); +} diff --git a/sys/kern/kern_sig.c b/sys/kern/kern_sig.c index 29d2b60..70a3569 100644 --- a/sys/kern/kern_sig.c +++ b/sys/kern/kern_sig.c @@ -56,6 +56,7 @@ #include <sys/acct.h> #include <sys/fcntl.h> #include <sys/ipl.h> +#include <sys/condvar.h> #include <sys/mutex.h> #include <sys/wait.h> #include <sys/ktr.h> @@ -1229,8 +1230,12 @@ psignal(p, sig) * the process runnable, leave it stopped. */ mtx_enter(&sched_lock, MTX_SPIN); - if (p->p_wchan && p->p_flag & P_SINTR) - unsleep(p); + if (p->p_wchan && p->p_flag & P_SINTR) { + if (p->p_flag & P_CVWAITQ) + cv_waitq_remove(p); + else + unsleep(p); + } mtx_exit(&sched_lock, MTX_SPIN); goto out; diff --git a/sys/kern/kern_synch.c b/sys/kern/kern_synch.c index ff240db..cbef526 100644 --- a/sys/kern/kern_synch.c +++ b/sys/kern/kern_synch.c @@ -47,6 +47,7 @@ #include <sys/ipl.h> #include <sys/kernel.h> #include <sys/ktr.h> +#include <sys/condvar.h> #include <sys/lock.h> #include <sys/mutex.h> #include <sys/signalvar.h> @@ -77,10 +78,8 @@ static struct callout roundrobin_callout; static int curpriority_cmp __P((struct proc *p)); static void endtsleep __P((void *)); -static void maybe_resched __P((struct proc *chk)); static void roundrobin __P((void *arg)); static void schedcpu __P((void *arg)); -static void updatepri __P((struct proc *p)); static int sysctl_kern_quantum(SYSCTL_HANDLER_ARGS) @@ -130,7 +129,7 @@ curpriority_cmp(p) * Arrange to reschedule if necessary, taking the priorities and * schedulers into account. */ -static void +void maybe_resched(chk) struct proc *chk; { @@ -358,7 +357,7 @@ schedcpu(arg) * For all load averages >= 1 and max p_estcpu of 255, sleeping for at * least six times the loadfactor will decay p_estcpu to zero. */ -static void +void updatepri(p) register struct proc *p; { @@ -993,8 +992,11 @@ setrunnable(p) default: panic("setrunnable"); case SSTOP: - case SSLEEP: - unsleep(p); /* e.g. when sending signals */ + case SSLEEP: /* e.g. when sending signals */ + if (p->p_flag & P_CVWAITQ) + cv_waitq_remove(p); + else + unsleep(p); break; case SIDL: diff --git a/sys/sys/condvar.h b/sys/sys/condvar.h new file mode 100644 index 0000000..6e0581a --- /dev/null +++ b/sys/sys/condvar.h @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 2000 Jake Burkholder <jake@freebsd.org>. + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + * + * $FreeBSD$ + */ + +#ifndef _SYS_CONDVAR_H_ +#define _SYS_CONDVAR_H_ + +#ifndef LOCORE +#include <sys/queue.h> + +struct mtx; +struct proc; + +TAILQ_HEAD(cv_waitq, proc); + +/* + * Condition variable. + */ +struct cv { + struct cv_waitq cv_waitq; /* Queue of condition waiters. */ + struct mtx *cv_mtx; /* + * Mutex passed in by cv_*wait*(), + * currently only used for CV_DEBUG. + */ + const char *cv_description; +}; + +#ifdef _KERNEL +#define CV_DECLARE(modifiers, name, descr) \ + static const char __cv_descr_##name[] = descr; \ + modifiers struct cv name = {{NULL, NULL}, NULL, __cv_descr_##name} + +void cv_init(struct cv *cvp, const char *desc); +void cv_destroy(struct cv *cvp); + +void cv_wait(struct cv *cvp, struct mtx *mp); +int cv_wait_sig(struct cv *cvp, struct mtx *mp); +int cv_timedwait(struct cv *cvp, struct mtx *mp, int timo); +int cv_timedwait_sig(struct cv *cvp, struct mtx *mp, int timo); + +void cv_signal(struct cv *cvp); +void cv_broadcast(struct cv *cvp); + +void cv_waitq_remove(struct proc *p); + +#define cv_waitq_empty(cvp) (TAILQ_EMPTY(&cvp->cv_waitq)) +#define cv_wmesg(cvp) ((cvp)->cv_description) + +#endif /* _KERNEL */ +#endif /* !LOCORE */ +#endif /* _SYS_CONDVAR_H_ */ diff --git a/sys/sys/proc.h b/sys/sys/proc.h index b0c90a2..c3cc2f7 100644 --- a/sys/sys/proc.h +++ b/sys/sys/proc.h @@ -313,6 +313,8 @@ struct proc { #define P_OLDMASK 0x2000000 /* Need to restore mask after suspend. */ #define P_ALTSTACK 0x4000000 /* Have alternate signal stack. */ +#define P_CVWAITQ 0x8000000 /* proces is on a cv_waitq (not slpq) */ + #define P_MAGIC 0xbeefface #define P_CAN_SEE 1 @@ -520,6 +522,8 @@ void cpu_idle __P((void)); void cpu_switch __P((void)); void cpu_throw __P((void)) __dead2; void unsleep __P((struct proc *)); +void updatepri __P((struct proc *)); +void maybe_resched __P((struct proc *)); void cpu_exit __P((struct proc *)) __dead2; void exit1 __P((struct proc *, int)) __dead2; |