65.9K
CodeProject 正在变化。 阅读更多。
Home

两个计时器的故事

starIconstarIconstarIconstarIconstarIcon

5.00/5 (26投票s)

2016年12月6日

CPOL

9分钟阅读

viewsIcon

30473

downloadIcon

383

一项使用周期性计时器测量 Windows PC 上时钟漂移的练习。

引言

这是第二版。它包含一个更新的ClockTracker应用程序,以及从2016年12月30日至2017年1月2日收集的96小时数据。

在域环境中运行的Windows PC上的时钟并不会以恒定的速度前进。相反,它会加速和减速,以与域控制器保持同步。

通过比较当前时间和Win32多媒体计时器的长期数据可以显示这一点。

下载包含

  1. 用于获取数据集的ClockTracker应用程序的源代码
  2. ClockTracker的可执行版本,以及
  3. 收集的数据集#1和#2(如下所述),以及(4)闰秒数据(请参阅下面的附录)

概述

本文展示了在Windows下比较两种时间信息源的结果:周期性计时器和当前时间值。

周期性计时器,也称为系统计时器,可以通过Win32多媒体计时器库访问。它被配置为具有一毫秒的粒度。当前时间值来自.NET的DateTime.Now属性。

人们会期望周期性计时器和当前时间时钟之间的关系是线性的。也就是说,对于周期性计时器的每一次滴答,当前时间时钟都会增加一个固定的增量值,等于周期性计时器滴答之间的真实时间差。

事实并非如此。对于域中的Windows PC,当前时间时钟相对于周期性计时器,在任何24小时内都会多次加速和减速。

数据采集

给定以下值

P0 = 初始周期性计时器值
T0 = 初始当前时间值
Pi = 第i个周期性计时器值(i > 0)
Ti = 第i个当前时间值(i > 0)

到第i次采样时,当前时间时钟与周期性计时器之间的差值是Di,计算公式为

DPi = Pi - P0
DTi = Ti - T0
Di = DTi - DPi

DPiDTiDi值都将根据需要转换为毫秒。

下载中包含的数据集数据仅包含PiDi值。

收集了两个数据集。每个数据集中,使用运行Windows 7 SP1的PC(称为“测试PC”)以每秒一次的采样率采集了大约24小时的数据。在每个图中,Y轴是D的值,X轴是采样数。X轴上的每个刻度代表3600个采样,约等于一小时。

数据集#1

在第一个数据集中(图1),测试PC在整个期间都连接到域。我们看到D值在24小时内多次在正负之间切换。

图1 - 数据集#1的D值图 - 测试PC连接到域

数据集#2

在第二个数据集中(图2),在测试开始前,测试PC已断开与网络的连接(因此也断开了与域的连接)。在数据收集开始后约18小时,它重新连接到网络(和域)。

图2 - 数据集#2的D值图 - 测试PC前18小时脱离域

数据集合并

请注意,图1和图2的Y轴刻度不同!如果将两个数据集绘制在一起,效果如下面的图#3所示。

图3 - 数据集#1(蓝色)和数据集#2(红色)的D值图

发生了什么?

警告:这完全是我的推测。我没有任何正式的参考来支持它。

周期性计时器精确但不准确。我所说的精确是指计数器值每次增量之间的时间是固定的,不会改变。我所说的准确是指每次增量之间的时间并不完全等于其设定的值。

如果周期性计时器被设置为1毫秒的分辨率,那么每次滴答之间的**实际**时间可能略有不同,例如1.0001毫秒或0.9999毫秒。

Windows PC上的当前时间时钟由周期性计时器驱动。每次滴答后,当前时间时钟将增加计时器的分辨率。例如,如果周期性计时器设置为4毫秒,那么每次滴答后,当前时间时钟将增加相同的量:4毫秒。

周期性计时器的任何不准确都会导致当前时间时钟漂移。例如,如果周期性计时器设置为一毫秒的分辨率,但实际滴答之间的时间是1.0001毫秒(误差为0.01%),那么24小时后,当前时间时钟将偏离八秒多。

域控制器登场...

域控制器以高精度了解真实时间,通常从已知精确的网络时间服务器获取此类信息。域中的每个PC都有一个当前时间偏移值,该值与周期性计时器分辨率一起,在每次周期性计时器滴答后添加到当前时间时钟。控制器会定期检查PC的当前时间时钟。如果时钟比域控制器上的当前时间时钟提前,则会减小偏移值。如果时钟落后,则会增加偏移值。

