剖析Linuxptp中ptp4l实现--OC

源码克隆地址:

git://git.code.sf.net/p/linuxptp/code

项目官网文档:

https://linuxptp.nwtime.org/documentation/

关于linuxptp的相关配置可以参考以下博文:

linuxptp/ptp4l PTP时钟同步配置选项

代码剖析

ptp4l的main函数在ptp4l.c中,命令行解析使用的是 getopt_long ,具体使用方法可以百度,这个是现成的命令行解析API。

剖析Linuxptp中ptp4l实现--OC_第1张图片

可以看到解析不同命令行参数后都是调用的 config_set_int 函数设置,linuxptp中配置一般都是保存在 config.c 中的 config_tab 中:

剖析Linuxptp中ptp4l实现--OC_第2张图片

关于配置项所代表的含义可以参考上文推荐的博文。

命令行中比较重要的是 -i ,也就是添加interface:

剖析Linuxptp中ptp4l实现--OC_第3张图片

创建接口使用的是网卡名称,比如 -i eth0,此时就会创建一个名字是eth0的接口,源码如下:

剖析Linuxptp中ptp4l实现--OC_第4张图片

在 interface_create 中注意,除了名字(name)还有ts_label也被设置为传入的网卡名称:

剖析Linuxptp中ptp4l实现--OC_第5张图片

除去配置参数和接口创建,其实功能主体就是创建clock,和轮询创建clock时添加的文件描述符:

剖析Linuxptp中ptp4l实现--OC_第6张图片

在 clock_create 中只看几个关键的地方,第一个是软硬件时间戳相关:

剖析Linuxptp中ptp4l实现--OC_第7张图片

在clock创建的初始你会看到基本都是初始化 c->dds 这个结构体相关的配置,这里在协议原文中有:

剖析Linuxptp中ptp4l实现--OC_第8张图片

其实dds就是defaultDS,这几个数据集都是协议明文规定的数据集,linuxptp中在ds.h中有所定义,详细内容可以参照协议原文第8章节PTP data sets。

在配置比如使用软件时间戳还是硬件时间戳,是onestep还是twostep时,会先根据设置得到一个网卡预期需要支持的模式,然后根据前面创建的interface,获取网卡的信息,再判断网卡是否支持:

剖析Linuxptp中ptp4l实现--OC_第9张图片

再下面是确定使用哪个PHC(ptp hardware clock)的逻辑:

剖析Linuxptp中ptp4l实现--OC_第10张图片

还有UDS(unix domain sockets)的配置:

剖析Linuxptp中ptp4l实现--OC_第11张图片

剩下的就是clock本身一些杂项初始化,在这个函数末尾有最重要的port添加与初始化:

剖析Linuxptp中ptp4l实现--OC_第12张图片

在添加port的时候,可以看到每个port申请了多少个fd:

剖析Linuxptp中ptp4l实现--OC_第13张图片

从上图可以看到clock的port个数=interface个数+2

剖析Linuxptp中ptp4l实现--OC_第14张图片

从上图可以看到,当没有添加过port的时候port个数是两个uds,每次添加一个port,实际是加了3个port,也就是添加一个port的时候,一共有5个port,每个port有 N_CLOCK_PFD 个文件描述符,这些文件描述符就是后续需要轮询的。N_CLOCK_PFD是12,其中除了包含下面11个fd,还有一个处理错误状态的定时器fd。

剖析Linuxptp中ptp4l实现--OC_第15张图片

回到刚才的函数,port_open中还有一些port的参数设置,其中比较重要的有:

剖析Linuxptp中ptp4l实现--OC_第16张图片

以及通过 transport_create 创建了传输实例:

剖析Linuxptp中ptp4l实现--OC_第17张图片

根据传输类型有UDS/ETHERNET/IPV4/IPV6可选,最终trp就是一组包含发送接收等的函数指针合集:

剖析Linuxptp中ptp4l实现--OC_第18张图片

比如IPV4:

剖析Linuxptp中ptp4l实现--OC_第19张图片

port_open 中还有有限状态机(fsm)的设置:

剖析Linuxptp中ptp4l实现--OC_第20张图片

需要注意,状态机的各种状态也是协议中所明文规定的:

剖析Linuxptp中ptp4l实现--OC_第21张图片

具体内容请参照协议原文9.2.5章节。 

