1.时间同步

1.1 单步同步(OneStep)

单步同步最为简单,master向slave发送一个Sync的同步包,同步包里带有这条信息发送时master的当前时间t1,假如这条信息从master传输到slave需要的传输时间是D,那么slave收到信息时,master的当前时间已经变成了t1+D,如果slave当前时间是t2,那么它们之间的时间差

offset = t2 - (t1+D)

比如上图的例子,假如master在自己时间是15:00的时候发送了一个Sync包,包里面带有时间t1即15:00,slave在自己时间是15:40的时候收到了这条信息,接收时间点是t2,slave看到包里面数据显示这条信息发送时间点是master的15:00,假如slave已经知道了它和master之间传递消息需要30分钟,那它可以很简单的算出它现在比master快了10分钟,否则它现在应该是15:30。

1.2两步同步(TwoStep)

两步同步一样简单,在单步同步里,Sync包直接携带了这个包的发送时间点,两步同步和它唯一的区别在于这个时间点不再直接放在Sync包里了,而是在发送完Sync包后,紧接着发送一个follow_up包,把这个时间放在follow_up包内。

对于上图的情况就是master在自己时间为15:00时发送了一个Sync包给slave,但是这个包里什么也没有。slave在自己时间是15:40时,收到了这个Sync包,此时时间点是t2,到这里为止,除了它并不知道Sync包是什么时候发送的(t1),其他都和单步一模一样,所以它会继续等待后续的follow_up包。而master在15:10的时候发送了一个follow_up包,所以slave在15:50才收到了follow_up包,但slave对于follow_up包的发送和接收时间根本不关心,因为现在只差Sync包的发送时间了,而Sync的发送时间就包含在follow_up包内。

是不是觉得单步同步是两步同步的优化版?明明单步就能实现,为何还要多一个follow_up包去传递Sync的发送时间?

很简单,思考一下Sync这个15:00的时间点是怎么来的?

假如master和slave是两台电脑,你发送Sync时,可以直接把15:00填充到Sync包里,然后告诉网卡,你需要在15:00把这条信息发送出去,但是这很难实现,而且很麻烦,还需要网卡支持。简单的方法是你把Sync包给网卡,但是里面的时间并不填充,而是让网卡在发送时,把这个时间填写进去,这也需要网卡支持,但是明显更符合实际一点。假如我们使用follow_up时,则最简单,我们只需要记录发送时间即可,而根本不需要在发送时填充。

2. 测量延迟

2.1 E2E

在时间同步里,我们假设了我们已知消息传递时间D,但其实这个时间我们一开始并不知道,所以需要计算。E2E的意思是End to End,从名字就可以看出,它从一个终点到另一个终点。

E2E必须要时间同步包Sync(如果是两步则也包括follow_up)同时参与。它的计算也很简单,master在t1时刻发送Sync给slave,slave收到的时候是t2,slave知道了t1和t2,则有

t2-t1=D+offset

slave在t3时刻发送Delay_Req给master,master收到的时候是t4,然后master把这个时间t4填充到Delay_Resp里再发回给slave,slave知道了t4和t3,则有

t4-t3=D+(-offset)

所以t4-t3+t2-t1=2D,从而消掉了offset,只需再除以2就可以算出D的时间。

或者由上图看,t-ms是master发送到slave的路径延迟,t-sm是slave发送到master的路径延迟。我们用t4-t1-(t3-t2)可以算出t-ms+t-sm的值,当t-ms=t-sm时,D=t-ms=t-sm,所以只需要把结果再除以2就可以了。这里很关键的前提条件是,从master->slave和slave->master的路径延迟相同!其实正常情况下并不相同,但是这里我们为了计算D,把它们看作是相同的。

2.2 P2P

P2P的意思是Peer to Peer,它意思是端到端。请注意上图,两边的设备不再是master和slave,也就是说对于无论是master还是slave都可以主动发送第一条Pdelay-Req消息,从而开启一次测量流程,这点和E2E不同,E2E里我们知道,最后所有的时间只有slave知道,所以只有slave可以计算出路径延迟D。还有一点不同是,仅仅用P2P计算路径延迟D不需要Sync包参与,所以当计算完路径延迟D后,再进行时间同步时还需要发送Sync包。

注意,P2P和E2E报文携带的时间不太一样。首先PDelay_Req的发送时间是t1,而对端接收到PDelay_Req的时间是t2,这和Sync包一模一样。但是Pdelay_Resp里面包含的不再是发送时间t3了!仔细回想上面的E2E,我们计算D的时候也需要Sync包参与,所以即使是单步也需要来回3条报文,P2P如果和E2E一模一样是不可能通过两条报文就计算出来D的,因为只在PDelay_Resp中填充t3的话,Node-A最后只有t1,t3,t4少了一个t2,根据我们在E2E中推导的计算D的最终表达式,我们知道t3和t2我们最终需要的是它们两个相减,所以PDelay_Resp里面填充的是t3-t2,而不是t3+t2。填充的位置是修正域。但是注意,这需要网卡支持:

  1. 在收到PDelay_Req时记录时间t2

  1. 在发送PDelay_Resp时获取当前时间t3,然后把t3-t2填充到PDelay_Resp报文中,立即发送。

