diff --git a/sys/kern/kern_clock.c b/sys/kern/kern_clock.c --- a/sys/kern/kern_clock.c +++ b/sys/kern/kern_clock.c @@ -556,60 +556,43 @@ } /* - * Compute number of ticks in the specified amount of time. + * Compute number of ticks representing the specified amount of time. + * If the specified time is negative, a value of 1 is returned. This + * function returns a value from 1 up to and including INT_MAX. */ int tvtohz(struct timeval *tv) { - unsigned long ticks; - long sec, usec; + int retval; /* - * If the number of usecs in the whole seconds part of the time - * difference fits in a long, then the total number of usecs will - * fit in an unsigned long. Compute the total and convert it to - * ticks, rounding up and adding 1 to allow for the current tick - * to expire. Rounding also depends on unsigned long arithmetic - * to avoid overflow. - * - * Otherwise, if the number of ticks in the whole seconds part of - * the time difference fits in a long, then convert the parts to - * ticks separately and add, using similar rounding methods and - * overflow avoidance. This method would work in the previous - * case but it is slightly slower and assumes that hz is integral. - * - * Otherwise, round the time difference down to the maximum - * representable value. - * - * If ints have 32 bits, then the maximum value for any timeout in - * 10ms ticks is 248 days. + * The values passed here may come from user-space and these + * checks ensure "tv_usec" is within its allowed range: */ - sec = tv->tv_sec; - usec = tv->tv_usec; - if (usec < 0) { - sec--; - usec += 1000000; - } - if (sec < 0) { -#ifdef DIAGNOSTIC - if (usec > 0) { - sec++; - usec -= 1000000; - } - printf("tvotohz: negative time difference %ld sec %ld usec\n", - sec, usec); -#endif - ticks = 1; - } else if (sec <= LONG_MAX / 1000000) - ticks = howmany(sec * 1000000 + (unsigned long)usec, tick) + 1; - else if (sec <= LONG_MAX / hz) - ticks = sec * hz - + howmany((unsigned long)usec, tick) + 1; - else - ticks = LONG_MAX; - if (ticks > INT_MAX) - ticks = INT_MAX; - return ((int)ticks); + + /* check for tv_usec underflow */ + if (__predict_false(tv->tv_usec < 0)) + tv->tv_usec = 0; + /* check for tv_usec overflow */ + else if (__predict_false(tv->tv_usec >= 1000000)) + tv->tv_usec = 999999; + + /* check for tv_sec underflow */ + if (__predict_false(tv->tv_sec < 0)) + return (1); + /* check for tv_sec overflow (including room for the tv_usec part) */ + if (__predict_false(tv->tv_sec >= tick_seconds_max)) + return (INT_MAX); + + /* use "int" cast to avoid platform differences */ + retval = (int)tv->tv_sec * hz + (int)(tv->tv_usec >> 6) * hz / (1000000 >> 6); + + /* assert output range, just in case */ + KASSERT(retval >= 0 && retval < INT_MAX, + ("tvtohz(): Assumptions are not met: %d %ld,%ld\n", retval, tv->tv_sec, tv->tv_usec)); + + /* add one additional tick */ + return (retval + 1); } /* diff --git a/sys/kern/subr_param.c b/sys/kern/subr_param.c --- a/sys/kern/subr_param.c +++ b/sys/kern/subr_param.c @@ -84,6 +84,7 @@ int hz; /* system clock's frequency */ int tick; /* usec per tick (1000000 / hz) */ +int tick_seconds_max; /* max hz * seconds an integer can hold */ struct bintime tick_bt; /* bintime per tick (1s / hz) */ sbintime_t tick_sbt; int maxusers; /* base tunable */ @@ -111,6 +112,10 @@ SYSCTL_INT(_kern, OID_AUTO, hz, CTLFLAG_RDTUN | CTLFLAG_NOFETCH, &hz, 0, "Number of clock ticks per second"); +SYSCTL_INT(_kern, OID_AUTO, hz_max, CTLFLAG_RD, SYSCTL_NULL_INT_PTR, HZ_MAXIMUM, + "Maximum hz value supported"); +SYSCTL_INT(_kern, OID_AUTO, hz_min, CTLFLAG_RD, SYSCTL_NULL_INT_PTR, HZ_MINIMUM, + "Minimum hz value supported"); SYSCTL_INT(_kern, OID_AUTO, nbuf, CTLFLAG_RDTUN | CTLFLAG_NOFETCH, &nbuf, 0, "Number of buffers in the buffer cache"); SYSCTL_INT(_kern, OID_AUTO, nswbuf, CTLFLAG_RDTUN | CTLFLAG_NOFETCH, &nswbuf, 0, @@ -173,9 +178,17 @@ TUNABLE_INT_FETCH("kern.hz", &hz); if (hz == -1) hz = vm_guest > VM_GUEST_NO ? HZ_VM : HZ; + + /* range check the "hz" value */ + if (__predict_false(hz < HZ_MINIMUM)) + hz = HZ_MINIMUM; + else if (__predict_false(hz > HZ_MAXIMUM)) + hz = HZ_MAXIMUM; + tick = 1000000 / hz; tick_sbt = SBT_1S / hz; tick_bt = sbttobt(tick_sbt); + tick_seconds_max = INT_MAX / hz; /* * Arrange for ticks to wrap 10 minutes after boot to help catch diff --git a/sys/sys/time.h b/sys/sys/time.h --- a/sys/sys/time.h +++ b/sys/sys/time.h @@ -505,6 +505,7 @@ extern volatile time_t time_uptime; extern struct bintime tc_tick_bt; extern sbintime_t tc_tick_sbt; +extern int tick_seconds_max; extern struct bintime tick_bt; extern sbintime_t tick_sbt; extern int tc_precexp; @@ -583,6 +584,13 @@ void timevalsub(struct timeval *t1, const struct timeval *t2); int tvtohz(struct timeval *tv); +/* + * The following HZ limits allow the tvtohz() function + * to only use integer computations. + */ +#define HZ_MAXIMUM (INT_MAX / (1000000 >> 6)) /* 137kHz */ +#define HZ_MINIMUM 10 /* hz */ + #define TC_DEFAULTPERC 5 #define BT2FREQ(bt) \