On the two systems I've tested (a 32-bit Ubuntu 12.04 server and a 64-bit Ubuntu 13.10 VM), the seconds since the epoch given by time() may differ from gettimeofday()'s.
Specifically, though I call time()
after calling gettimeofday()
, the value returned by time()
is sometimes less than the tv_sec
value returned by gettimeofday()
.
This apparently occurs just after the clock rolls over to a new second.
This caused bugs in some of my code that expected time()'s and gettimeofday()'s seconds to be interchangeable.
Sample code demonstrating this problem:
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
int main()
{
time_t start = time(NULL);
int same = 0;
int different = 0;
int max_usec = 0;
while (1) {
time_t t;
struct timeval tv;
gettimeofday(&tv, NULL);
t = time(NULL);
if (t < tv.tv_sec) {
different++;
if (tv.tv_usec > max_usec) {
max_usec = tv.tv_usec;
}
} else {
same++;
}
if (t > start + 5) {
break;
}
}
printf("Same: %i\n", same);
printf("Different: %i\n", different);
printf("Largest difference seen at %i\n", max_usec);
}
Note that I'm calling time() second and only complaining if its value is less than gettimeofday()'s.
Sample output:
Same: 33282836
Different: 177076
Largest difference seen at 5844
I.e., the two values were the same 33 million times, they were different 177k times, and they were always different within 5844 microseconds of a new second.
Is this a known issue? What causes this?
Both calls are implemented as kernel syscalls. Both functions end up reading a struct timekeeper
, both refer to the very same instance. But they differ in what they do with it:
time():
uses the get_seconds()
function, which is a shortcut to this:
struct timekeeper *tk = &timekeeper;
return tk->xtime_sec;
it just returns xktime_sec
.
gettimeofday()
:
gettimeofday()
on the other hand uses do_gettimeofday()
(via getnstimeofday
) which reads both fields xktime_sec
as well as xktime_nsec
(via timekeeping_get_ns
). Here it might happen that xktime_nsec
holds more nanoseconds than a second. This potential extra time is used to increase the tv_sec
field by calling the function timespec_add_ns()
which does this:
a->tv_sec += __iter_div_u64_rem(a->tv_nsec + ns, NSEC_PER_SEC, &ns);
a->tv_nsec = ns;
So, tv_sec
might get bigger than the xktime_sec
field was. And there you have it: a little difference in what time()
gives you and what gettimeofday()
gives you.
I fought against this issue in fluxbox today and until a better solution occurs I live with this:
uint64_t t_usec = gettimeofday_in_usecs(); // calcs usecs since epoch
time_t t = static_cast<time_t>(t_usec / 1000000L);
Both time
and gettimeofday
are implemented as so called Linux vsyscalls. Means that your code will be redirected to kernel owned, but userspace mapped pages containing the results which are only periodically updated.
In Ubuntu (I have not observed this behaviour in RedHat Linux) the value for gettimeofday
is updated before the value for time
thus it is possible to get inconsistent values:
kernel updates gettimeofday
You query gettimeofday
You query time
kernel updates time
Swapping your calls around gives consistent results:
t = time(NULL);
gettimeofday(&tv, NULL);
if (t > tv.tv_sec) { ...
This behaviour is due to the implementation of timekeeping in the Linux kernel.
Linux maintains a variable which tracks the current wall-clock time; this is maintained to nanosecond precision and is updated periodically. (It is tk_core.timekeeper.{xtime_secs, tkr_mono.xtime_nsec}
in recent kernel versions.)
time()
calls get_seconds() which simply returns the seconds part of this variable - so, depending on how long ago the wall-clock time was updated, may return a slightly out-of-date value.
gettimeofday()
not only reads the latest value of the wall-clock variables, but also (via timekeeping_get_ns()) makes a new reading from the hardware clock (typically the TSC in x86 systems, though this is configurable at runtime) and applies a correction.
Thanks to that correction calculation, it is possible for the result returned by gettimeofday()
to roll over into the next second, and hence return a tv_sec
value higher than the result of time()
.