现代人的生活已经离不开时间了,无论是出门上班,还是外出旅行,都需要准确的知道我们所处位置的时间。日常生活中,往往分钟、秒级的时间精确度就够用了,但在工程技术中,比如飞机巡航、机器控制、网络管理都需要更高精度的时间测量。我们需要准确的知道两个事件之间发生的时间。精确的测量时间是一件非常复杂的技术活儿。在世界各地,要在不同网络与设备之间同步时间是一件非常具有挑战的事情。首先,需要解决的问题是如何精确测量时间,其次是将时间准确的同步到其他系统或者设备。第一个问题可以通过原子钟(atomic clocks)来解决,比如标准时间的采用的铯原子钟误差可以达到1亿年1秒;卫星导航系统如GPS,北斗都会搭载一个原子钟用于高精度的导航,因此GPS信号也可以作为一个时钟源用于授时;第二个时间同步一般通过标准的协议来实现,本文重点介绍使用较为普遍的一种同步协议PTP(Precise Time Protocol)。
时间同步协议如NTP(Network Time Protocol)或者SNTP(Simple Network Time Protocol)本质上是一种基于UDP协议(协议端口号123)的同步协议,用于同步世界时钟(UTC)与主机的时间。NTP最早在1981年提出,经过多个版本的迭代优化,最新NTPv4版本同时支持IPV4/IPV6,并提供加密认证的流程,在安全性上有比较大的提升。

NTP使用分布式、分层的时间同步架构,每层的时间同步被称为stratum(层),比如最高层stratum0一般是原子钟或者GPS时钟,作为整个系统的时钟源。具体来说,时间同步协议采用客户端-服务端架构,客户端通过向NTP服务器发送时间同步请求(NTP服务器则通过原子钟或者GPS进行授时),时间同步的具体步骤简述如下:
- 客户端首先向服务端发送一个
NTP请求报文,其中包含了该报文离开客户端的时间戳T1 NTP请求报文到达NTP服务器,此时NTP服务器的时刻为T2。当服务端接收到该报文时,NTP服务器处理之后,在T3时刻发出NTP应答报文。该应答报文中携带报文离开NTP客户端时的时间戳T1、到达NTP服务器时的时间戳T2、离开NTP服务器时的时间戳T3- 客户端在接收到响应报文时,记录报文返回的时间戳
T4
根据上述4个时间戳,客户端可以计算出NTP报文从客户端到服务端的延迟
$$ delay = (T4 - T1) - (T3 - T2) $$
假定客户端与服务端之间的时间差为offset,可以得到:
$$ T4 = T3 - offset + \frac{delay}{2}$$
因此我们可以计算出时间差offset为:
$$ offset = \frac{(T2 - T1) + (T4 - T3)}{2} $$
客户端基于该时间差来调整自己的系统时间,完成最终的时间同步。
PTP协议基本概念
NTP一般用于操作系统的时间同步,如Windows、Android等系统都支持NTP时间同步,但是由于NTP是一个应用层的同步协议,因此会受系统调度延迟的影响,而且会因为网络不对称、系统RTC(Real-Time Clock)时钟温漂、老化等因素,导致时间同步的精度下降,一般只能达到ms级别的精度。而在工业自动化如机器人控制,5G通信,高频金融交易以及电力系统中,需要更高精度的时间同步,为此IEEE在2002年发布了一个新的时间同步协议1588v1,之后在2008年又发布了第二个版本1588v2;2019发布了一个改进版本1588v2.1,增强了安全性与兼容性。目前常用的时间同步协议PTP都是基于1588v2版本实现,如gPTP(Generalized PTP)协议就是基于1588v2扩展而来。
PTP协议主要有如下几个核心的概念:
PTP域: 应用了PTP协议的网络称为一个PTP域;PTP域内有且只有一个同步时钟,域内的其他设备需要与该时钟保持同步;域内负责同步时间的节点称为master,而接收时间同步的设备节点称为slavePTP域中有几种不同类型的时钟:OC(Ordinary Clock)普通时钟,只有一个物理端口用于时间同步,可以作为首节点(Grandmaster Clock)向下游节点发布时间,也可以作为末节点(slave clock)从上游节点同步时间BC(Boundary Clock)边界时钟:该时钟节点有多个物理端口可以用于网络通讯,其中一个端口用于从上游设备同步时间,其余端口向下游设备发布时间TC(Transparent Clock)透明时钟,节点有多个物理端口可以进行网络通讯,不过不用于同步时间,只负责处理与转发PTP协议报文,透明时钟节点有两种类型,一种是E2E(End-to-End),一种是P2P(Peer-to-Peer),区别在于E2E TC转发报文时,会测量报文经过时的转发延迟,并修正到PTP报文中;P2P TC不仅修正转发延迟,还会测量并修正该节点每个端口相连链路的时延(链路传递的延迟)。