上述单步情况需要网卡支持,如果网卡不支持,我们也可以使用两步应答,这就不需要在发送PDelay_Resp报文时就立马把t3-t2计算出来填充到报文中,而是在发送完PDelay_Resp报文后,获取发送时间t3,然后手动计算t3-t2,再发送一个包含它的PDelay_Resp_Follow_up报文,填充位置是修正域。注意PDelay_Req中表示是单步还是两步同步的标志并不重要,其实直接置为单步就可以,因为P2P测量是单步还是两步看的是PDelay_Resp这个报文中的标志,只有它里面显示是两步的,后面才会有PDelay_Resp_Follow_up报文,这点和Sync一模一样。

两步应答还有一种方式,是把PDelay_Req的接收时间t2放在PDelay_Resp的原始时间戳字段,再把PDelay_Resp的发送时间放在PDelay_Resp_Follow_up的原始时间戳字段,由发送PDelay_Req那一方自己计算t3-t2。

实际的计算过程则大同小异,从上图上看,计算无非是:

D=(t4-t1-(t3-t2))/2

当然也可以理解为t4-t3=D+(-offset),t2-t1=D+offset,再去消掉offset计算。

2.3 E2E-TC

其实只单单看E2E和P2P感觉并没有什么不同,所以当然会有感而发,为何有了E2E还需要P2P?

TC的全称是Transparent clock,即透明时钟,何为透明?

即对于上图的master和slave来说,它们两个在通过报文交互的时候根本不知道它们是否中间存在一个TC(一般来说是交换机),TC对于它们好像透明的一样,它们之间还是认为彼此是直接相连的。

对于E2E方式的TC来说,TC仅仅是一个中转站,它把收到的master的报文直接转发给slave,但是注意,从TC收到报文,再到转发出去,这中间是需要时间的(比如它可能先把收到的包统一放在某个缓存中,然后发送的时候再从缓存中取包发出去,一来一回肯定需要额外的耗时)!所以在master和slave之间添加了一个TC就相当于平白无故的多了一些延时,假如TC从收到master的数据包到转发给slave需要额外增加的延迟是C1,而TC从收到slave的数据包到转发给master需要额外增加的延迟是C2(通常这两个时间并不相同),那么是不是意味着我们只需要把这个时间转发的时候放在数据包里就可以了?

回到我们最开始的单步同步的情况:

一开始我们的单步中间没有TC,现在新加了一个TC,已知从TC收到数据到把这个数据再转发出去需要的时间C1是10分钟,这就导致了原本15:00发送的Sync包在master时间是15:30的时候就能到达slave,但是现在需要15:40才能到达slave!而此时slave的时间已经是15:50了,假如我们已经事先知道了路径延迟D=30min,转发延迟C1=10min,那么我们很快就可以求得slave和master之间的偏移offset=15:50-15:00-30min-10min=10min。所以新增了TC后,最简单有效的办法就是把TC上的延迟C1,在转发的时候直接放在Sync包里,这样slave在收到Sync包后,直接就可以计算offset了。但是注意,在单步的时候Sync的时间戳是硬件填充的,也就是给到网卡的数据是没有时间戳的,网卡在发送前会先获取当前时间,然后填到时间戳字段,最后发出去。如果TC也想延续单步直接转发,那么TC的网卡需要支持:

  1. 收到Sync包时记录接收时间

  1. 转发前获取当前时间,用这个时间减去之前记录的接收时间

  1. 把这个时间填写到时间戳修正字段,然后立即发送

以上是master支持单步同步,TC也支持单步同步的情况。如果master是两步同步,TC支持单步同步则可以是:

TC只需要在转发的Sync包的时候把延迟C1添加到时间戳修正字段即可。这样slave收到Sync和Follow_up后就可以得到C1和t1、t2。注意Sync和follow_up都有时间戳修正字段,但是只添加一个就可以了,一般硬件支持的话才可以直接添加到Sync(当然也可以不用硬件直接添加的,自己计算然后添加到follow_up里,不过纯属多此一举),硬件不支持时,只能添加到follow_up里,就比如下面的情况。

