summaryrefslogtreecommitdiffstats
path: root/lib/qtime.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/qtime.c')
-rw-r--r--lib/qtime.c205
1 files changed, 205 insertions, 0 deletions
diff --git a/lib/qtime.c b/lib/qtime.c
new file mode 100644
index 00000000..42b903da
--- /dev/null
+++ b/lib/qtime.c
@@ -0,0 +1,205 @@
+/* Quagga realtime and monotonic clock handling -- functions
+ * Copyright (C) 2009 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <sys/times.h>
+#include <errno.h>
+
+#include "zassert.h"
+#include "qtime.h"
+
+/*==============================================================================
+ * This is a collection of functions and (in qtime.h) macros and inline
+ * functions which support system time and a monotonic clock.
+ *
+ * TODO: introduce mutex for crafted monotonic time, and initialisation
+ * routine for that: which can preset the various variables... but
+ * unless is guaranteed to be called, must leave the on-the-fly
+ * initialisation... could also start a watchdog at that point.
+ */
+
+/*==============================================================================
+ * Replacement for CLOCK_MONOTONIC.
+ *
+ * With thanks to Joakim Tjernlund for reminding everyone of the return value
+ * from times() !
+ *
+ * times() is defined to return a value which is the time since some fixed time
+ * before the application started (or when the application started). This time
+ * is measured in units of sysconf(_SC_CLK_TCK) ticks per second.
+ *
+ * The only tricky bit is that the value returned (of type clock_t) is a
+ * signed integer, which can overflow. It is not defined exactly how it
+ * does this... This code assumes that the system will wrap around in some
+ * obvious way. The base of the time for this clock may be when the *system*
+ * started... so when it overflows may depend on how long the system has been
+ * up... which suggests that some sensible wrap around is likely (?).
+ *
+ * The qtime_t value is in nano-seconds.
+ *
+ * The result from times() is in units of sysconf(_SC_CLK_TCK) ticks per second.
+ *
+ * If clock_t is a signed 32-bit integer, which is kept +ve, then the clock
+ * overflows/wraps round in 2^31 ticks which is:
+ *
+ * at 100 ticks/sec: > 248 days
+ * at 1,000 ticks/sec: > 24 days
+ * at 10,000 ticks/sec: > 59 hours
+ *
+ * For safety, this asserts that sysconf(_SC_CLK_TCK) <= 1,000,000 for
+ * sizeof(clock_t) > 4, but <= 1,000 for sizeof(clock_t) == 4.
+ *
+ * (It appears that 60, 100, 250 and 1,000 ticks/sec. are popular options.)
+ *
+ * If sizeof(clock_t) > 4, it is assumed large enough never to wrap around.
+ *
+ * When clock_t is a 32-bit integer must be at least ready for wrap around.
+ * There are two cases:
+ *
+ * * +ve wrap around. new < old value, and new >= 0
+ *
+ * step = (INT32_MAX - old + 1) + new
+ *
+ * * -ve wrap around. new < old value, and new < 0 (and old > 0)
+ *
+ * step = (INT32_MAX - old + 1) - (INT32_MIN - new)
+ *
+ * In any event, a step > 24 hours is taken to means that something has gone
+ * very, very badly wrong.
+ *
+ * NB: it is assumed that qt_craft_monotonic will be called often enough to
+ * ensure that the check on the step size will not be triggered !
+ *
+ * NB: it is assumed that times() does not simply stick if it overflows.
+ *
+ * TODO: Add a watchdog to monitor the behaviour of this clock ?
+ */
+
+CONFIRM((sizeof(clock_t) >= 4) && (sizeof(clock_t) <= 8)) ;
+
+#ifdef GNU_LINUX
+#define TIMES_TAKES_NULL 1
+#else
+#undef TIMES_TAKES_NULL
+#endif
+
+static uint64_t monotonic = 0 ; /* monotonic clock in _SC_CLK_TCK's */
+static int64_t last_times_sample = 0 ; /* last value returned by times() */
+
+static uint64_t step_limit = 0 ; /* for sanity check */
+
+static int64_t times_clk_tcks = 0 ; /* sysconf(_SC_CLK_TCK) */
+static qtime_t times_scale_q = 0 ; /* 10**9 / times_clk_tcks */
+static qtime_t times_scale_r = 0 ; /* 10**9 % times_clk_tcks */
+
+qtime_mono_t
+qt_craft_monotonic(void) {
+ struct tms dummy ;
+ int64_t this_times_sample ;
+ uint64_t step ;
+
+ /* Set up times_scale_q & times_scale_q if not yet done. */
+ if (times_clk_tcks == 0) /* Is zero until it's initialized */
+ {
+ lldiv_t qr ;
+ confirm(sizeof(qtime_t) <= sizeof(long long int)) ;
+
+ times_clk_tcks = sysconf(_SC_CLK_TCK) ;
+ passert((times_clk_tcks > 0) &&
+ (times_clk_tcks <= (sizeof(clock_t) > 4) ? 1000000
+ : 1000)) ;
+
+ qr = lldiv(QTIME_SECOND, times_clk_tcks) ;
+ times_scale_q = qr.quot ;
+ times_scale_r = qr.rem ;
+
+ step_limit = (uint64_t)24 * 60 * 60 * times_clk_tcks ;
+ } ;
+
+ /* No errors are defined for times(), but a return of -1 is defined */
+ /* to indicate an error condition, with errno saying what it is ! */
+ /* */
+ /* The following deals carefully with this -- cannot afford for the */
+ /* clock either to jump or to get stuck ! */
+
+#ifdef TIMES_TAKES_NULL
+ this_times_sample = times(NULL) ; /* assume this saves effort ! */
+#else
+ this_times_sample = times(&dummy) ;
+#endif
+
+ if (this_times_sample == -1) /* deal with theoretical error */
+ {
+ errno = 0 ;
+ this_times_sample = times(&dummy) ;
+ if (errno != 0)
+ zabort_errno("times() failed") ;
+ } ;
+
+ /* Calculate the step and verify sensible. */
+ /* */
+ /* Watch out for huge jumps and/or time going backwards. */
+ /* For 32-bit clock_t, look out for wrap-around. */
+
+ if ((sizeof(clock_t) > 4) || (this_times_sample > last_times_sample))
+ /* time going backwards will appear as HUGE step forwards. */
+ step = (uint64_t)(this_times_sample - last_times_sample) ;
+ else
+ {
+ if (this_times_sample > 0)
+ /* both samples +ve => +ve wrap around. */
+ step = (uint64_t)( ((int64_t)INT32_MAX - last_times_sample + 1)
+ + this_times_sample ) ;
+ else
+ /* this sample -ve and last sample +ve => -ve wrap round */
+ /* this sample -ve and last sample -ve => time gone backwards */
+ /* (which appears as a HUGE step forwards). */
+ step = (uint64_t)( ((int64_t)INT32_MAX - last_times_sample + 1)
+ - ((int64_t)INT32_MIN - this_times_sample) ) ;
+ } ;
+
+ /* TODO: better error messaging for large clock jumps. */
+ if (step > step_limit)
+ zabort("Sudden large monotonic clock jump") ;
+
+ /* Advance the monotonic clock in sysconf(_SC_CLK_TCK) units. */
+ monotonic += step ;
+
+ /* Remember what we got, for next time. */
+ last_times_sample = this_times_sample ;
+
+ /* Scale to qtime_t units. */
+ if (times_scale_r == 0)
+ return monotonic * times_scale_q ;
+ else
+ return (monotonic * times_scale_q) +
+ ((monotonic * times_scale_r) / times_clk_tcks) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get crafted monotonic time -- in seconds
+ */
+extern time_t
+qt_craft_mono_secs(void)
+{
+ qt_craft_monotonic() ; /* update the monotonic counter */
+
+ return monotonic / times_clk_tcks ;
+} ;