【问题标题】:Clock drift on WindowsWindows 上的时钟漂移
【发布时间】:2010-09-11 06:02:14
【问题描述】:

我开发了一个跟踪业务事件的 Windows 服务。它使用 Windows 时钟为事件添加时间戳。然而,底层时钟可能会发生相当大的漂移(例如每分钟损失几秒钟),尤其是在 CPU 努力工作时。我们的服务器使用 Windows 时间服务与域控制器保持同步,域控制器在后台使用 NTP,但同步频率由域策略控制,在任何情况下,即使每分钟同步一次,仍然会出现明显的偏差。除了使用硬件时钟之外,我们还可以使用什么技术来保持时钟更稳定?

【问题讨论】:

  • 这不是一个真正与编程相关的问题,所以我不确定它是否属于 StackOverflow。
  • 这似乎是一个与编程相关的问题
  • 每分钟几秒钟的时钟漂移是一个坏时钟,这与机器工作的努力程度无关。您需要新硬件。
  • 你不应该每分钟都有几秒钟的时间。时钟有问题。或者,您有两个软件服务在玩机器上的时间并从不同的来源更改它?
  • 您可能会遇到Microsoft Minute

标签: windows clock


【解决方案1】:

时钟滴答应该是可预测的,但在大多数 PC 硬件上 - 因为它们不是为实时系统设计的 - 其他 I/O 设备中断优先于时钟滴答中断,还有一些驱动程序在中断服务例程中进行大量处理,而不是将其推迟到deferred procedure call (DPC),这意味着系统可能无法在(有时)发出信号后很久才能为时钟滴答中断提供服务。

其他因素包括总线主控 I/O 控制器会从 CPU 窃取许多内存总线周期,导致它在很长一段时间内缺乏内存总线带宽。

正如其他人所说,时钟生成硬件也可能随着组件值随温度的变化而改变其频率。

Windows 确实允许调整每次中断时添加到实时时钟的滴答数:请参阅 SetSystemTimeAdjustment。但是,这仅在您有可预测的时钟偏差时才有效。如果时钟只是略微偏离,SNTP 客户端(“Windows 时间”服务)将调整此偏差,使时钟滴答声稍微快一点或慢一点,以趋向正确的时间。

