0 前言
ptp4l
是linuxptp-4.2
的一部分。
1 main()
ptp4l的main()
主要做三件事:
- 读取配置。
- 调用功能config_create()创建
config
实例,并从全局变量config_tab[]
数组读取默认配置值。 - 调用config_create_interface(),从命令行选项解析网络接口。
- 调用clock_read(),从配置文件读取用户配置值。
- 调用功能config_create()创建
- 调用clock_create()创建
clock
实例,以及其下的port
、transport
、tsproc
实例等。初始化每个port
的定时器fd
和socket fd
。 - 在循环中调用clock_poll(),监听所有
fd
的事件,如定时器超时、ptp消息到达等,派发处理。
![](https://img.haomeiwen.com/i15107051/30df1d2bdd40c3ca.png)
2 config
config
保存用户配置项。配置项可以来自如下三个源:
- ptp4l的默认配置项、
- ptp4l配置文件、
- 启动ptp4l的命令行选项。
![](https://img.haomeiwen.com/i15107051/05b64a8fd442c0ce.png)
2.1 config.interfaces
ptp4l可以同时指定多个网卡授时,config.interfaces
保存网卡列表。这是一个STAILQ
,其定义来自于<sys/queue.h>
。interfaces
可以通过命令行或配置文件增加。
config_create_interface()创建interface
实例。
![](https://img.haomeiwen.com/i15107051/4f4693ae1625aa02.png)
interface
的成员如下。
- 成员
list
用于将interface
连接成一个STAILQ
。 - 成员
name
是网卡的名字,ts_label
一般与name
相同。 - 成员
ts_info
是网卡的时间戳支持特性,如timestamping
、phc_index
等。 - 成员
if_info
是网卡的支持速率信息,如speed
等。
![](https://img.haomeiwen.com/i15107051/1cc0a4d29913f076.png)
2.2 config.htab
htab
是一个hash表
。所有的配置参数都以config_item
格式保存在这个表中。
在config_create()中,创建htab
,并从全局数组config_tab[]
中读入参数。
struct config_item config_tab[] = {
PORT_ITEM_INT("allowedLostResponses", 3, 1, 255),
PORT_ITEM_INT("announceReceiptTimeout", 3, 2, UINT8_MAX),
PORT_ITEM_ENU("asCapable", AS_CAPABLE_AUTO, as_capable_enu),
...
}
![](https://img.haomeiwen.com/i15107051/49e476c86f077662.png)
在config_read()中,解析配置文件时,从中读出的参数值将覆盖htab
中的值。
config_read()的第一个参数是配置文件名,这是在命令行选项中指定的。
![](https://img.haomeiwen.com/i15107051/888bd3933378c5f9.png)
2.3 hash与node
hash实现了一个hash表。
- 成员
table
是一个长度为200
的数组,数组元素是node
链表。
node
是hash
表中的节点。
- 成员
data
和key
分别是数据和它的关键字。
![](https://img.haomeiwen.com/i15107051/1d6e81eeeaa1b5ec.png)
2.4 config.config_item
config_item
是全局变量config_tab
元素类型。
- 成员
label
为参数名 -
config_type
为参数类型,如int/double/enum/string等等。- 当
config_type
为int
/double
类型时,成员val
、min
和max
保存默认值,最小、最大值; - 当
config_type
为enum
类型时,tab
保存值; - 当
config_type
为string
类型时,成员val
保存值。
- 当
-
flags
是参数标志。比如是全局有效,还是只针对指定端口。
![](https://img.haomeiwen.com/i15107051/1539fbd54ddb88ca.png)
2.5 config.opts
opts
是struct option
数组。他用于调用getopt_long()
函数,从命令行选项获取参数。
3 clock
clock
定义时钟节点。
- 成员
type
按照角色定义clock
类型,如普通节点、边界节点、PTP节点等。 - 成员
timestamping
按照打时间戳方式定义类型,如软时钟、硬时钟等。 - 成员
ports
是clock下属的ports实例的列表。成员nports是ports实例数。 - 成员
pollfd
数组,保存所有fd,用于监听。其中有所有port的fd,加上另外两个UDS port的fd。 - 如果
pollfd
数组准备好,则成员pollfd_valid
为true
,否则为false
。当port的fd有变化时,这个标志可以告知监听函数,应该重建pollfd
数组了。
![](https://img.haomeiwen.com/i15107051/612a5ae5e576090e.png)
4 port
port
定义网络接口,与config.interfaces
是对应的。
- 成员
list
将port实例链接在一个列表中。 - 成员
name
是网口名,比如eth0
。 -
clock
是port
关联的clock实例。 - 成员
trp
是port的transport
实例,负责收发网络包。 - 成员
timestamping
为打时间戳的方式,如TS_SOFTWARE
、TS_HARDWARE
等。 - 成员
fda
的类型是fdarray
。fdarray
包含一个fd数组,数组长度是一个port需要创建的fd数。-
FD_PORT
值是fd数组中的索引。其中FD_EVENT
和FD_GENERAL
对应收发网络包的socket; -
FD_DELAY_TIMER
~FD_UNICAST_SRV_TIMER
对应定时器的fd
, -
FD_RTNL
对应获取端口信息的socket
。
-
- 成员
phc_index
是phc设备索引。 - 成员
state
为port
的当前状态。 - 成员
peer_pelday_req
/peer_delay_resp
/peer_delay_fup
保存一个ptp消息序列。后面还会详细说明。
![](https://img.haomeiwen.com/i15107051/d006d24c4cbfef6f.png)
4.1 port_state
port
状态state
的变换是一个状态机,由fsm_event
事件驱动。
port_state
定义port
的状态。
![](https://img.haomeiwen.com/i15107051/9ae4fab8acbffd81.png)
4.2 state_fsm
ptp master和ptp slave的状态转换的差异由port_fsm区分。比如,ptp master 的处理函数是designated_master_fsm、ptp slave的处理函数是designed_slave_fsm等等。
![](https://img.haomeiwen.com/i15107051/e1c6bd841b2bbb1b.png)
4.3 event dipatch
按照clock的角色看,不同角色的时间派发方式有差异。如边界时钟的处理函数是bc_dispatch/bc_event等。普通时钟的处理函数与边界时钟相同。
![](https://img.haomeiwen.com/i15107051/de76a6d13ff49880.png)
5 transport
transport
负责收发PTP消息。
基于PTP协议基于哪一层,有不同类型的transport。如:
-
IEEE_802_3
基于mac层,如下raw是对应的实现。 -
UDP_IPV4
基于UDP协议,如下udp是对应的实现。 -
UDP_IPV6
基于UDP IPV6协议,如下udp6是对应的实现。
![](https://img.haomeiwen.com/i15107051/561684d3d4edfea9.png)
6 tsproc
tsproc
处理PTP消息。
- 它从PTP消息获取时间戳,计算
ptp slave
与ptp master
的时间差,设置ptp slave的时间。这个时间差只是保存在ptp4l进程,设置系统时间需要其他程序去做,如phc2sys
。 - 成员t1、t2、t3、t4是当前这个序列的PTP消息中的4个时间戳。
- 成员mode是计算时间差的方式。
-
TSPROC_RAW
只考虑当前序列的PTP消息。 -
TSPROC_FILTER
则会参考最近几个序列的PTP消息。成员filtered_delay 保存前几个序列消息的数据。
-
-
TSPROC_FILTER
模式下,delay_filter计算时间差。支持两种类型的filter:mave_filter
和mmedian_filter
。
![](https://img.haomeiwen.com/i15107051/5fb1b8e15e37aa8b.png)
7 clock_create()
clock_create()创建时钟节点。
- 将这个节点指向全局变量the_clock。这是ptp4l实例默认的时钟实例
struct clock the_clock;
- 读取config,配置clock的参数。
- 调用config_harmonize_onestep()检查用户配置参数是否一致。比如只有软时钟不能使用one_step方式等。
- 调用clock_required_modes(),根据用户设置,得到clock需要支持的属性,如SOF_TIMESTAMPING_SOFTWARE、 SOF_TIMESTAMPING_RAW_HARDWARE等。
- 遍历config中的所有interface,
- 调用interface_get_ifinfo()得到interface的速度属性
- 调用interface_get_tsinfo()得到interface的时钟属性。然后调用interface_tsmodes_supported(),检查interface是否满足
clock_required_modes()的要求,如果不满足,返回失败。
- 调用interface_phc_index()得到interface的phc设备索引phc_index。这里是
0
,所以phc_device是/dev/ptp0
。 - 调用generate_clock_identity(),产生clock的indentity,这个用于在PTP通信中唯一标识clock。
![](https://img.haomeiwen.com/i15107051/21c97e177325342c.png)
- 调用phc_open()打开phc设备
/dev/ptp0
. - 调用phc_max_adj()/clockadj_get_freq(),得到时钟最大调整范围。
- 根据用户配置,设置clock->dscmp = dscmp。这个函数比较两个clock,根据priority/quality.clockCalss/qulity.clockAccuracy决定哪个clock更好。这里指定的dscmp是一个全局函数。
- 调用tsproc_create(),创建ts_proc实例,其中根据配置创建相应的filter,保存在tsproc->filter。
![](https://img.haomeiwen.com/i15107051/86364af4dccdbff4.png)
- 遍历clock的所有interface,调用clock_add_port(),创建port实例。
- 调用clock_resize_pollfd(),根据计算的fd数,调整clock.fdarray[]数组大小。
- 调用port_open()创建port实例,并将它加入队列clock->ports中。
- 调用clock_fda_changed()设置标志clock->pollfd_valid。后面clock_check_pollfd()会根据这个标志,判断是否需要重新配置fd监听列表clock->pollfd。
- 调用port_dispatch(),处理EV_INITIALIZE事件,初始化port。
![](https://img.haomeiwen.com/i15107051/0404b6a8ea283b93.png)
8 port_open()
port_open()创建端口。
- malloc()创建port实例。
- 根据clock角色指定port的事件处理函数。这里是ordinary_clock,所以指定bc_dispatch/bc_event。
bc
指boundary clock
。oridinay_clock的处理与它的相同。 - 调用transport_create()。
- 根据用户指定的transport_type,创建transport实例。这里是IEEE_802_3协议,所以调用raw_transport_create()创建raw实例。raw的处理函数是raw_open()/raw_send()/raw_recv()。
- 根据clock的主从关系,如
BMCA
/master_only
/slave_only
设置项,指定port的处理函数。- 如果指定
BMCA
=NOOP
,master_only
=true
,则是designated_master_fsm
, - 如果指定
BMCA
=NOOP
,slave_only
=true
,则是designated_slave_fsm
。
- 如果指定
- 调用port_clear_fda()清除port->fdarray。
- 调用timerfd_create()创建port->fault_fd。
![](https://img.haomeiwen.com/i15107051/9576ca521ab97c5d.png)
9 bc_dispatch()
bc_dispatch() 处理事件驱动port的状态机。
- 调用port_state_update()。
- 初始化时处理事件
EV_INITIALIZE
。如果是ptp master,则designated_master_fsm()会将状态转换成PS_MASTER
;如果是ptp slave,则designated_slave_fsm()会将状态转换成PS_SLAVE
。 - 调用port_initialize()初始化port。
- 如果有状态变化发生,则调用port_show_transition()打印状态转换消息,如果没有,直接返回
- 初始化时处理事件
- 如果没有状态变化,则根据delayMechanism选项设置(
DM_PSP
/DS_E2E
),调用相应的处理函数,这里为delayMechanism =DM_P2P
,则调用port_p2p_transition()。它的主要工作是设置port各个计时器的超时时间。
![](https://img.haomeiwen.com/i15107051/ca7814b77e89f2fc.png)
10 port_initialize()
port_initialize()初始化port实例。
- 读取config,初始化port的参数。
- 调用timerfd_create(),创建所有的计时器fd。
- 调用transport_open(),其中调用transport->open,这里是raw_open()。
- 调用rtnl_open()创建port的
RTNL socket
,这个socket用于查询port的连接状态。然后用rtnl_link_query()来查询。 - 调用clock_fda_changed()设置port->pollfd_valid标志。后面clock_check_pollfd()会根据这个标志,判断是否重新配置clock的fd监听列表clock->pollfd。
![](https://img.haomeiwen.com/i15107051/6c09046a46f2491d.png)
11 raw_open()
raw_open()创建PTP通信的socket。
- 调用mac_to_addr(),将PTP目标地址从字符串转换成MAC地址。
- 调用sk_interface_macaddr(),得到本地MAC地址。
- 调用open_socket()创建两个socket,这是用于PTP通信的socket。
- 调用sk_timstamping_init()初始化socket的时间戳选项,如
SOF_TIMESTAMPING_BIND_PHC
/SOF_TIMESTAMPING_SOFTWARE
/SOF_TIMESTAMPING_RAW_HARDWARE
等。 - 将两个socket保存到port的fd数组中。
![](https://img.haomeiwen.com/i15107051/b1d23d653ce3660f.png)
12 open_socket()
open_socket()创建PTP socket,并配置。
- 调用socket()创建socket实例,并调用set_socket_opt()设置选项,如
SO_PRIORITY
。 - 调用raw_configure()进一步配置其他选项,如加入组播组。
![](https://img.haomeiwen.com/i15107051/16905a0b5399e5e0.png)
13 clock_poll()
clock_poll()监听port的fd的事件,并处理。
- 调用clock_check_pollfd()。如果clock->pollfd_valid = false, 则遍历clock所属ports,用port的所有fd填充clock->pollfd。
- 调用poll监听clock->pollfd数组,信号到达时(定时器超时或收到PTP消息),进行处理,否则返回。
- 遍历ports,
- 遍历port的所有fd,调用port_state()得到port状态,调用port_event()处理事件,这里实际上调用bc_event();
- 如果是EV_STATE_DECISION_EVENT,则设置c->sde = 1,这种情况下,后面调用handle_state_decision_event()进行处理。
- 调用port_dispatch()处理事件,这里实际上会调用bc_dispatch()。
![](https://img.haomeiwen.com/i15107051/7ecb16768556fae1.png)
14 handle_state_decision_event()
handle_state_desision_event()在收到EV_STATE_DECISON_EVENT
时,决定使用哪个port。
不同的port接入各自的网络,每个网络上可以比较得到一个最好的clock。也就是每个port有一个最好的clock。而clock的最好的clock,则是所有port的clock中最好的一个。
- 遍历clock的所有port,调用port_compute_best(),计算它的外接clock节点中哪个最好。如果本地变量best =
NULL
,则保存到best中,否则与best比较,如果比best更好,则替换best。同时将它的identity则保存到本地变量best_id中。 - 比较best_id与clock->best_id不同,则重置clock的相关参数,如clock_freq、tsproc等。
- 比较best_id与clock自己的dds.clockIdentity,如果相同,则打印”select local clock xxx as best master”,这说明自己就是网络上最好的时钟;如果不同,则打印”selected best master clock xxx”,这意味最好的时钟是别人。
- 遍历ports,调用bmc_state_decision(),计算这个port的状态,如PS_GRAND_MASTER、PS_MASTER、PS_SLAVE等。调用clock_update_grand_master()/clock_update_slave()更新clock的parentDS状态。
- 调用port_disptach()进行其他处理。对于
EV_STATE_DECISION_EVENT
事件,其实没有什么其他处理了。
![](https://img.haomeiwen.com/i15107051/d841d763c49a577b.png)
15 bc_event() + FD_RTNL
FD_RTNL
消息是网络断开和连接造成的。这里调用rtnl_link_status()处理。
- 调用recvmsg()接收RTNL消息,并调用rtnl_attr_parse()和rtnl_linkinfo_parse()解析,得到port的连接状态,并调用port_link_status()。后者
- 调用interface_get_ifinfo()和interface_get_tsinfo()得到port信息。
- 调用port_change_phc(),更改phc设备
- 调用clock_set_sde(),告知handle_state_decision_event(),需要重新决定最好的clock。
![](https://img.haomeiwen.com/i15107051/8875d6c0080a8497.png)
16 bc_event() FD_MANNO_TIMER (PTP MASTER端)
FD_MANNO_TIMER
定时器超时,be_event()处理,发送announce_msg消息。这个消息是PTP MASTER
对外公告自己的clock参数,以便与其他节点协商最佳clock。
- 调用port_tx_announce()。其中,创建announce_msg消息,设置domainNumber/sourcePortIdentity/sequence_id属性,调用port_prepare_and_send()发送。
![](https://img.haomeiwen.com/i15107051/30b6462095558e55.png)
17 bc_event() ANNOUNCE (PTP SLAVE端)
收到announce_msg消息,be_event()处理。
- 调用process_announce(),它又调用update_current_master()。其中,
- 将发出这个消息的foreign clock不是port_>best,则调用add_foreigin_master(),将它加入port->foreigin_master。否则,
- 将消息加入fc->messages中。调用announce_compare()对fc->messages中的消息进行比较,如果新消息的foreigin_clock导致外部最佳时钟变化,则返回1。
- process_announce()返回1时,bc_event()会返回
EV_STATE_DECISiON_DEVENT
。如前所述,这会导致调用handle_state_decision_event(),重新选择最佳外部时钟。
![](https://img.haomeiwen.com/i15107051/9189179719f2a017.png)
18 bc_event() FD_SYNC_TX_TIMER (PTP MASTER端)
FD_MANNO_TIMER
定时器超时,be_event()处理,发送sync_msg和follow_up_msg消息。通过sync_msg消息,PTP MASTER
发送自己的时间基准数据,follow_up_msg则可以让这个数据更精准。
- 调用port_tx_sync()。其中,
- 创建sysnc_msg消息,调用port_prepare_and_send()发送。
- 创建follow_up_msg消息,调用port_prepare_and_send()发送。
![](https://img.haomeiwen.com/i15107051/24be96eae2eff539.png)
19 bc_event() SYNC (PTP SLAVE端)
收到SYNC
消息,be_event()处理。
- 调用process_sync()。其中,
- 调用port_syfufsm()处理,计算与PTP MASTER的时间差。
![](https://img.haomeiwen.com/i15107051/7b9fb24afd908808.png)
20 bc_event() FD_SYNC_TX_TIMER (PTP SLAVE端)
收到FOLLOW_UP
消息,be_event()处理。
- 调用process_follow_up()。其中,
- 调用port_syfufsm()处理,精确计算与PTP MASTER的时间差。port_syfufsm()的时间参数来自sync_msg和follow_up_msg。
![](https://img.haomeiwen.com/i15107051/3de362c319d2d6db.png)
21 port_syfufsm()
port_syfufsm()根据时间戳数据计算时间差,并将PTP SLAVE
与PTP MASTER
调整到一致(不是很精确的一致)。
- 它调用port_synchronze(),后者又调用clock_synchronize()。其中,
- 调用servo_sample(),从累积数据中得到时间差
- 调用clockadj_set_freq()和clockadj_step(),调整本地时间。
![](https://img.haomeiwen.com/i15107051/afc009c079dd545e.png)
22 PTP消息序列
对于gPTP,每个PTP消息序列应该有3个消息:PDelay_Req
、PDelay_Resp
、PDelay_Resp_Follow_Up
。
![](https://img.haomeiwen.com/i15107051/eb542c6555562c5e.png)
步骤如下。
- PTP Slave(这里是官方Orin盒子)发送
PDelay_Req
消息。源MAC地址是ptp slave的网卡地址。SourcePortID
+sequenceId
唯一标识这个消息序列。
![](https://img.haomeiwen.com/i15107051/a05c07e73dc2486e.png)
- PTP Master(这里是H_Orin A面)发送
PDelay_Resp
消息。源MAC地址是master的网卡地址。这里的SourcePortID
+sequenceId
与PDelay_Req
消息的相同,所以是同一个消息序列。它携带时间戳T2。
![](https://img.haomeiwen.com/i15107051/891507012dbe12eb.png)
- PTP Master(这里是H orin A面)发送
PDelay_Resp_Follow_Up
消息。源MAC地址是master的网卡地址。这里的SourcePortID
+sequenceId
与PDelay_Req
消息的相同,所以是同一个消息序列。它携带时间戳T3。
![](https://img.haomeiwen.com/i15107051/00877146256ff328.png)
在PTP slave端,port的成员peer_pelday_req
/peer_delay_resp
/peer_delay_fup
保存这个ptp消息序列的3个消息。后面依次说明这几个消息的处理。
23 bc_event() FD+DELAY_TIMER (PTP SLAVE端)
PTP SLAVE端的定时器超时触发,bc_event()处理。
- 调用port_delay_request(),发送
pdelay_req_msg
消息,其中,- 创建
pdelay_req_msg消息,设置消息的
domainaNumber/
sourcePortIdentity/
sequenceId`值,然后调用port_prepare_and_send()发送。 - 这里还设置消息的
msg.hwts.type
,告诉网口给消息打时间戳,也就是T1。 -
pdelay_req_msg
消息同时保存在port.peer_pdelay_req。
- 创建
![](https://img.haomeiwen.com/i15107051/633dadb41244405e.png)
24 bc_event() DELAY_REQ (PTP MASTER端)
收到PTP Slave发送的pdelay_req_msg消息,bc_event()处理。
- 调用process_pdelay_req(),其中,
- 创建
pdelay_resp_msg
消息,设置domainNumber
/requestingPortIdentity
/sequenceId
值,然后发送。这里还设置了网卡接收pdelay_req_msg
消息打的时间戳,也就是T2。 - 创建
pdelay_resp_fup_msg
消息,设置domainNumber
/requestingPortIdentity
/sequenceId
值,然后发送。这里还设置了网口发送pdelay_resp_msg
消息打的时间戳,也就是T3。
- 创建
![](https://img.haomeiwen.com/i15107051/d4fbe869e0e8e93d.png)
25 bc_event() PDELAY_RESP (PTP SLAVE端)
收到PTP MASTER发送的pdelay_resp_msg消息,bc_event()处理。
- 将消息保存在port.peer_delay_resp。
- 调用process_pdelay_resp()。对于two_step模式,因为4个时间戳还不全,现在还不会做计算。
![](https://img.haomeiwen.com/i15107051/4c0a5d1039c03e51.png)
26.bc_event() PDELAY_RESP_FOLLOW_UP (PTP SLAVE端)
收到pdelay_resp_fup_msg消息,bc_event()处理。
- 将消息保存到port.peer_delay_fup
- 调用process_pdelay_resp()。现在有了所有4个时间戳,可以计算与PTP Master的时间差了。
![](https://img.haomeiwen.com/i15107051/2d7d8dacbeed4526.png)
27 port_peer_delay()
port_peer_delay根据4个时间戳计算时间差。
- 调用pid_eq()检查是否portIdentity是否匹配
- 计算t4 - t1
- 计算 t3 - t2
- 调用tsproc_update_delay()计算时间差。调用clock_peer_delay()更新本地时间戳。
![](https://img.haomeiwen.com/i15107051/f8259f0cadde8159.png)
网友评论