如果master支持单步,但是TC不支持,那么只能TC自己手动更改单步为两步模式。具体如何操作?

  1. 首先我们把Sync包中表示单步同步的标志改成两步同步,记录接收到Sync包的时间,和Sync包里面的发送时间戳t1

  1. 直接转发Sync包,在Sync包发出去之后,获取发出Sync包时的时间,然后减去接收时间,计算出来延迟C1

  1. slave接收到Sync看到是两步同步,不会理会Sync中的时间戳,而是会等待后续的follow_up包,不过此时slave已经获得了Sync包的接收时间t2

  1. TC在计算出来C1后,会自己生成一个follow_up包,把刚才记录的Sync包的发送时间t1填到时间戳那里,然后把C1填到时间戳修正那里,直接发送给slave

  1. slave收到TC给它的follow_up包后,用t2-t1-D-C1即可求出来offset

但是上述过程是在我们知道路径延迟D的前提下,所以我们还需要知道添加了TC之后,计算D有何变化。

由上图可知,从master到slave的D = t2-t1-C1-offset,slave到master的D = t4-t3-C2-(-offset)

所以还是假设两个D相等,D = t2-t1-C1-offset+t4-t3-C2-(-offset) = (t2-t1-C1+t4-t3-C2)/2

此时计算的D可能受C1和C2的误差影响,计算offset时,会再次受C1影响。

最终整个流程如下:

这里注意C2给到了master,而C2对slave才是有用的,所以master通过Delay_Resp给slave发送t4的时候,会把修正域改成C2,这个时间是slave发给master的Delay_Req的停留时间,而不是Delay_Resp的停留时间!当然这样做的前提是TC的硬件支持转发Delay_Req的时候直接把停留时间C2添加到修正域,如果TC硬件仅支持双步,也就是TC转发后才能获取到发送时间,那么TC上需要获取Delay_Req的发送和接收时间,然后相减,把计算得到的C2放在后续转发的Delay_Resp的修正域里。

2.4 P2P-TC

E2E是End to End,也可以理解为终点到终点,所以它不管master和slave之间多了多少个TC,它最终计算的都是master到slave它们两个之间的路径延迟D。但是P2P是端到端,每插入一个TC,会把master和slave之间多隔开一段。P2P-TC中,计算D的时候,是分段计算的,就拿master和slave之间插入了一个P2P-TC来说,计算D的时候,我们需要计算master和TC之间的路径延迟D1,然后再计算slave和TC之间的路径延迟D2,总的路径延迟D = D1+D2。假如master用两步同步,P2P-TC和slave用单步同步,报文交互过程如下(只截取某段,实际master先发送的PDelay_Req):

以上流程可以简化总结为:

  1. TC发送PDelay_Req给master,master应答PDelay_Resp给TC,从而TC计算得到D1

  1. slave发送PDelay_Req给TC,TC应答PDelay_Resp,从而slave计算D2

  1. master发送Sync给slave,TC收到Sync后直接转发,因为硬件支持单步,所以停留时间C1会被硬件直接添加到Sync包的修正域内

  1. master发送follow_up包给slave,TC收到后把计算得到的D1添加到Follow_up包的修正域内,然后直接转发

  1. slave收到TC发过来的Sync包后,得到了C1和t2,收到Follow_up包后得到了t1和和D1,最后可以计算offset = t2-t1-D1-D2-C1

这里注意,使用P2P计算D时,不分master和slave,两端都可以发送PDelay从而计算D。

其次可以看到,除了Sync和Follow_up这些用来计算offset的报文,其实单单计算D时,master和TC之间的路径延迟D1,slave和TC之间的路径延迟D2,都是独立计算的,它们之间没有关联。

所以master、slave还有P2P-TC它们每个都可能是单步或者两步的,彼此不影响对方。

假设master是单步,slave和TC是两步的,整个流程为:

  1. TC发送PDelay_Req给master,master应答PDelay_Resp给TC,从而TC计算得到D1(这个过程中master也会给TC发送PDelay_Req从而计算D1,但是其实这个结果用不到)

  1. slave发送PDelay_Req给TC,TC应答PDelay_Resp和PDelay_Resp_Follow_up,从而slave计算D2

  1. master发送Sync给slave,TC收到Sync后记录Sync报文中的时间t1以及Sync报文的接收时间,然后把报文类型改成两步同步,再转发给slave

  1. slave收到Sync报文后看到是两步同步,不会理会Sync内的时间戳,但是会记录Sync的接收时间t2

  1. TC在转发完Sync后,会自己生成Follow_up报文,先获取刚才发送的Sync报文的发送时间,再根据Sync报文的接收时间,算出来停留时间C1,把D1和C1相加填到时间戳修正字段,把刚才记录的t1填到原始时间戳字段,然后发送

  1. slave收到Follow_up包后,就拿到了D1、C1、t1,加上自己算得的D2和已有的t2,可以计算

offset = t2-t1-D1-D2-C1

