私がテストした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マイクロ秒以内に常に異なっていました。
これは既知の問題ですか?これは何が原因ですか?
両方の呼び出しは、カーネルシステムコールとして実装されます。どちらの関数も struct timekeeper
を読み取ることになり、どちらもまったく同じインスタンスを参照します。しかし、彼らはそれをどうするかで異なります:
get_seconds()
関数を使用します。これは、これへのショートカットです。
struct timekeeper *tk = &timekeeper;
return tk->xtime_sec;
xktime_sec
を返すだけです。
gettimeofday()
:一方、gettimeofday()
はdo_gettimeofday()
(getnstimeofday
経由)を使用し、フィールドxktime_sec
とxktime_nsec
( timekeeping_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_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);
time
とgettimeofday
はどちらも、いわゆるLinuxvsyscallとして実装されています。コードがカーネル所有にリダイレクトされるが、定期的にのみ更新される結果を含むユーザースペースマップページを意味します。
Ubuntu(RedHat Linuxではこの動作は観察されていません)では、gettimeofday
の値がtime
の値の前に更新されるため、一貫性のない値が取得される可能性があります。
カーネルの更新
gettimeofday
gettimeofday
をクエリします
time
をクエリしますカーネルの更新
time
通話を入れ替えると、一貫した結果が得られます。
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() を介して)ハードウェアクロックから新しい読み取りを行います(通常は [ 〜#〜] tsc [〜#〜] x86システムでは、これは実行時に構成可能ですが)、修正を適用します。
その補正計算のおかげで、gettimeofday()
によって返された結果が次の秒にロールオーバーする可能性があるため、time()
の結果よりも高い_tv_sec
_値を返すことができます。