Я упомянул свой пост "Один на миллион - это в следующий вторник" в разговоре с женой на следующий день, и она спросила меня: "Так что, ты рассказал там про часы в персоналках"?
И меня озарило, что я этого не сказал. Чёрт! Поэтому: вот этот рассказ. На самом деле, это довольно любопытно.
Время на персоналках хранится подсчётом количества прерываний часов, которые произошли. Каждый PC содержит кристалл, который приводит в движение чип часов, который генерирует прерывание процессора примерно каждые 10 миллисекунд. Поэтому NT увеличивает системное время на 10 миллисекунд каждый раз, когда она получает аппаратное прерывание от часов.
Но проблема в том, что кристалл, используемый в системах, имеет частоту отказа примерно 100ppm – другими словами, 100 раз за каждый миллион тиков часов чип не генерирует прерывание. Для большинства приложений это не проблема – вместо переключения контекста каждые 10 миллисекунд, очень редко система будет переключать контекст только через 20 миллисекунд.
Но это катастрофа для учёта времени. Если у вас есть таймер на 10 миллисекунд, то у вас получается 8'640'000 тиков часов в день. Если за миллион тиков мы пропускаем 100, то это означает, что система пропускает 864 тиков в день, что равно более 8-ми секунд в день.
Вы можете заметить, что на практике величина отклонения времени намного меньше, хотя всё ещё значительна.
Так как NT это исправляет? Ну, во времена NT 3.1 раз в час NT спрашивала чип часов реального времени (RTC - это железо, которое хранит вашу дату и время даже когда компьютер выключен). Если системное время отличалось от времени на этом чипе, то NT просто сбрасывала системное время, чтобы оно соответствовало бы этому RTC. Что означает, что время могло значительно прыгать (назад или вперёд) – поэтому следующее выражение могло провалиться:
GetFileTime(Time1); GetFileTime(Time2); Assert(CompareFileTime(Time1, Time2) < 0);Очевидно, что это было неприемлемым решением, поэтому нужно было что-то сделать, чтобы это исправить. Исправление (в NT 3.5) заключалось в изменении способа учёта времени в системе. В старом варианте каждое прерывание часов увеличивало время на 10 миллисекунд. После изменения, когда система вычисляла время от RTC, вместо применения нового времени немедленно, она вычисляла поправку к этим 10 миллисекундам. Если часы отставали, то каждый тик мог учитываться как 11 или 12-ти миллисекундный интервал. Если часы спешили, то каждый тик мог быть 8 или 9-ю миллисекундами. Иными словами, вместо применения разового исправления времени оно размазывалось на промежуток, чтобы избежать скачков времени.
Это вообще-то очень клёво (ok, я думаю, что это необычайно умно), но у нас снова есть проблемы (почему у нас всегда есть проблемы?). Что если вы используете текущие дату и время вместе с каким-нибудь высокопроизводительным таймером (типа QueryPerformanceCounter)? Тогда отклонения часов приведут к неверным измерениям, которые будут отличаться от реальных. Мы даже встретились с этой проблемой в проекте SCP – наши тесты часов показывали, что часы на чипе SCP отклонялись, но мы никак не могли понять, почему же они плывут – как оказалось, часы SCP шли верно: это часы PC имели смещение.
Чтобы позволить людям учитывать это смещение в своих измерениях времени, мы добавили новый API: GetSystemTimeAdjustment. Функция GetSystemTimeAdjustment позволяет вам определить частоту прерывания часов (это параметр lpTimeIncrement), и добавку, которая применяется к каждому тику (это параметр lpTimeAdjustment).
Я вот об этом не знал. И ни разу не видел вызова GetSystemTimeAdjustment в коде измерения чего-либо по времени.
ОтветитьУдалитьсразу бросилась в глаза ошибка с подсчетом пропущенных тиков. полез в оригинал статьи и в комментариях нашел описание этой ошибки (самому было лень набирать :))) )
ОтветитьУдалитьIlya Haykinson
Wait a second. 100 per million out of 8.64 million is about 864 ticks missed, which is just 8.64 seconds per day.
Угу, исправил, спасибо.
ОтветитьУдалитьВстречался с этим, когда настраивал NTP в Linux. А то, что GetSystemTimeAdjustment никто не пользуется - не удивительно. Оно актуально только для зверских около-realtime систем. Т.е. не для 99.(9)% прикладных программ.
ОтветитьУдалить