Windows上的时钟漂移

时间:2020-03-06 14:26:05  来源:igfitidea点击:

我已经开发了一种Windows服务,该服务可以跟踪业务事件。它使用Windows时钟对事件进行时间戳记。但是,底层时钟可能会发生相当大的漂移(例如,每分钟损失几秒钟),尤其是在CPU努力工作时。我们的服务器使用Windows时间服务与域控制器保持同步,该域控制器在后台使用NTP,但是同步频率由域策略控制,并且在任何情况下,即使每分钟同步也仍然会产生很大的漂移。除了使用硬件时钟以外,是否有其他技术可以使时钟更稳定?

解决方案

除了更频繁地重新同步时钟外,我不认为除了获得新的主板外,我们还不能做很多事情,因为时钟信号似乎频率不正确。

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

同步更多。查看W32Time服务的注册表项,尤其是" Period"。 " SpecialSkew"听起来可能会对我们有所帮助。

时钟漂移可能是温度的结果;也许我们可以尝试使用更好的冷却方法使温度更恒定?但是,我们永远都不会完全摆脱漂移。

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

我们正在运行哪些服务器?在台式机中,我遇到的情况是启用了扩频FSB,这会导致中断时序出现一些问题,这正是时钟计时的原因。可能希望查看其中一台服务器上BIOS中的选项,如果启用,则将其关闭。

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

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

这是一个完整的检查结果:KB816042

我们可以在计划的任务.bat文件中运行" w32tm / resync"。这适用于Windows Server 2003.

我曾经写过一个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.

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

我们可以在此处获取适用于Windows的完整NTP服务器。

时钟滴答应该是可预测的,但是在大多数PC硬件上,因为它们不是为实时系统设计的,所以其他I / O设备中断的优先级高于时钟滴答中断,并且某些驱动程序在中断服务例程中进行了大量处理,而不是延迟将其发送给延迟过程调用(DPC),这意味着系统可能直到发出信号后(有时)很长时间才能处理时钟滴答中断。

其他因素还包括总线主控I / O控制器,这些控制器从CPU窃取了许多内存总线周期,从而导致其在相当长的一段时间内都缺乏内存总线带宽。

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

Windows确实允许调整每个中断上添加到实时时钟的滴答声的数量:请参阅SetSystemTimeAdjustment。但是,这仅在我们具有可预测的时钟偏斜的情况下才有效。如果时钟仅略微关闭,则SNTP客户端(" Windows时间"服务)将调整此偏斜以使时钟略微变快或者变慢,以趋向正确的时间。

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

Windows存在一个问题,如果我们使用timeBeginPeriod()大量更改计时器分辨率,则时钟会漂移。

实际上,Java的Threadwait()(和os :: sleep())函数的Windows实现中存在一个导致此行为的错误。为了准确(无论睡眠时间长短),它总是将计时器分辨率设置为等待前1 ms(无论睡眠时间长短),并在完成后立即将其恢复,除非任何其他线程仍在睡眠。然后,此设置/重置将使​​Windows时钟混乱,后者期望Windows时间范围相当恒定。

实际上,Sun自2006年以来就已经知道这一点,并且尚未解决,AFAICT!

正因为如此,我们实际上使时钟运行速度提高了一倍!一个简单的Java程序在一个循环中睡眠1毫秒。

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

对于那些使用Java进行编码的人来说,解决此问题的更简单方法是创建一个线程,该线程在应用程序运行时一直处于休眠状态。

请注意,这将是在全局计算机上解决此问题的方法,而不管哪个应用程序是真正的罪魁祸首。

由于听起来生意很大:

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