【讨论】:

    【解决方案2】:

    我不知道这是否适用,但是...

    Windows 存在一个问题,如果您用timeBeginPeriod() 更改定时器分辨率很多,时钟会漂移。

    实际上,Java 的 Thread wait()(和 os::sleep())函数的 Windows 实现中存在导致此行为的错误。它总是在等待之前将计时器分辨率设置为 1 毫秒,以便准确(不管睡眠时间长短),并在完成后立即恢复它,除非任何其他线程仍在睡眠。然后,此设置/重置将混淆 Windows 时钟,它期望 Windows 时间量是相当恒定的。

    Sun 实际上有 known about this since 2006,但还没有修复它,AFAICT!

    因此,我们的时钟实际上快了两倍!一个循环休眠 1 毫秒的简单 Java 程序显示了这种行为。

    解决方案是自己将时间分辨率设置为较低的值,并尽可能长时间地保持在那里。使用 timeBeginPeriod() 来控制它。 (我们将其设置为 1 毫秒,没有任何不利影响。)

    对于那些使用 Java 编码的人,解决此问题的更简单方法是创建一个线程,只要应用程序存在,它就会一直休眠。

    请注意,无论哪个应用程序是真正的罪魁祸首,这都会在计算机上全局解决此问题。

    【讨论】:

    • 哇哦。我每天都慢了几个小时,并广泛使用 Java...
    • @deed02392:使用 Java 时,时间似乎变慢了。这就像被困在90年代。 ;-p
    • 问题:这仅适用于 java 还是 Delphi 或 C# 在大量使用计时器时会导致类似的问题?
    • @Vincent :与任何调用 timeBeginPeriod() 且经常使用不同值的 Windows 应用程序相关。
    【解决方案3】:

    您可以在计划任务 .bat 文件中运行“w32tm /resync”。这适用于 Windows Server 2003。

    【讨论】:

      【解决方案4】:

      除了更频繁地重新同步时钟之外,我认为您无能为力,只能获得一块新主板,因为您的时钟信号似乎没有处于正确的频率。

      【讨论】:

        【解决方案5】:

        http://www.codinghorror.com/blog/2007/01/keeping-time-on-the-pc.html

        PC 时钟通常应精确到每天几秒钟。如果你正在经历巨大的时钟漂移——每天大约几分钟——首先要检查的是你的交流电源。我个人观察到将 UPS 插入另一个 UPS 的系统(顺便说一句,这是一个禁忌),每天增加几分钟。从链中移除不必要的 UPS 解决了时间问题。我不是硬件工程师,但我猜测电源中的某些时序信号被主板上的实时时钟芯片使用。

        【讨论】:

          【解决方案6】:

          如前所述,Java 程序可能会导致此问题。

          另一个不需要修改代码的解决方案是添加 VM 参数 -XX:+ForceTimeHighResolution(可在 NTP support page 上找到)。

          9.2.3。 Windows 和 Sun 的 Java 虚拟机

          Sun 的 Java 虚拟机需要使用 >-XX:+ForceTimeHighResolution 参数启动以避免丢失中断。

          请参阅http://www.macromedia.com/support/coldfusion/ts/documents/createuuid_clock_speed.htm 了解更多信息。

          来自引用的链接(通过Wayback machine - 原始链接已消失):

          ColdFusion MX:CreateUUID 提高 Windows 系统时钟速度

          在load中多次调用createUUID函数 Macromedia ColdFusion MX 及更高版本可能导致 Windows 系统时钟 加速。这是 Java 虚拟机 (JVM) 的问题 哪个 Thread.sleep 调用少于 10 毫秒 (ms) 会导致 Windows 系统时钟运行速度更快。此行为最初被归档 作为 Sun Java 错误 4500388 (developer.java.sun.com/developer/bugParade/bugs/4500388.html) 并且有 已确认适用于 1.3.x 和 1.4.x JVM。

          在 ColdFusion MX 中,createUUID 函数有一个内部 Thread.sleep 1毫秒的调用。当 createUUID 被大量使用时, Windows 系统时钟将每分钟增加几秒。以...的速率 加速度与 createUUID 调用的数量和 在 ColdFusion MX 服务器上加载。 Macromedia 观察到了这一点 Windows XP、2000 和 2003 上 ColdFusion MX 及更高版本中的行为 系统。

          【讨论】:

            【解决方案7】:

            增加重新同步的频率。 如果同步是与您自己网络上的主服务器同步,则没有理由不每分钟同步一次。

            【讨论】:

              【解决方案8】:

              更频繁地同步。查看Registry entries for the W32Time service,尤其是“期间”。 “SpecialSkew”听起来对你有帮助。

              【讨论】:

                【解决方案9】:

                时钟漂移可能是温度的结果;也许您可以尝试使温度更加恒定-也许使用更好的冷却?不过,你永远不会完全放弃漂移。

                使用外部时钟(GPS 接收器等),以及将 CPU 时间与绝对时间相关联的统计方法是我们在这里用来同步分布式系统中的事件的方法。

                【讨论】:

                  【解决方案10】:

                  因为听起来你的生意很大:

                  拿一台旧笔记本电脑或其他不太好用的东西,但似乎有一个或多或少可靠的时钟,并将其称为计时器。 Timekeeper 的唯一工作是每(比如)2 分钟一次,向服务器发送一条消息,告知时间。服务器不会使用 Windows 时钟作为它们的时间戳,而是记下从 Timekeeper 的最后一个信号开始的时间,加上自该信号以来经过的时间。每周用手表检查一次或两次计时员的时钟。这应该足够了。

                  【讨论】:

                    【解决方案11】:

                    您正在运行什么服务器?在台式机中,我遇到这种情况的时间是启用了扩频 FSB,这会导致中断时序出现一些问题,这就是时钟滴答作响的原因。可能想看看这是否是其中一台服务器的 BIOS 中的一个选项,如果启用则将其关闭。

                    您的另一个选项是编辑时间轮询间隔并使用以下注册表项使其更短,很可能您必须添加它(注意这是一个 DWORD 值,该值以秒为单位,例如 600 10分钟):

                    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient\SpecialPollInterval
                    

                    这是一个完整的检查:KB816042

                    【讨论】:

                      【解决方案12】:

                      我曾经写过一个 Delphi 类来处理时间重新同步。它粘贴在下面。现在我看到了 Larry Silverman 提到的“w32tm”命令,我怀疑我是在浪费时间。

                      unit TimeHandler;
                      
                      interface
                      
                      type
                        TTimeHandler = class
                        private
                          FServerName : widestring;
                        public
                          constructor Create(servername : widestring);
                          function RemoteSystemTime : TDateTime;
                          procedure SetLocalSystemTime(settotime : TDateTime);
                        end;
                      
                      implementation
                      
                      uses
                        Windows, SysUtils, Messages;
                      
                      function NetRemoteTOD(ServerName :PWideChar; var buffer :pointer) : integer; stdcall; external 'netapi32.dll';
                      function NetApiBufferFree(buffer : Pointer) : integer; stdcall; external 'netapi32.dll';
                      
                      type
                        //See MSDN documentation on the TIME_OF_DAY_INFO structure.
                        PTime_Of_Day_Info = ^TTime_Of_Day_Info;
                        TTime_Of_Day_Info = record
                          ElapsedDate : integer;
                          Milliseconds : integer;
                          Hours : integer;
                          Minutes : integer;
                          Seconds : integer;
                          HundredthsOfSeconds : integer;
                          TimeZone : LongInt;
                          TimeInterval : integer;
                          Day : integer;
                          Month : integer;
                          Year : integer;
                          DayOfWeek : integer;
                        end;
                      
                      constructor TTimeHandler.Create(servername: widestring);
                      begin
                        inherited Create;
                        FServerName := servername;
                      end;
                      
                      function TTimeHandler.RemoteSystemTime: TDateTime;
                      var
                        Buffer : pointer;
                        Rek : PTime_Of_Day_Info;
                        DateOnly, TimeOnly : TDateTime;
                        timezone : integer;
                      begin
                        //if the call is successful...
                        if 0 = NetRemoteTOD(PWideChar(FServerName),Buffer) then begin
                          //store the time of day info in our special buffer structure
                          Rek := PTime_Of_Day_Info(Buffer);
                      
                          //windows time is in GMT, so we adjust for our current time zone
                          if Rek.TimeZone <> -1 then
                            timezone := Rek.TimeZone div 60
                          else
                            timezone := 0;
                      
                          //decode the date from integers into TDateTimes
                          //assume zero milliseconds
                          try
                            DateOnly := EncodeDate(Rek.Year,Rek.Month,Rek.Day);
                            TimeOnly := EncodeTime(Rek.Hours,Rek.Minutes,Rek.Seconds,0);
                          except on e : exception do
                            raise Exception.Create(
                                                   'Date retrieved from server, but it was invalid!' +
                                                   #13#10 +
                                                   e.Message
                                                  );
                          end;
                      
                          //translate the time into a TDateTime
                          //apply any time zone adjustment and return the result
                          Result := DateOnly + TimeOnly - (timezone / 24);
                        end  //if call was successful
                        else begin
                          raise Exception.Create('Time retrieval failed from "'+FServerName+'"');
                        end;
                      
                        //free the data structure we created
                        NetApiBufferFree(Buffer);
                      end;
                      
                      procedure TTimeHandler.SetLocalSystemTime(settotime: TDateTime);
                      var
                        SystemTime : TSystemTime;
                      begin
                        DateTimeToSystemTime(settotime,SystemTime);
                        SetLocalTime(SystemTime);
                        //tell windows that the time changed
                        PostMessage(HWND_BROADCAST,WM_TIMECHANGE,0,0);
                      end;
                      
                      end.
                      

                      【讨论】:

                        【解决方案13】:

                        我相信 Windows 时间服务只实现了 SNTP,它是 NTP 的简化版本。完整的 NTP 实施会在决定同步频率时考虑时钟的稳定性。

                        您可以获取full NTP server for Windows here

                        【讨论】:

                        猜你喜欢
                        • 2022-07-09
                        • 1970-01-01
                        • 2011-03-04
                        • 2020-06-05
                        • 2016-06-06
                        • 2013-12-15
                        • 2013-01-15
                        • 2016-02-15
                        • 1970-01-01
                        相关资源
                        最近更新 更多