time()和gettimeofday()返回不同的秒数

21
在我测试过的两个系统(32位Ubuntu 12.04服务器和64位Ubuntu 13.10虚拟机)中,time()给出的纪元秒数可能与gettimeofday()的不同。
具体来说,尽管我在调用 gettimeofday()之后调用了 time(),但 time()返回的值有时会小于 gettimeofday()返回的 tv_sec 值。
这似乎发生在时钟滚动到新的一秒之后。
这导致我的一些代码出现错误,因为它们期望 time() gettimeofday()的秒数是可互换的。
演示此问题的示例代码:
#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);
}

请注意,我调用time()函数并且只有当它的值小于gettimeofday()函数的值时才会进行抱怨。
示例输出:
Same:      33282836
Different: 177076
Largest difference seen at 5844

即两个值相同的情况出现了3300万次,不同的情况出现了177,000次,并且它们总是在新秒的5844微秒内不同。

这是一个已知的问题吗?是什么导致了这种情况?


我已经在你的问题中添加了一个澄清,以使其更清晰,即你得到的结果似乎显示时间倒流。 - Keith Thompson
我已经在RedHat Linux实现上尝试了您的代码,并没有发现任何不同的值。看起来我的vsyscall实现更好。 - Sergey L.
@Deduplicator - 我已经关闭了NTP客户端,但仍然看到这种行为。 - Josh Kelley
我想知道是否因为您正在循环中运行它,才会导致内核内部变慢。 - Engineer2021
我在我的Linux Mint x86_64系统上看到了类似的行为。一个打印更多信息的程序版本显示,似乎表明time()值在gettimeofday()值更新后的7000到7003微秒之间被相当一致地更新。在我的x86_64 Debian系统上(运行较旧的内核),它没有显示任何不一致性。 - Keith Thompson
显示剩余3条评论
3个回答

23

两个调用都是内核系统调用实现的。两个函数最终都会读取一个struct timekeeper,它们引用的是同一个实例。但它们在处理该结构体时有所不同:

time():

使用get_seconds()函数,而该函数是这个快捷方式:

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字段,其方法如下:

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 中遇到了这个问题,直到有更好的解决方案出现,我只能接受这个结果:

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

1
这是一个有帮助的答案,但我觉得它并没有真正解释为什么timekeeping_get_ns()可能会返回超过十亿纳秒。 - richvdh
@richvdh https://bugs.php.net/bug.php?id=69044,感谢[NikiC](https://chat.stackoverflow.com/transcript/message/46522350#46522350)。 - Code4R7

9

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

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

内核更新gettimeofday

您查询gettimeofday

您查询time

内核更新time

交换您的调用可以得到一致的结果:

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

这肯定可以解释我所看到的,但你有任何官方文档或内核源代码链接可以证实吗?我曾试图在内核源代码中查找,但没有任何好运。 - Josh Kelley
2
两个函数也使用相同的时间结构作为基础,该结构同时更新(因为它是同一实例)用于两个函数。区别在于累积的纳秒数以及它们是否添加到返回的秒数。 - akira

7

这种行为是由于Linux内核中时间管理的实现所致。

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

time()调用get_seconds()函数,它仅返回该变量的秒部分-因此,根据挂钟时间更新的时间长度,可能会返回稍微过时的值。

gettimeofday()不仅读取挂钟变量的最新值,还通过timekeeping_get_ns()从硬件时钟(通常是x86系统中的TSC,但这可以在运行时配置)中进行新的读数,并应用更正。

由于进行了这种更正计算,使得gettimeofday()返回的结果有可能翻转到下一秒,从而返回比time()的结果更高的tv_sec值。


谢谢!我引用了您的帖子使用printf内置函数打印毫秒或纳秒的当前时间 - F. Hauri - Give Up GitHub

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接