web-dev-qa-db-ja.com

time()とgettimeofday()は異なる秒を返します

私がテストした2つのシステム(32ビットのUbuntu12.04サーバーと64ビットのUbuntu13.10 VM)では、 time() で指定されたエポックからの秒数が gettimeofday() の。

具体的には、time()afterを呼び出してgettimeofday()を呼び出しても、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

つまり、2つの値は同じ3,300万回であり、177k回異なっており、新しい秒から5844マイクロ秒以内に常に異なっていました。

これは既知の問題ですか?これは何が原因ですか?

21
Josh Kelley

両方の呼び出しは、カーネルシステムコールとして実装されます。どちらの関数も struct timekeeper を読み取ることになり、どちらもまったく同じインスタンスを参照します。しかし、彼らはそれをどうするかで異なります:

time()

get_seconds() 関数を使用します。これは、これへのショートカットです。

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

xktime_secを返すだけです。

gettimeofday()

一方、gettimeofday()do_gettimeofday()getnstimeofday経由)を使用し、フィールドxktime_secxktime_nsectimekeeping_get_ns 経由)の両方を読み取ります。ここで、xktime_nsecが1秒よりも多くのナノ秒を保持する場合があります。この潜在的な余分な時間は、これを行う関数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_secxktime_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);
22
akira

timegettimeofdayはどちらも、いわゆるLinuxvsyscallとして実装されています。コードがカーネル所有にリダイレクトされるが、定期的にのみ更新される結果を含むユーザースペースマップページを意味します。

Ubuntu(RedHat Linuxではこの動作は観察されていません)では、gettimeofdayの値がtimeの値の前に更新されるため、一貫性のない値が取得される可能性があります。

カーネルの更新gettimeofday

gettimeofdayをクエリします

timeをクエリします

カーネルの更新time

通話を入れ替えると、一貫した結果が得られます。

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

この動作は、Linuxカーネルにタイムキーピングが実装されているためです。

Linuxは、現在の実時間を追跡する変数を維持しています。これはナノ秒の精度で維持され、定期的に更新されます。 (最近のカーネルバージョンでは_tk_core.timekeeper.{xtime_secs, tkr_mono.xtime_nsec}_です。)

time()get_seconds() を呼び出します。これは、この変数の秒の部分を返すだけです。したがって、実時間が更新された時間によっては、わずかにずれた値が返される場合があります。日付値。

gettimeofday()は、壁時計変数の最新の値を読み取るだけでなく、( timekeeping_get_ns() を介して)ハードウェアクロックから新しい読み取りを行います(通常は [ 〜#〜] tsc [〜#〜] x86システムでは、これは実行時に構成可能ですが)、修正を適用します。

その補正計算のおかげで、gettimeofday()によって返された結果が次の秒にロールオーバーする可能性があるため、time()の結果よりも高い_tv_sec_値を返すことができます。

3
richvdh