跳到主要内容

5. 本地时钟

为了实现精确且准确的本地时钟, 主机必须配备硬件时钟, 该硬件时钟由振荡器和接口组成, 并能达到所需的精度和稳定性. 随后, 使用这些硬件组件以及软件组件构造逻辑时钟; 软件组件会根据 NTP 或其他时间同步协议 (例如 Hellospeak [MIL83b] 或 Unix 4.3bsd TSP [GUS85a]) 定期计算出的更新来调整表观时间和频率. 本节描述 Fuzzball 本地时钟模型及其实现, 该模型包含精确时间和频率调整机制, 可将时间维持在 15 ns 以内, 频率维持在每天 0.3 ms 以内. 该模型适用于带补偿和不带补偿的石英振荡器, 也可适配电源频率振荡器. 这些振荡器和其他类型振荡器的特性摘要可见附录 E, NTP 本地时钟模型的完整数学分析可见附录 G.

需要注意的是, 这里描述的具体实现只是可能提供等价功能的众多实现之一. 但同样重要的是, 附录 G 描述并作为该实现基础的时钟模型涉及一种特定类型的控制反馈环; 如果违反设计规则, 该反馈环可能不稳定. 附录 G 描述的模型和参数旨在使用常规硬件, 并在硬件或网络连通性受扰的情况下, 于典型运行条件下提供准确而稳定的时间. 这些参数经过工程设计, 可在多层分级子网中可靠运行, 因为某一层的不稳定运行可能扰乱许多其他层.

5.1. Fuzzball 实现​

Fuzzball 本地时钟由一组硬件和软件寄存器以及一组算法组成, 它们实现一个逻辑时钟, 该逻辑时钟作为受控振荡器运行并同步到外部源. 以下描述其组成和运行方式. 注意, 所有算术均为二进制补码整数算术, 所有移位 << 和 >> 都是算术移位 (右移符号填充, 左移零填充). 还需注意, x << n 等价于 x >> -n.

本地时钟的主要组件是 48 位 Clock 寄存器和 32 位 Prescaler. 它们作为受控振荡器工作, 在小数点位置相对于午夜以毫秒递增. 32 位 Clock-Adjust 寄存器用于以渐进步骤调整振荡器相位, 以避免所指示时间尺度的不连续. 下文将其内容记为 x. 32 位 Skew-Compensation 寄存器用于通过在周期性调整间隔内添加小相位增量来微调振荡器频率, 可补偿最高 0.01% 或 ±100 ppm 的频率误差. 下文将其内容记为 y. 16 位 Watchdog 计数器和 32 位 Compliance 寄存器用于判断有效性, 并建立 PLL 带宽和轮询间隔 (见附录 G). 下文将 Compliance 寄存器内容记为 z. 32 位 PPS-Adjust 寄存器只在 pulse-per-second (PPS, 每秒脉冲) 信号可用时使用, 用于调整时钟.

主要数据结构由一个 48 位时间戳以及与之相关的 Offset (THETA)、Delay (DELTA) 和 Dispersion (EPSILON) 组成. 在 Fuzzball 实现中, Clock 寄存器设计为以 1 毫秒 (ms) 为单位递增, 因此使用 32 位 Prescaler 寄存器在毫秒增量之间插值. 硬件时钟由晶体振荡器驱动的可编程总线超时时钟组成, 通常以若干微秒为间隔 tick. 每次 tick 都会发生中断, 并将常量 TICK 加到 Prescaler. 当 Prescaler 溢出时 (约每毫秒一次), 常量 TICK1MS 加到 Clock 寄存器.

5.2. 渐进相位调整​

Clock-Adjust 寄存器用于以渐进步骤调整本地时钟相位. 每次时钟中断时, Clock-Adjust 寄存器内容 x 按如下方式加到 Prescaler 寄存器:

Prescaler <- Prescaler + TICK + x