假如master、slave、P2P-TC都是单步,则需要硬件:

  1. 转发Sync报文时可以自动添加停留时间C1到修正域,同时不能转发PDelay_Req、PDelay_Resp等报文

  1. 硬件要提供接口设置我们计算的D1到Sync的修正域(一般转发报文硬件可以做到,发送报文很难实现,所以给master发送PDelay_Req,以及给slave应答PDelay_Resp一般都需要我们软件实现)

  1. 应答slave的PDelay_Resp报文时,硬件需要添加发送时间,此时需要软件添加接收时间。或者硬件可以自己计t3-t2然后添加到报文中。

2.5 TC对比

首先很重要的一点是,E2E和P2P针对的是路径延迟D的测量,和offset的测量没有关系。

通过对比来看,整个链路中如果使用E2E的方式对时,所有slave都会向master发送Delay_Req从而进行延迟测量(也就是前文说的,无论master和slave之间有多少个E2E-TC,最终测量的都是两个终点之间的延迟),这样会对master处理报文的能力有所要求,当slave过多时,master必定会出现处理不过来的问题。很明显,P2P的方式就不存在这样的问题,因为P2P的整条路径延迟被分段进行。也就是计算offset的报文会被P2P-TC转发,比如master发送的Sync、Follow_up会被转发给所有slave,但是计算路径延迟的报文只会在分隔成每段的两个节点之间进行。

其次,当链路出现故障,要重构网络时,如果是E2E方式,那必定要重新计算master到slave之间的路径延迟,但是使用P2P时,由于路径延迟是一段一段的,所以不需要再重新计算整条路径的延迟,报文在经过哪个P2P-TC时,哪个P2P-TC就会把它计算的路径延迟D添加到修正域,这样的结果就是,Follow_up包的修正域所包含的路径延迟D总是它经过的那些路径的。

是不是感觉有些绕口?这里有一个问题,假如master和slave中间有很多条路径可以到达,使用E2E时,如果slave发送的Delay_Req和master发送Sync、Delay_Resp这三条报文选择的路径都不相同会发生什么?答案是,你通过Delay_Req和Delay_Resp计算的路径延迟D,并不是Sync报文的路径延迟,这势必会导致偏差offset计算错误。

同样的问题思考P2P时会发生什么?

假如是单步,P2P-TC会添加Sync报文经过的上一段路径的路径延迟D到此时需要转发的Sync报文的修正域,这保证了Sync报文从master到slave的路径延迟永远是正确的。双步的情况下,P2P-TC假如Sync和Follow_up包选择了不同路径,在向Follow_up包添加D的时候还是可能出现问题的,但是如果P2P-TC支持向Sync报文添加D,就可以规避这种情况。

是不是E2E-TC一无是处?答案是否定的。E2E-TC的兼容性要好于P2P-TC,纯硬件实现E2E-TC是比P2P-TC要简单的。毕竟E2E-TC不用主动发送报文,只需要转发时计算停留时间。其次如果使用P2P的master和slave之间插入一台E2E-TC,理论上master和slave仍旧可以正常同步时间。

3. 频率同步

频率同步既可以是单步也可以是两步,对于单步的情况来说,假如15:00的时候B给A发送了Sync包,15:10的时候又发送了一个Sync包,理论上来说,假如B和A的时间走的一样快,那么A收到两个Sync包的时间间隔也是10分钟!路径延迟和两个设备之间的时钟偏差并不影响这个间隔。如果不能理解就再做一个假设,A收到第一个包的时间是15:40,路径延迟是30分钟,那么我们知道A和B时钟偏差是10分钟,算上这个偏差,第二个包里面的时间是15:10,那么A可以计算出来,如果频率没有偏差,收到第二个包的时间应该是15:50!但是假如A收到第二个包的时候它自己的时间是16:00,它就知道了B发送两个包的间隔是10分钟,而自己收到两个包的时间分别是15:40和16:00,间隔20分钟,所以A的时钟比B快一倍!知道这个之后,我们只需要把A的时钟调慢一倍即可,注意不是直接把时钟当前时间减去10分钟,那再过10分钟A又比B快10分钟了,而是把A原来走2秒的时间调成只算做1秒!

附一些PTP相关资料:

1588v2报文格式

linuxptp项目

linuxptp项目github仓库

linuxptp中ptp4l使用手册

瑞萨关于linux对PTP硬件时间戳支持介绍

https://www.elinux.org/images/f/f9/Introduction_to_IEEE_1588_Precision_Time_Protocol_(PTP)_Using_Embedded_Linux_Systems.pdf

【精选】IEEE1588v2解析(4)--透明时钟/一步时钟/二步时钟_1-step clock和2-step clock_Simple-Easy 化繁为简的博客-CSDN博客

LinuxPTP没那么简单_ts2phc-CSDN博客

【精选】linuxptp/ptp4l PTP时钟同步配置选项-CSDN博客

新华三PTP介绍

华为PTP介绍

GB/T 25931-2010

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