Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/trunk]: src/sys New cv_timedwaitclock, cv_timedwaitclock_sig.



details:   https://anonhg.NetBSD.org/src/rev/7b14a56bb2ac
branches:  trunk
changeset: 932241:7b14a56bb2ac
user:      riastradh <riastradh%NetBSD.org@localhost>
date:      Sun May 03 01:24:37 2020 +0000

description:
New cv_timedwaitclock, cv_timedwaitclock_sig.

Usage: given a struct timespec timeout copied from userland, along
with a clockid and TIMER_* flags,

        error = cv_timedwaitclock(cv, lock, timeout, clockid, flags,
            DEFAULT_TIMEOUT_EPSILON);
        if (error)
                /* fail */

If flags is relative (i.e., (flags & TIMER_ABSTIME) == 0), then this
deducts the time spent waiting from timeout, so you can run it in a
loop:

        struct timespec timeout;

        error = copyin(SCARG(uap, timeout), &timeout, sizeof timeout);
        if (error)
                return error;

        mutex_enter(lock);
        while (!ready()) {
                error = cv_timedwaitclock_sig(cv, lock, &timeout,
                    SCARG(uap, clockid), SCARG(uap, flags),
                    DEFAULT_TIMEOUT_EPSILON);
                if (error)
                        break;
        }
        mutex_exit(lock);

CAVEAT: If the system call is interrupted by a signal with SA_RESTART
so cv_timedwaitclock_sig fails with ERESTART, then the system call
will be restarted with the _original_ relative timeout, not counting
the time that was already spent waiting.  This is a problem but it's
not a problem I want to deal with at the moment.

diffstat:

 sys/kern/kern_condvar.c |  187 +++++++++++++++++++++++++++++++++++++++++++++++-
 sys/sys/condvar.h       |    7 +-
 2 files changed, 191 insertions(+), 3 deletions(-)

diffs (236 lines):

