关于C#:time()和gettimeofday()返回不同的秒数

time() and gettimeofday() return different seconds

在我测试过的两个系统上(一台32位Ubuntu 12.04服务器和一台64位Ubuntu 13.10 VM),自time()给出纪元以来的秒数可能不同于gettimeofday()的秒数。

具体来说,尽管我在调用gettimeofday()之后调用了time(),但是time()返回的值有时小于gettimeofday()返回的tv_sec值。

这显然是在时钟翻转到新的秒之后发生的。

这导致我的某些代码中的错误,它们期望time()和gettimeofday()的秒数可以互换。

演示此问题的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#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
"
, same);
  printf("Different: %i
"
, different);
  printf("Largest difference seen at %i
"
, max_usec);
}

请注意,我第二次调用time(),并且仅在其值小于gettimeofday()的情况下才抱怨。

样本输出:

1
2
3
Same:      33282836
Different: 177076
Largest difference seen at 5844

即,这两个值是相同的3300万次,它们是17.7万次,并且在新秒的5844微秒内始终是不同的。

这是一个已知的问题? 是什么原因造成的?


这两个调用都实现为内核syscall。这两个函数最终都读取一个struct timekeeper,都引用同一实例。但是他们在处理方面有所不同:

时间():

使用get_seconds()函数,这是此函数的快捷方式:

1
2
struct timekeeper *tk = &timekeeper;
return tk->xtime_sec;

它只是返回xktime_sec

gettimeofday()

另一方面,gettimeofday()使用do_gettimeofday()(通过getnstimeofday)读取两个字段xktime_secxktime_nsec(通过timekeeping_get_ns)。在这里,xktime_nsec可能比一秒钟保持更多的纳秒。通过调用执行以下操作的函数timespec_add_ns(),可以使用此潜在的额外时间来增加tv_sec字段:

1
2
a->tv_sec += __iter_div_u64_rem(a->tv_nsec + ns, NSEC_PER_SEC, &ns);
a->tv_nsec = ns;

因此,tv_sec可能会大于xktime_sec字段。这样就可以了:time()给您的内容和gettimeofday()给您的内容有些不同。

今天,我在fluxbox中与这个问题作斗争,直到出现更好的解决方案,我对此表示赞同:

1
2
uint64_t t_usec = gettimeofday_in_usecs(); // calcs usecs since epoch
time_t t = static_cast<time_t>(t_usec / 1000000L);


timegettimeofday都被实现为所谓的Linux vsyscalls。意味着您的代码将被重定向到内核拥有的页面,但是包含结果的用户空间映射页面仅定期更新。

在Ubuntu中(我没有在RedHat Linux中观察到这种行为),gettimeofday的值在time的值之前被更新,因此有可能获得不一致的值:

kernel updates gettimeofday

You query gettimeofday

You query time

kernel updates time

交换您的电话会得到一致的结果:

1
2
3
t = time(NULL);
gettimeofday(&tv, NULL);
if (t > tv.tv_sec) { ...


此行为是由于在Linux内核中实现了计时功能。

Linux维护一个变量,该变量跟踪当前的挂钟时间;保持到纳秒精度,并定期更新。 (在最新的内核版本中为tk_core.timekeeper.{xtime_secs, tkr_mono.xtime_nsec}。)

time()调用get_seconds()只是返回此变量的秒部分-因此,取决于更新壁钟时间的时间,可能会返回一个稍微过期的值。

gettimeofday()不仅读取壁钟变量的最新值,而且(通过timekeeping_get_ns())从硬件时钟(通常是x86系统中的TSC,尽管可以在运行时对其进行配置)进行新的读取,并应用更正。

由于进行了此校正计算,gettimeofday()返回的结果可能会翻转到下一秒,因此返回的tv_sec值将比time()的结果高。