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

使用系统时钟的时间偏差

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (3投票s)

2015 年 12 月 17 日

CPOL

4分钟阅读

viewsIcon

8465

使用系统时钟的时间偏差

引言

在许多情况下,需要精确计时未来事件以启动进程;不幸的是,通常没有明确的解决方案。

在 Windows 中,有三种方法可以测量时间

  • 一种基于系统时钟 - 如果发生夏令时或有人手动调整时钟,这可能会产生危险的影响,一个等待 5 秒后执行某些操作的进程可能会等待 1 小时 5 分钟或完全错过。
  • 使用带有等待计时器的系统回调 - 这很好用,但您将无法监控已经过去了多少时间。
  • GetTickCountGetTickCount64 - 这两种方法都易于使用,但前者每 49.7 天会循环一次,而后者在 Windows Vista 之前不可用。

在其他平台上;例如嵌入式系统,没有实时时钟,因此必须通过计算处理器或其他指定芯片引起的特定中断来进行计时;一些嵌入式库(如 Arduino)将使用对 millis() 的调用为您完成此操作。

在这篇文章中,我将使用 GetTickCount 以及如何解决 49.7 天的限制问题。虽然我将使用 VB.net 代码展示这一点,但这里没有内容是特定于语言或操作系统的,因为这很容易适应其他语言和平台。

背景

我在工业控制领域开发软件;因此,我的很多代码都使用基于时钟、滴答和回调的计时器。我的大部分工作都使用 PC 来控制进程,通过读取 IO 的信息并做出相应的决策。我还制作了一些嵌入式设备。

此处介绍的内容不应被视为实时控制,而无需考虑权衡。并非所有嵌入式系统都能使用我所阐述的内容,每个特定设备都必须单独处理。

去年,网上发表了一篇文章,讲述了一家飞机制造商如何要求他们的面板大约每 48 天完全断电一次,因为系统上运行的一些代码存在错误。这听起来熟悉吗?

显然有人正在使用某种类似于 GetTickCount 的调用,并将旧值与新值进行比较以计算各种事物。这在大多数情况下都能正常工作,直到 32 位整数填满并回滚到零,从而造成各种混乱。

我无法说明他们使用的平台或其他任何信息,因为我从未在其中一个平台上工作过,但是有一些技巧可以解决这个问题。

使用代码

我的解决方案是将值提升到更大的 64 位数。

Private _current As Int64
Private _last As UInt32
Private Declare Function GetTickCount Lib "kernel32" () As UInt32

    Sub New()
        _current = GetTickCount()
        _last = _current
    End Sub

    Private Sub update()
        Dim t As UInt32 = GetTickCount()

        If t < _last Then
            'roll over detected
            Dim tmp As Int64 = t Or &H100000000   '32 second bit
            _current += (tmp - _last)
        Else
            _current += (t - _last)
        End If

        _last = t
    End Sub

就是这样。我所做的只是获取初始值并将其保存在更大的容器中,还有一个 32 位标记用于知道上次获取的值。因此,随着代码的更新;我获取最新的滴答计数,如果它看起来小于上一次的值,则发生了回滚,因此我将使用 2^32 位和当前值进行或运算,然后从 _last 值中减去该值以获得调用之间的时间差,并将其添加到 _current 标记中。

因为 _current 标记是 64 位的,所以我确信在我的有生之年(重启的可能性更大)该值不会溢出。

关注点

我最终将此代码包装到一个 .NET 日期样式结构中,这使我可以将值转换回日期或反之亦然,添加或减去毫秒等等。如果需要将时间戳保存到文件或日志中,我将始终转换为日期结构,以防计算机重启。

在没有实时时钟的嵌入式系统上,不太可能需要转换为日期和时间或表示过去的任何时间戳,因此我将使用无符号数来保存和缓冲。

如果您必须负责计数中断,请尽快退出代码,我会尽量使代码保持简单,例如 Var++;您绝不应该让中断处理太多工作。使用您的普通循环代码来检查使用 Var 的溢出,因为您永远不知道它何时会被中断。

© . All rights reserved.