数据集#1表明偏移值并非完美。也就是说,每次周期性滴答的实际持续时间加上偏移值永远不会精确地等于一毫秒。它总是略微偏少或偏多。因此,随着域控制器不断进行校正,方向会不断切换。

数据集#2显示,如果没有域控制器作为时间源,当前时间时钟会与真实时间越来越远。一旦测试PC重新连接,当前时间时钟就会被快速调整,以使其与域控制器时钟同步。对重新连接到域后的数据进行检查表明,当前时间时钟以每秒3毫秒的速度(约0.3%)进行调整。

这真的重要吗?

嗯……不,其实不是。

无论域控制器对当前时间时钟应用多少校正,时钟的速度只会发生微小的变化。时钟肯定不会停止或倒退!

用户不会注意到,几乎所有软件都不会受到影响。请注意,我说的是几乎所有,因为它确实影响了我的一项项目,这也是我发现这个问题的契机。请参阅下面的“**趣味点**”。

但我倾向于发现这些幕后发现很有趣,即使是像这样不相关的发现……

代码

完整的源代码可在下载中找到。

下面显示的代码是负责收集时间数据的线程。为了使意图更清晰,代码已简化。

ThreadProc()之外定义的变量如下:

  • dataCount - 要收集的数据样本数
  • sampleTime - 采样之间的毫秒数
  • ClockEvent() - 一个事件处理程序,用于接收收集的数据;仅记录periodOffsetclockDiff(分别对应上面描述的PiDi)这两个值。
  • TimePeriod - 一个封装Win32 timePeriod API的类
private void ThreadProc()
{
    TimePeriod.Begin(1);

    DateTime startClock = DateTime.Now;
    int startPeriod = TimePeriod.Ticks;

    while (dataCount > 0)
    {
        Thread.Sleep(sampleTime);
        DateTime curClock = DateTime.Now;
        int curPeriod = TimePeriod.Ticks;

        int clockOffset = (int)(curClock - startClock).TotalMilliseconds;
        int periodOffset = (curPeriod - startPeriod);
        int clockDiff = clockOffset - periodOffset;

        ClockEvent(this, periodOffset, clockDiff);
        dataCount--;
    }
}

关注点

我在开发.NET软件以与外部设备通信时偶然发现了这个问题。

设备和PC将通信数小时,在此期间需要使设备时钟和PC时钟保持严格同步。不幸的是,设备时钟的精度不够高,无法满足我们的要求。我们的实验,以及设备制造商的规格证实,其漂移高达百万分之一(或0.001%)。这似乎是一个很小的数字,但经过10小时后,可能会导致时钟差异超过300毫秒。

对我们来说,300毫秒是一个问题,但解决方案很简单。该软件将维护一个时钟偏移值,该值将被加到设备的时戳上以获得“真实”时间。该偏移值大约每分钟更新一次,方法是比较设备时戳和相应的PC时钟值。

在与设备通信了数小时后,我期望偏移值的图是一条直线——根据设备上特定时钟晶体运行快还是慢,均匀地增加或减少。但该图与上面的图#1类似——它改变方向的次数很多。

起初,我以为是时钟晶体有问题。直到比较了周期性计时器和当前时间时钟之后,问题才得以解决。

未来工作

我认为深入研究这个问题会很有趣,只是为了看看还能发现什么。我很好奇时钟时间的模式在更长的时间段内(例如几周和几个月)会如何变化。一种处理方法是将其应用程序更改为服务,以便能够自动跟踪时间漂移,而无需登录。

附录#1 - 2016年闰秒

在2016年底,世界时钟增加了一个闰秒。谷歌处理闰秒的方式是将闰秒“摊开”20个小时,从12月31日午夜前十小时开始,并在午夜后十小时结束。

我运行了ClockTracker四天(96小时)——12月30日、31日以及1月1日、2日——采样率为每十秒一次。我希望在足够长的数据采集时间内,这种“摊开”效果会显现出来。

结果(如下所示)……令人失望。没有明显的“摊开”效果。

时钟时间模式在12月31日接近午夜时确实发生了变化,1月1日中午(以及1月2日下午3点左右)开始出现了大的跳跃。这些可能很重要,但数据中没有什么能够跳出来说“嗨!是我!闰秒!我在这儿!”。算了……

历史

  • 2016年12月6日 - 第一个版本
  • 2017年1月14日 - 第二个版本;新版本的ClockTracker应用程序和2016年闰秒的数据
© . All rights reserved.