time() and gettimeofday() return different seconds

2020-06-07 06:52发布

问题:

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?

回答1:

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);


回答2:

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) { ...


回答3:

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().