diff -r bca62788a569 -r 7b14a56bb2ac sys/kern/kern_condvar.c
--- a/sys/kern/kern_condvar.c   Sun May 03 01:20:37 2020 +0000
+++ b/sys/kern/kern_condvar.c   Sun May 03 01:24:37 2020 +0000
@@ -1,4 +1,4 @@
-/*     $NetBSD: kern_condvar.c,v 1.48 2020/05/03 01:19:47 riastradh Exp $      */
+/*     $NetBSD: kern_condvar.c,v 1.49 2020/05/03 01:24:37 riastradh Exp $      */
 
 /*-
  * Copyright (c) 2006, 2007, 2008, 2019, 2020 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: kern_condvar.c,v 1.48 2020/05/03 01:19:47 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: kern_condvar.c,v 1.49 2020/05/03 01:24:37 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -244,6 +244,189 @@
        return error;
 }
 
+struct timedwaitclock {
+       struct timespec         *timeout;
+       clockid_t               clockid;
+       int                     flags;
+       const struct bintime    *epsilon;
+       struct timespec         starttime;
+};
+
+static int
+cv_timedwaitclock_begin(struct timedwaitclock *T, int *timo)
+{
+       struct timespec delta;
+       const struct timespec *deltap;
+       int error;
+
+       /* Sanity-check timeout -- may have come from userland.  */
+       if (T->timeout->tv_nsec < 0 || T->timeout->tv_nsec >= 1000000000L)
+               return EINVAL;
+
+       /*
+        * Compute the time delta.
+        */
+       if ((T->flags & TIMER_ABSTIME) == TIMER_ABSTIME) {
+               /* Check our watch.  */
+               error = clock_gettime1(T->clockid, &T->starttime);
+               if (error)
+                       return error;
+
+               /* If the deadline has passed, we're done.  */
+               if (timespeccmp(T->timeout, &T->starttime, <=))
+                       return ETIMEDOUT;
+
+               /* Count how much time is left.  */
+               timespecsub(T->timeout, &T->starttime, &delta);
+               deltap = &delta;
+       } else {
+               /* The user specified how much time is left.  */
+               deltap = T->timeout;
+
+               /* If there's none left, we've timed out.  */
+               if (deltap->tv_sec == 0 && deltap->tv_nsec == 0)
+                       return ETIMEDOUT;
+       }
+
+       /*
+        * Convert to ticks, but clamp to be >=1.
+        *
+        * XXX In the tickless future, use a high-resolution timer if
+        * timo would round to zero.
+        */
+       *timo = tstohz(deltap);
+       KASSERTMSG(*timo >= 0, "negative ticks: %d", *timo);
+       if (*timo == 0)
+               *timo = 1;
+
+       /* Success!  */
+       return 0;
+}
+
+static void
+cv_timedwaitclock_end(struct timedwaitclock *T)
+{
+       struct timespec endtime, delta;
+
+       /* If the timeout is absolute, nothing to do.  */
+       if ((T->flags & TIMER_ABSTIME) == TIMER_ABSTIME)
+               return;
+
+       /*
+        * Check our watch.  If anything goes wrong with it, make sure
+        * that the next time we immediately time out rather than fail
+        * to deduct the time elapsed.
+        */
+       if (clock_gettime1(T->clockid, &endtime)) {
+               T->timeout->tv_sec = 0;
+               T->timeout->tv_nsec = 0;
+               return;
+       }
+
+       /* Find how much time elapsed while we waited.  */
+       timespecsub(&endtime, &T->starttime, &delta);
+
+       /*
+        * Paranoia: If the clock went backwards, treat it as if no
+        * time elapsed at all rather than adding anything.
+        */
+       if (delta.tv_sec < 0 ||
+           (delta.tv_sec == 0 && delta.tv_nsec < 0)) {
+               delta.tv_sec = 0;
+               delta.tv_nsec = 0;
+       }
+
+       /*
+        * Set it to the time left, or zero, whichever is larger.  We
+        * do not fail with EWOULDBLOCK here because this may have been
+        * an explicit wakeup, so the caller needs to check before they
+        * give up or else cv_signal would be lost.
+        */
+       if (timespeccmp(T->timeout, &delta, <=)) {
+               T->timeout->tv_sec = 0;
+               T->timeout->tv_nsec = 0;
+       } else {
+               timespecsub(T->timeout, &delta, T->timeout);
+       }
+}
+
+/*
+ * cv_timedwaitclock:
+ *
+ *     Wait on a condition variable until awoken normally, or the
+ *     specified timeout expires according to the provided clock.
+ *     Returns zero if awoken normally or EWOULDBLOCK if the timeout
+ *     expired.  For relative timeouts ((flags & TIMER_ABSTIME) == 0),
+ *     updates timeout with the time left.
+ *
+ *     timeout == NULL specifies an infinite timeout.  epsilon is a
+ *     requested maximum error in timeout (excluding spurious
+ *     wakeups).
+ */
+int
+cv_timedwaitclock(kcondvar_t *cv, kmutex_t *mtx, struct timespec *timeout,
+    clockid_t clockid, int flags, const struct bintime *epsilon)
+{
+       struct timedwaitclock T = {
+               .timeout = timeout,
+               .clockid = clockid,
+               .flags = flags,
+               .epsilon = epsilon,
+       };
+       int timo;
+       int error;
+
+       if (timeout == NULL) {
+               cv_wait(cv, mtx);
+               return 0;
+       }
+
+       error = cv_timedwaitclock_begin(&T, &timo);
+       if (error)
+               return error;
+       error = cv_timedwait(cv, mtx, timo);
+       cv_timedwaitclock_end(&T);
+       return error;
+}
+
+/*
+ * cv_timedwaitclock_sig:
+ *
+ *     Wait on a condition variable until awoken normally, interrupted
+ *     by a signal, or the specified timeout expires according to the
+ *     provided clock.  Returns zero if awoken normally,
+ *     EINTR/ERESTART if interrupted by a signal, or EWOULDBLOCK if
+ *     the timeout expired.  For relative timeouts ((flags &
+ *     TIMER_ABSTIME) == 0), updates timeout with the time left.
+ *
+ *     timeout == NULL specifies an infinite timeout.  epsilon is a
+ *     requested maximum error in timeout (excluding spurious
+ *     wakeups).
+ */
+int
+cv_timedwaitclock_sig(kcondvar_t *cv, kmutex_t *mtx, struct timespec *timeout,
+    clockid_t clockid, int flags, const struct bintime *epsilon)
+{
+       struct timedwaitclock T = {
+               .timeout = timeout,
+               .clockid = clockid,
+               .flags = flags,
+               .epsilon = epsilon,
+       };
+       int timo;
+       int error;
+
+       if (timeout == NULL)
+               return cv_wait_sig(cv, mtx);
+
+       error = cv_timedwaitclock_begin(&T, &timo);
+       if (error)
+               return error;
+       error = cv_timedwait_sig(cv, mtx, timo);
+       cv_timedwaitclock_end(&T);
+       return error;
+}
+
 /*
  * Given a number of seconds, sec, and 2^64ths of a second, frac, we
  * want a number of ticks for a timeout:
diff -r bca62788a569 -r 7b14a56bb2ac sys/sys/condvar.h
--- a/sys/sys/condvar.h Sun May 03 01:20:37 2020 +0000
+++ b/sys/sys/condvar.h Sun May 03 01:24:37 2020 +0000
@@ -1,4 +1,4 @@
-/*     $NetBSD: condvar.h,v 1.15 2020/03/26 19:46:42 ad Exp $  */
+/*     $NetBSD: condvar.h,v 1.16 2020/05/03 01:24:37 riastradh Exp $   */
 
 /*-
  * Copyright (c) 2006, 2007, 2008, 2020 The NetBSD Foundation, Inc.
@@ -40,6 +40,7 @@
 
 struct bintime;
 struct kmutex;
+struct timespec;
 
 void   cv_init(kcondvar_t *, const char *);
 void   cv_destroy(kcondvar_t *);
@@ -48,6 +49,10 @@
 int    cv_wait_sig(kcondvar_t *, struct kmutex *);
 int    cv_timedwait(kcondvar_t *, struct kmutex *, int);
 int    cv_timedwait_sig(kcondvar_t *, struct kmutex *, int);
+int    cv_timedwaitclock(kcondvar_t *, struct kmutex *, struct timespec *,
+           clockid_t, int, const struct bintime *);
+int    cv_timedwaitclock_sig(kcondvar_t *, struct kmutex *, struct timespec *,
+           clockid_t, int, const struct bintime *);
 int    cv_timedwaitbt(kcondvar_t *, struct kmutex *, struct bintime *,
            const struct bintime *);
 int    cv_timedwaitbt_sig(kcondvar_t *, struct kmutex *, struct bintime *,



Home | Main Index | Thread Index | Old Index