diff options
Diffstat (limited to 'sys/kern/kern_timeout.c')
-rw-r--r-- | sys/kern/kern_timeout.c | 91 |
1 files changed, 90 insertions, 1 deletions
diff --git a/sys/kern/kern_timeout.c b/sys/kern/kern_timeout.c index feb702d..8cd3467 100644 --- a/sys/kern/kern_timeout.c +++ b/sys/kern/kern_timeout.c @@ -40,6 +40,7 @@ __FBSDID("$FreeBSD$"); #include <sys/param.h> #include <sys/systm.h> #include <sys/callout.h> +#include <sys/condvar.h> #include <sys/kernel.h> #include <sys/lock.h> #include <sys/mutex.h> @@ -71,6 +72,32 @@ struct mtx dont_sleep_in_callout; #endif static struct callout *nextsoftcheck; /* Next callout to be checked. */ +/* + * Locked by callout_lock: + * curr_callout - If a callout is in progress, it is curr_callout. + * If curr_callout is non-NULL, threads waiting on + * callout_wait will be woken up as soon as the + * relevant callout completes. + * wakeup_needed - If a thread is waiting on callout_wait, then + * wakeup_needed is nonzero. Increased only when + * cutt_callout is non-NULL. + * wakeup_ctr - Incremented every time a thread wants to wait + * for a callout to complete. Modified only when + * curr_callout is non-NULL. + */ +static struct callout *curr_callout; +static int wakeup_needed; +static int wakeup_ctr; +/* + * Locked by callout_wait_lock: + * callout_wait - If wakeup_needed is set, callout_wait will be + * triggered after the current callout finishes. + * wakeup_done_ctr - Set to the current value of wakeup_ctr after + * callout_wait is triggered. + */ +static struct mtx callout_wait_lock; +static struct cv callout_wait; +static int wakeup_done_ctr; /* * kern_timeout_callwheel_alloc() - kernel low level callwheel initialization @@ -122,6 +149,12 @@ kern_timeout_callwheel_init(void) #ifdef DIAGNOSTIC mtx_init(&dont_sleep_in_callout, "dont_sleep_in_callout", NULL, MTX_DEF); #endif + mtx_init(&callout_wait_lock, "callout_wait_lock", NULL, MTX_DEF); + cv_init(&callout_wait, "callout_wait"); + curr_callout = NULL; + wakeup_needed = 0; + wakeup_ctr = 0; + wakeup_done_ctr = 0; } /* @@ -150,6 +183,7 @@ softclock(void *dummy) int depth; int mpcalls; int gcalls; + int wakeup_cookie; #ifdef DIAGNOSTIC struct bintime bt1, bt2; struct timespec ts2; @@ -208,6 +242,7 @@ softclock(void *dummy) c->c_flags = (c->c_flags & ~CALLOUT_PENDING); } + curr_callout = c; mtx_unlock_spin(&callout_lock); if (!(c_flags & CALLOUT_MPSAFE)) { mtx_lock(&Giant); @@ -241,6 +276,21 @@ softclock(void *dummy) if (!(c_flags & CALLOUT_MPSAFE)) mtx_unlock(&Giant); mtx_lock_spin(&callout_lock); + curr_callout = NULL; + if (wakeup_needed) { + /* + * There might be someone waiting + * for the callout to complete. + */ + wakeup_cookie = wakeup_ctr; + mtx_unlock_spin(&callout_lock); + mtx_lock(&callout_wait_lock); + cv_broadcast(&callout_wait); + wakeup_done_ctr = wakeup_cookie; + mtx_unlock(&callout_wait_lock); + mtx_lock_spin(&callout_lock); + wakeup_needed = 0; + }; steps = 0; c = nextsoftcheck; } @@ -344,6 +394,17 @@ callout_reset(c, to_ticks, ftn, arg) { mtx_lock_spin(&callout_lock); + + if (c == curr_callout && wakeup_needed) { + /* + * We're being asked to reschedule a callout which is + * currently in progress, and someone has called + * callout_drain to kill that callout. Don't reschedule. + */ + mtx_unlock_spin(&callout_lock); + return; + }; + if (c->c_flags & CALLOUT_PENDING) callout_stop(c); @@ -364,18 +425,46 @@ callout_reset(c, to_ticks, ftn, arg) mtx_unlock_spin(&callout_lock); } +/* For binary compatibility */ +#undef callout_stop int callout_stop(c) struct callout *c; { + return(_callout_stop_safe(c, 0)); +} + +int +_callout_stop_safe(c, safe) + struct callout *c; + int safe; +{ + int wakeup_cookie; + mtx_lock_spin(&callout_lock); /* * Don't attempt to delete a callout that's not on the queue. */ if (!(c->c_flags & CALLOUT_PENDING)) { c->c_flags &= ~CALLOUT_ACTIVE; - mtx_unlock_spin(&callout_lock); + if (c == curr_callout && safe) { + /* We need to wait until the callout is finished */ + wakeup_needed = 1; + wakeup_cookie = wakeup_ctr++; + mtx_unlock_spin(&callout_lock); + mtx_lock(&callout_wait_lock); + /* + * Check to make sure that softclock() didn't + * do the wakeup in between our dropping + * callout_lock and picking up callout_wait_lock + */ + if (wakeup_cookie - wakeup_done_ctr > 0) + cv_wait(&callout_wait, &callout_wait_lock); + + mtx_unlock(&callout_wait_lock); + } else + mtx_unlock_spin(&callout_lock); return (0); } c->c_flags &= ~(CALLOUT_ACTIVE | CALLOUT_PENDING); |