PTP时间同步原理
PTP协议在时间同步之前,一般需要从同步域中选择一个最优时钟Grandmaster Clock, GM),即整个PTP同步域中的时间源。最优时钟可以通过静态配置制定,也可以通过BMC(Best Master Clock)算法动态选举得到:
- 各个时钟节点通过
Announce包围报告端口上的时钟源信息(最终时钟优先级、时间等级、时间精度、本地晶振的稳定性等),维护本地获得的时钟数据组,按照严格的时钟等级选择最佳时间源,并确定端口状态。通过时钟选举过程,整个PTP域内构建出一颗无环、全连通,以GM为根的生成树。 - 此后,
master节点会定时发送Announce报文给其他节点,如果网络发生变化,或者从节点没有收到来自主节点的Announce报文,需重新进行最优时钟的选择
在上文中提到,PTP时间同步协议中,有两种不同的同步机制,一种是端到端E2E(End-to-End),另一种是点对点P2P(Peer-to-Peer),两者的差异在于主时钟节点(master)与从时钟节点(slave)的链路延迟测量机制不同:
E2E会直接测量两个OC或者BC之间的总链路延迟,包括其间的所有中间TC节点。P2P仅限于测量两个直连相连的OC,BC或者TC节点之间的逐点链路延迟
E2E同步
E2E时间同步基于主从节点(master-slave)的方式,通过Sync,Delay_Req,Delay_Resp报文交互,从节点计算出与主节点的时间差,从而完成系统时间的同步。具体的流程如下:
Master节点在t1时刻发送Sync报文(如果配置为双步模式(two-step),需要发送Follow_Up报文;单步模式下(one-step),无需发送Follow_Up报文),并将t1时间戳携带在Sync报文(或Follow_Up报文)中Slave节点在t2时刻接收到Sync报文,在本地产生t2时间戳,并从报文中提取t1时间戳Slave节点在t3时刻发送Delay_Req报文,并在本地产生t3时间戳Master节点在t4时刻接收到Delay_Req报文,并在本地产生t4时间戳,然后将t4时间戳携带在Delay_Resp报文中,回传给SlaveSlave节点接收到Delay_Resp报文,从报文中提取t4时间戳。最后Slave节点得到了一组时间戳(t1,t2,t3,t4)

假设Master节点到Slave节点的发送链路延迟是$t_{ms}$,Slave节点到Master节点的发送链路延迟是$t_{sm}$,Slave节点和Master之间的时间偏差为offset,于是可以得到:
$$ t2 - t1 = t_{ms} + offset $$
$$ t4 - t3 = t_{sm} - offset $$
$$ (t2 - t1) - (t4 - t3) = (t_{ms} + offset) - (t_{sm} - offset) $$
$$ offset = [(t2 - t1) - (t4 - t3) - (t_{ms} - t_{sm})] / 2 $$
如果$t_{ms} = t_{sm}$,即Master节点和Slave节点之间的收发链路延迟对称,那么:
$$offset = [(t2 - t1) - (t4 - t3)] / 2 $$
这样Slave节点就可以根据t1,t2,t3,t4四个时间戳计算出自己和Master节点之间的时间偏差offset,完成了Slave节点与Master节点的时间同步。但如果Master节点和Slave节点之间的收发链路延迟存在不对称,会存在同步误差,误差的大小为两个方向链路延迟差值的二分之一。因此,对于一些高精度同步场景,需要对Master和Slave之间的收发链路延迟不对称进行补偿。
P2P同步
P2P时间同步模式下,所有节点都会与相连节点进行报文交互,这样每个节点都可以计算出与其他连接节点的链路延迟;但真正的时间同步,依然只存在于Master节点与Slave节点之间。类似于E2E的模式,每个节点发送报文也分为单步与双步两种方式,对于单步方式,Pdelay_Resp报文带有本报文发送时刻的时间戳;而双步方式,Pdelay_Resp报文并不带有本报文发送时刻的时间戳,只是记录本报文发送时的时间,本报文发送时刻的时间戳由后续报文Pdelay_Resp_Follow_Up携带。各个节点的链路延迟测量步骤如下:

- 节点2在
t1时刻发送Pdelay_Req报文 - 节点1在
t2时刻接收到Pdelay_Req报文,生成该报文的接收时间戳t2 - 节点1在
t3时刻发送Pdelay_Resp报文,生成该报文的发送时间戳t3
- 对于单步方式,把
t3 – t2携带在Pdelay_Resp报文中 - 对于双步方式,把
t3 – t2携带在Pdelay_Resp_Follow_Up报文中,或者Pdelay_Resp报文携带t2,Pdelay_Resp_Follow_Up报文携带t3
- 节点2在
t4时刻接收到Pdelay_Resp报文,在本地产生t4时间戳;最后节点2得到了一组时间戳(t1,t2,t3,t4)
假设节点2到节点1的发送链路延迟是$t_{reqresp}$,节点1到节点2的发送链路延迟是$t_{respreq}$,可以得到节点2到节点1的总链路往返延迟为:
$$ (t_{reqresp} + t_{respreq}= (t4 - t1) - (t3 - t2) $$
如果$t_{reqresp} = t_{respreq}$,即节点2到节点1之间的收发链路延迟对称,那么节点2和节点1之间的链路平均延迟为:
$$ MeanPathDelay = \frac{[(t4 - t1) - (t3 - t2)]}{2} $$
上述过程只是不断地实时计算和更新相连节点之间的链路延迟,并不进行时间同步。时间同步,还需要有Master节点与Slave通过交互Sync/Pdelay_Req/Pdelay_Resp报文计算得到(如下图所示):Master节点向Slave节点周期发送Sync报文(Slave节点得到t5/t6两个时间戳)。最终,Slave节点与Master节点之间的时间偏差为:
$$ offset = t6 - t5 - MeanPathDelay $$

PTP协议在Linux中是如何实现的
PTP是一个通用的时间同步协议,可用于不同的网络环境,其支持UDP(V4/V6)协议发送同步报文,也可以基于L2(MAC)进行时间的同步。从上面的PTP协议时间同步的机制来看,时间同步的精度主要依赖于如下几个个关键的因素:
- 报文的时间戳标记的层级,时间戳的位置离硬件层级越近,时间戳的精度越高;如果依赖于软件时间戳,则会受到软件调度与网络协议栈的波动影响
- 两个同步节点之间的延迟波动,如果节点之间的延迟不对称,则可能影响时间精度;类似地,如果中间节点存在延迟波动,也会影响时间精度
- 系统时钟与
MAC/PHY中的硬件时钟(PHC, PTP Hardware Clock)的精度,容易受温度、电压、环境等因素的影响
Linux中有一个开源的PTP协议栈(简称LinuxPTP),主要包括两个核心的工具,一个是ptp4l,主要用于发送、接收PTP报文,完成时间同步;一个是phc2sys,用于同步系统中不同的时间域的时间,比如PHC时钟与RTC时钟的同步。为了实现纳秒级别时间精度,通常需要使用物理时间戳,也就是以太网网卡MAC/PHY中增加一个TSU(TimeStamping Unit)模块,用于专门解析PTP报文,并将报文的时间戳用PHC物理时钟的时间替代,以尽可能降低系统带来的精度波动(jitter),比如IEEE 802.1AS的时间同步协议gPTP就要求必须支持物理时间戳。
如下图所示,PTP时间同步主要有如下几个核心模块:
LinuxPTP协议栈,实现了IEEE1588v2协议,包含ptp4l和phc2sys两个核心工具MAC/PHY中的TSU(TimeStamping Unit)模块,提供物理时间戳能力PHC时钟,为TSU模块提供时钟源参考Linux内核驱动,包括PTP时钟驱动,posix时钟驱动,以及UDP协议栈

要查看以太网网卡是否支持物理时间戳,可以使用ethtool命令:ethtool -T eth0,如果结果中显示有hardware-transmit/hardware-receive能力,则表示网卡是支持物理时间戳。
1 | ~$ ethtool -T enp0s31f6 |
接下来,我们结合LinuxPTP协议栈与Linux内核的源码看一看PTP协议的实现;主要分为两个部分,一个是ptp4l代码的实现,一个是Linux内核驱动部分包括PTP时钟的实现。
本文使用的
LinuxPTP版本为V4.2
ptp4l的实现
ptp4l除了常规的命令行参数之外,还可以通过一个配置文件来设定时间同步的参数;以配置文件为例,常见的参数主要有如下几个
logSyncInterval:PTP时间同步间隔,更低的间隔通常能改善本地时间的精度delay_mechanism: 时间同步的方式,有E2E/P2P/Auto三种,默认是E2Enetwork_transport: 网络传输方式,有UDPv4/UDPv6/L2三种,默认是UDPv4twoStepFlag: 是否支持双步同步,默认是开启双步同步masterOnly: 是否只支持master节点,默认是falseptp_dst_mac:PTP报文目的MAC地址,如果选择L2的通讯方式,则需要指定目的MAC地址clock_type: 时钟类型,有OC/BC/P2P_TC/E2E_TC几种,默认是OCBMCA:最佳时钟选择算法,用于配置master与slave节点,如果设定为noop,则会跳过常规的BMCA过程,使用静态的配置
在LinuxPTP源码的目录(configs/automotive-master.cfg),已有部分ptp4l的配置文件可以参考,以车载网络中的master节点配置为例:
1 |
|
ptp4l的源代码主要有几个关联的部分:
port: 对应于以太网网卡的网口,一个网口可能有好几个状态enum port_state,比如初始化、运行、监听等clock:PTP时钟对象,可能包含了很多的port,也保存了时间同步的一些状态信息PTP时间同步协议消息的封装与发送,系统不同时钟之间的处理
我们找到ptp4l的入口函数是main()函数,可以看到,其核心逻辑主要有如下几个步骤:
clock_create: 根据用户指定的参数与配置文件,创建PTP时钟对象,同时会创建时钟对应的port对象clock_add_portclock_poll: 持续监控port状态,根据port的时间类型进行状态转换与处理
1 |
|
限于篇幅,感兴趣的可以参考源码中的clock.c、port.c等文件。接下来,我们简单看看PTP时钟内核的实现框架。
PTP物理时钟驱动框架
PTP物理时钟的驱动框架主要分为两个部分,一个是提供公共接口与驱动框架的,相关的类型定义都放在include/linux/ptp_clock_kernel.h中;PTP物理时钟对应一个结构体struct ptp_clock_info,包含了PTP时钟的配置以及获取、设置时钟参数的接口。
1 |
|
对于支持PTP物理时钟的以太网驱动来说,需要在初始化的时候调用ptp_clock_register注册PTP时钟,并在驱动卸载的时候调用ptp_clock_unregister进行反注册。以英特尔的一个千兆以太网驱动ethernet/intel/igb/igb_main.c为例,可以看到网卡驱动在执行初始化igb_probe时,会调用igb_ptp_init注册PTP时钟:
1 |
|
函数ptp_clock_register主要完成了如下几个工作:
- 创建一个字符设备,设置设备号与名称,注册完成后可以在
/dev/ptpx上访问到PTP时钟 - 如果时钟本身支持
PPS(Pulse-per-Second),那么还需要通过pps_register_source注册PPS源 - 最后将
PTP时钟注册为一个标准的posix时钟,这样可以通过标准的posix接口方式来访问PTP时钟
1 |
|
总结
高精度的时间同步实现起来是一个比较复杂的事情,从最初的NTP到如今的PTP,时间同步的精度已经可以达到微妙级别,但随着实时音视频、自动驾驶等场景对时间同步精度的要求日益提高,基于PTP协议衍生出了新的时间同步方式,比较常见的有:
TSN(Time Sensitive Networking)时间敏感网络,是一种可以实现确定性时延迟、高可靠性与支持优先级控制的协议,一般用于实时音视频流的传输;TSN协议集中包含了一个改善的PTP协议gPTP(generalized Precision Time Protocol, IEEE802.1AS)PTM(Precision Time Measurement)/ePTM(enhanced Precision Time Measurement)时间测量,基于PCIE总线实现的一种时间同步协议。PTM协议目前在桌面系统、服务器领域有使用,车载领域还是以PTP使用最为广泛
参考资料
- https://info.support.huawei.com/info-finder/encyclopedia/zh/1588v2.html
- https://blog.csdn.net/woswod/article/details/82345380
- https://blog.csdn.net/yaojiawan/article/details/124601694
- https://linux.die.net/man/8/ptp4l
- https://info.support.huawei.com/info-finder/encyclopedia/zh/NTP.html
- https://eci.intel.com/docs/3.3/development/performance/tsnrefsw/tsn-overview.html
- https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-configuring_ptp_using_ptp4l