由于 x 可以是正整数或负整数, 上述操作会改变本地时钟频率. 每次秒变化时, Clock-Adjust 寄存器按 CLOCK.ADJUST (通常为 8) 增加或减少, 其效果是增加或减少时钟频率, 并相应减少或增加调整时钟所需时间. 如果残余 offset 小于 CLOCK.MAX (通常为 128 ms), 则以较小幅度调整时钟频率, 并按与残余 offset 成比例的量调整时钟纪元.

以下算法在每次时钟中断时执行:

if (|THETA| >= CLOCK.MAX)               /* phase adjustment */
if (THETA > 0)
x <- x + CLOCK.ADJUST
else if (THETA < 0)
x <- x - CLOCK.ADJUST
call adjust-phase
else /* frequency adjustment */
x <- THETA / CLOCK.PHI
Prescaler <- Prescaler + x
THETA <- 0

调用 adjust-phase 过程来调整相位并更新各种状态变量. 在 Fuzzball 实现中, compliance 寄存器是长度为 NTP.WINDOW 的循环缓冲区, 用于记录近期历史. 在本地时间每秒开始时更新 compliance 寄存器, 并丢弃最旧值. 随后将所有条目绝对值的指数加权平均计算为 compliance 的估计值.

5.3. 阶跃相位调整​

如果相位调整的幅度超过 CLOCK.MAX (通常为 128 ms), 则执行 step adjustment (阶跃调整). 当本地时钟时间与参考时间差异显著时, 例如重启之后或由于时区变化需要校正时, 会使用阶跃调整. 在阶跃调整中, 时钟会直接设置为新时间, 并重新初始化所有正在进行的频率和 compliance 估计.

使用如下算法:

if (first update)                       /* never allow step on first update */
exit
if (|THETA| >= CLOCK.MAXSTEP) /* step the clock */
Clock <- Clock + THETA
THETA <- 0
Watchdog <- 0
Compliance <- 0
return (TRUE)
else
return (FALSE)

每次更新 Clock-Adjust 寄存器时, Watchdog 计数器会重置为零. 如果计数器达到 CLOCK.HOLDTC 的值 (通常为 2048 s, 约 34 分钟), Clock-Adjust 寄存器会重置为零. 这提供了滞后特性, 并防止使用过时的 offset 数据.

5.4. 实现问题​

这里和附录 G 中描述的特定本地时钟模型基于二阶反馈环. 该环路的时间常数相对较长, 大约为数小时, 目的是最小化瞬时网络延迟的影响并最大化稳定性. 因此, 频率校正的变化可能需要相当长时间才能完成, 尤其是在重启或阶跃相位调整之后. 为加快环路响应, Clock-Adjust 寄存器可预装入关闭前最后计算出的值.

在 Fuzzball 实现中, 提供了完整的监控和控制设施, 可检查所有本地时钟变量并控制时钟运行. 此外, 一个特殊功能提供 time-of-day 显示, 不仅显示时钟读数, 还显示 compliance 以及是否有有效同步源可用. 关于 Fuzzball 本地时钟设计和实现的更多信息可见 [MIL88b].

这里描述的本地时钟模型专门设计为与 NTP 和 Internet 环境配合工作. 但是, 在不同情况下可能还有许多更合适的可能方案. 例如, 某些系统可能可以访问在长时间间隔内高度稳定的精密振荡器, 如铯振荡器或 GPS 接收器. 在这种情况下, 频率校正机制可能不必要或不合适. 此外, 如果需要秒级左右间隔内的高精度, 例如语音或视频应用, 这里描述的渐进相位校正机制可能无法提供足够精度. 在这种情况下, 可能需要某种 predictor-corrector (预测-校正器) 或 phase-locked loop (锁相环), 以在 NTP 不频繁更新之间进行插值.

如前所述, 在某些配置中可能有以一秒为间隔的精确定时脉冲可用. 在这种情况下, 可能适合使用 PPS 信号约束本地时钟频率, 同时使用 NTP 约束秒、分钟、小时和日期的编号. 这可以在仍提供可信 UTC 时间的同时, 达到微秒级精度. 在这种情况下必须小心: 如果编号源失效, 应采取适当措施抑制来自 PPS 信号的进一步更新.