源码克隆地址:
git://git.code.sf.net/p/linuxptp/code
项目官网文档:
https://linuxptp.nwtime.org/documentation/
关于linuxptp的相关配置可以参考以下博文:
linuxptp/ptp4l PTP时钟同步配置选项
代码剖析
ptp4l的main函数在ptp4l.c中,命令行解析使用的是 getopt_long ,具体使用方法可以百度,这个是现成的命令行解析API。
可以看到解析不同命令行参数后都是调用的 config_set_int 函数设置,linuxptp中配置一般都是保存在 config.c 中的 config_tab 中:
关于配置项所代表的含义可以参考上文推荐的博文。
命令行中比较重要的是 -i ,也就是添加interface:
创建接口使用的是网卡名称,比如 -i eth0,此时就会创建一个名字是eth0的接口,源码如下:
在 interface_create 中注意,除了名字(name)还有ts_label也被设置为传入的网卡名称:
除去配置参数和接口创建,其实功能主体就是创建clock,和轮询创建clock时添加的文件描述符:
在 clock_create 中只看几个关键的地方,第一个是软硬件时间戳相关:
在clock创建的初始你会看到基本都是初始化 c->dds 这个结构体相关的配置,这里在协议原文中有:
其实dds就是defaultDS,这几个数据集都是协议明文规定的数据集,linuxptp中在ds.h中有所定义,详细内容可以参照协议原文第8章节PTP data sets。
在配置比如使用软件时间戳还是硬件时间戳,是onestep还是twostep时,会先根据设置得到一个网卡预期需要支持的模式,然后根据前面创建的interface,获取网卡的信息,再判断网卡是否支持:
再下面是确定使用哪个PHC(ptp hardware clock)的逻辑:
还有UDS(unix domain sockets)的配置:
剩下的就是clock本身一些杂项初始化,在这个函数末尾有最重要的port添加与初始化:
在添加port的时候,可以看到每个port申请了多少个fd:
从上图可以看到clock的port个数=interface个数+2
从上图可以看到,当没有添加过port的时候port个数是两个uds,每次添加一个port,实际是加了3个port,也就是添加一个port的时候,一共有5个port,每个port有 N_CLOCK_PFD 个文件描述符,这些文件描述符就是后续需要轮询的。N_CLOCK_PFD是12,其中除了包含下面11个fd,还有一个处理错误状态的定时器fd。
回到刚才的函数,port_open中还有一些port的参数设置,其中比较重要的有:
以及通过 transport_create 创建了传输实例:
根据传输类型有UDS/ETHERNET/IPV4/IPV6可选,最终trp就是一组包含发送接收等的函数指针合集:
比如IPV4:
port_open 中还有有限状态机(fsm)的设置:
需要注意,状态机的各种状态也是协议中所明文规定的:
具体内容请参照协议原文9.2.5章节。
再有就是fault定时器也在这里被创建:
回到clock_create函数最后对port的初始化:
根据前面port_open中的源码,假如我们是E2E的OC,那么我们的 port_dispatch 函数是 bc_dispatch :
在 port_state_update 里我们根据 EV_INITIALIZE 事件对端口进行了初始化:
在 port_initialize 函数中,除了初始化一些参数配置,最重要的是创建了各种定时器fd:
拿IPv4来举例,319和320是固定的两个端口,它们就是通过 transport_open 函数打开:
这里event port用来接收event消息,general端口用来接收general消息:
详细信息可以参考协议原文7.3.3。
至此,所有配置都初始化完成了,后续就只剩下一直轮询之前添加的fd而已了。在main里有:
clock_poll 函数里面主要就是轮询fd,然后分发事件。
假如还按照之前举的例子,E2E的OC的话,port_event实际是 bc_event 函数。
比如我们是master的话,sync同步包发送定时器时间到了我们就会:
在处理完定时器fd事件后,紧跟着就是接收来自两个fd的数据:
然后根据接收到的事件做不同处理,同时更新状态机状态。
先写到这儿,有时间再完善。