Time() и gettimeofday() возвращают разные секунды
В двух системах, которые я тестировал (32-разрядный сервер Ubuntu 12.04 и 64-разрядная Ubuntu 13.10 VM), секунды с момента, заданного time() может отличаться от gettimeofday().
В частности, хотя я вызываю time()
после вызова gettimeofday()
, значение, возвращаемое time()
, иногда меньше значения tv_sec
, возвращаемого gettimeofday()
.
Это, по-видимому, происходит сразу после того, как часы переходят к новой секунде.
Это вызвало ошибки в некоторых из моего кода, в которых ожидалось, что время() и 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
I.e., эти два значения были одинаковыми 33 миллиона раз, они были разными 177k раз, и они всегда отличались в течение 5844 микросекунд новой секунды.
Это известная проблема? Что вызывает это?
Ответы
Ответ 1
Оба вызова реализованы как системные вызовы ядра. Обе функции заканчивают чтение struct timekeeper
, оба относятся к одному и тому же экземпляру. Но они отличаются тем, что с ним делают:
использует функцию get_seconds()
, которая является ярлыком для этого:
struct timekeeper *tk = &timekeeper;
return tk->xtime_sec;
он просто возвращает xktime_sec
.
gettimeofday()
использует do_gettimeofday()
(через getnstimeofday
), который читает оба поля xktime_sec
, а также xktime_nsec
(через timekeeping_get_ns
). Здесь может случиться, что xktime_nsec
занимает больше наносекунд, чем секунда. Это дополнительное дополнительное время используется для увеличения поля tv_sec
, вызывая функцию timespec_add_ns()
, которая делает это:
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);
Ответ 2
Оба time
и gettimeofday
реализованы как так называемые Linux vsyscalls. Означает, что ваш код будет перенаправлен на принадлежащее ядру, но сопоставленные страницы в пользовательском пространстве, содержащие результаты, которые периодически обновляются.
В Ubuntu (я не наблюдал этого поведения в RedHat Linux) значение для gettimeofday
обновляется до значения для time
, поэтому можно получить несогласованные значения:
Обновление ядра gettimeofday
Вы запрашиваете gettimeofday
Вы запрашиваете time
Обновления ядра time
Обмен вашими вызовами дает согласованные результаты:
t = time(NULL);
gettimeofday(&tv, NULL);
if (t > tv.tv_sec) { ...
Ответ 3
Такое поведение связано с внедрением хронометража в ядре Linux.
Linux поддерживает переменную, которая отслеживает текущее время настенных часов; это поддерживается до наносекундной точности и периодически обновляется. (В последних версиях ядра это tk_core.timekeeper.{xtime_secs, tkr_mono.xtime_nsec}
.)
time()
вызывает get_seconds(), который просто возвращает секундную часть этой переменной - так, в зависимости от того, как давно настенные часы время было обновлено, может вернуть немного устаревшее значение.
gettimeofday()
не только считывает последнее значение переменных настенных часов, но также (через timekeeping_get_ns()) делает новое чтение от аппаратных часов (обычно TSC в системах x86, хотя это настраивается во время выполнения) и применяет исправление.
Благодаря этому вычислению коррекции можно вернуть результат, возвращаемый gettimeofday()
в следующую секунду, и, следовательно, вернуть значение tv_sec
выше результата time()
.