再有就是fault定时器也在这里被创建:

剖析Linuxptp中ptp4l实现--OC_第22张图片

回到clock_create函数最后对port的初始化:

剖析Linuxptp中ptp4l实现--OC_第23张图片

根据前面port_open中的源码,假如我们是E2E的OC,那么我们的 port_dispatch 函数是 bc_dispatch :

剖析Linuxptp中ptp4l实现--OC_第24张图片

在 port_state_update 里我们根据 EV_INITIALIZE 事件对端口进行了初始化:

剖析Linuxptp中ptp4l实现--OC_第25张图片

在 port_initialize 函数中,除了初始化一些参数配置,最重要的是创建了各种定时器fd:

剖析Linuxptp中ptp4l实现--OC_第26张图片

拿IPv4来举例,319和320是固定的两个端口,它们就是通过 transport_open 函数打开:

剖析Linuxptp中ptp4l实现--OC_第27张图片

这里event port用来接收event消息,general端口用来接收general消息:

剖析Linuxptp中ptp4l实现--OC_第28张图片

详细信息可以参考协议原文7.3.3。

至此,所有配置都初始化完成了,后续就只剩下一直轮询之前添加的fd而已了。在main里有:

剖析Linuxptp中ptp4l实现--OC_第29张图片

clock_poll 函数里面主要就是轮询fd,然后分发事件。 

剖析Linuxptp中ptp4l实现--OC_第30张图片

假如还按照之前举的例子,E2E的OC的话,port_event实际是 bc_event 函数。

剖析Linuxptp中ptp4l实现--OC_第31张图片

比如我们是master的话,sync同步包发送定时器时间到了我们就会:

剖析Linuxptp中ptp4l实现--OC_第32张图片

在处理完定时器fd事件后,紧跟着就是接收来自两个fd的数据:

剖析Linuxptp中ptp4l实现--OC_第33张图片

然后根据接收到的事件做不同处理,同时更新状态机状态。

E2E--master

其实master要做的事情很简单:

1.发送Announce报文

2.发送Sync(FollowUp)报文

3.应答DelayReq报文,也就是回发DelayResp报文。

正常对时的两个设备报文如下:

剖析Linuxptp中ptp4l实现--OC_第34张图片

ptp4l中是如何实现的呢?

首先,master是如何当上grandmaster的,这里我讲一下只有一个设备时,自身是怎么被选举为grandmaster的,在port初始化函数 port_initialize 中,有初始化两个参数:

剖析Linuxptp中ptp4l实现--OC_第35张图片

在 port_open 函数中有初始化另一个参数:

剖析Linuxptp中ptp4l实现--OC_第36张图片

这几个参数又在 port_initialize 中被使用:

剖析Linuxptp中ptp4l实现--OC_第37张图片

剖析Linuxptp中ptp4l实现--OC_第38张图片

上图函数中:

M = p->announceReceiptTimeout = 3 

S = p->announce_span = 1

N = p->logAnnounceInterval = 1

所以设置的超时时间范围是6~8s

注意,周期性的报文的周期值最好不是一个定值。比如现在要以周期为2秒发送一个X报文,那最好的办法不是每隔2秒就发送一个X报文,而是在2秒的基础上加一个随机值(比如+1~-1之间的一个数),这样可以减少碰撞。 

设置完 FD_ANNOUNCE_TIMER 后,当定时器超时后:

剖析Linuxptp中ptp4l实现--OC_第39张图片

会返回一个Announce报文接收超时的状态,这个状态在clock_poll中有:

剖析Linuxptp中ptp4l实现--OC_第40张图片

可以看到会改变clock的sde的值(state decision event,状态决策事件) ,当这个值置1后,会处理一次状态,还是在这个函数内:

剖析Linuxptp中ptp4l实现--OC_第41张图片

剖析Linuxptp中ptp4l实现--OC_第42张图片

其中历遍所有port的时候会用类似冒泡法去对比,当只有一个port的时候这个port自然会被选为master:

剖析Linuxptp中ptp4l实现--OC_第43张图片

当选举完master clock后,会更新所有port的状态:

剖析Linuxptp中ptp4l实现--OC_第44张图片

bmc_state_decision 中关于状态决策可以参考协议原文9.3.5中的相关描述。

然后生成了一个 EV_RS_GRAND_MASTER 事件,被分发到相关端口:

剖析Linuxptp中ptp4l实现--OC_第45张图片

OC/BC使用的分发函数是 bc_dispatch  在这个函数中有更新port状态:

剖析Linuxptp中ptp4l实现--OC_第46张图片

port_state_update 中使用的状态机 state_machine 是在 port_open 函数中初始化时赋值的:

剖析Linuxptp中ptp4l实现--OC_第47张图片

 这里master使用的是ptp_fsm,如果是slave则是ptp_slave_fsm。然后根据这个函数得到下个状态:

剖析Linuxptp中ptp4l实现--OC_第48张图片

我们的当前状态是 PS_LISTENING,事件是 EV_RS_GRAND_MASTER ,所以下个状态就是 PS_GRAND_MASTER:

剖析Linuxptp中ptp4l实现--OC_第49张图片

拿到这个状态后,根据选择的是E2E测量方式,所以会使用 port_e2e_transition 函数处理:

剖析Linuxptp中ptp4l实现--OC_第50张图片

在这个函数中,当处于这个状态时,则会设置两个定时器:FD_MANNO_TIMER 和 FD_SYNC_TX_TIMER。注意之前 port_initialize 函数中有初始化 p->inhibit_announce = 0:

剖析Linuxptp中ptp4l实现--OC_第51张图片

在这里第一次设置了 FD_MANNO_TIMER ,第一次设置的超时时间很短,后续设置是在 bc_event 中的:

剖析Linuxptp中ptp4l实现--OC_第52张图片

默认情况下,超时时间是2秒:

而发送报文的内容可以查看 port_tx_announce 函数。

和设置 Announce 报文发送时间差不多,发送 Sync 报文的超时时间是1秒: 

剖析Linuxptp中ptp4l实现--OC_第53张图片

发送Sync报文的函数可以查看 port_tx_sync 。

E2E--slave

一般指定当前设备为slave时使用 -s选项:

剖析Linuxptp中ptp4l实现--OC_第54张图片

这里我主要讲一下slave收到报文后怎么计算offset和pathDelay的。

上面已经介绍过,对于接收报文的处理在 bc_event 函数内,对于E2E的slave主要是处理以下几种报文:

剖析Linuxptp中ptp4l实现--OC_第55张图片

首先看对于sync报文的处理:

剖析Linuxptp中ptp4l实现--OC_第56张图片

这里我们假如先收到的是Sync报文,则事件是 SYNC_MISMATCH ,在Sync和FollowUp报文处理状态机函数 port_syfufsm 里面有:

剖析Linuxptp中ptp4l实现--OC_第57张图片

我们再看假如在Sync后收到FollowUp报文该如何处理:

剖析Linuxptp中ptp4l实现--OC_第58张图片

 至此当前状态是 SF_HAVE_SYNC ,处理的事件是 FUP_MATCH:

剖析Linuxptp中ptp4l实现--OC_第59张图片

在函数 port_synchronize 中就可以拿到当前的t1、t2、c1(Sync的修正域)、c2(FollowUp),此时即可计算:

offset = t2-(t1+c1+c2) 代码中如下所示:

剖析Linuxptp中ptp4l实现--OC_第60张图片

在函数 clock_synchronize 中有:

剖析Linuxptp中ptp4l实现--OC_第61张图片

剖析Linuxptp中ptp4l实现--OC_第62张图片

这里的 delay 也就是 filtered_delay 又是从何而来的呢?

在 bc_event 中对与发送DelayReq报文的定时器超时后会发送此报文:

剖析Linuxptp中ptp4l实现--OC_第63张图片

port_delay_request 函数里关键的有以下处理: 

剖析Linuxptp中ptp4l实现--OC_第64张图片

还是在 bc_event 里收到 DelayResp 报文后 使用 process_delay_resp 函数处理:

剖析Linuxptp中ptp4l实现--OC_第65张图片

剖析Linuxptp中ptp4l实现--OC_第66张图片

剖析Linuxptp中ptp4l实现--OC_第67张图片

原始pathdelay的计算过程如下:

剖析Linuxptp中ptp4l实现--OC_第68张图片

上图中可以看到我添加了一些关键位置的打印,现在实际运行起来,slave打印如下:

剖析Linuxptp中ptp4l实现--OC_第69张图片

你可能感兴趣的:(1588v2,linuxptp,1588v2,嵌入式,PTP,linux)