ptp4l v4.2源代码分析

0 前言

ptp4llinuxptp-4.2的一部分。

1 main()

ptp4l的main()主要做三件事:

  • 读取配置。
    • 调用功能config_create()创建config实例,并从全局变量config_tab[]数组读取默认配置值。
    • 调用config_create_interface(),从命令行选项解析网络接口。
    • 调用clock_read(),从配置文件读取用户配置值。
  • 调用clock_create()创建clock实例,以及其下的porttransporttsproc实例等。初始化每个port的定时器fdsocket fd
  • 在循环中调用clock_poll(),监听所有fd的事件,如定时器超时、ptp消息到达等,派发处理。

2 config

config保存用户配置项。配置项可以来自如下三个源:

  • ptp4l的默认配置项、
  • ptp4l配置文件、
  • 启动ptp4l的命令行选项。

2.1 config.interfaces

ptp4l可以同时指定多个网卡授时,config.interfaces保存网卡列表。这是一个STAILQ,其定义来自于<sys/queue.h>interfaces可以通过命令行或配置文件增加。

链表之STAILQ

config_create_interface()创建interface实例。

interface的成员如下。

  • 成员list用于将interface连接成一个STAILQ
  • 成员name是网卡的名字,ts_label一般与name相同。
  • 成员ts_info是网卡的时间戳支持特性,如timestampingphc_index等。
  • 成员if_info是网卡的支持速率信息,如speed等。

RTNL/Route Netlink

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),   
  ...
}

在config_read()中,解析配置文件时,从中读出的参数值将覆盖htab中的值。

config_read()的第一个参数是配置文件名,这是在命令行选项中指定的。

2.3 hash与node

hash实现了一个hash表。

  • 成员table是一个长度为200的数组,数组元素是node链表。

nodehash表中的节点。

  • 成员datakey分别是数据和它的关键字。

2.4 config.config_item

config_item是全局变量config_tab元素类型。

  • 成员label 为参数名
  • config_type 为参数类型,如int/double/enum/string等等。
    • config_typeint/double类型时,成员valminmax保存默认值,最小、最大值;
    • config_typeenum类型时,tab保存值;
    • config_typestring类型时,成员val保存值。
  • flags是参数标志。比如是全局有效,还是只针对指定端口。

2.5 config.opts

optsstruct option数组。他用于调用getopt_long()函数,从命令行选项获取参数。

参数解析getopt_long()

3 clock

clock定义时钟节点。

  • 成员type按照角色定义clock类型,如普通节点、边界节点、PTP节点等。
  • 成员timestamping按照打时间戳方式定义类型,如软时钟、硬时钟等。
  • 成员ports是clock下属的ports实例的列表。成员nports是ports实例数。
  • 成员pollfd数组,保存所有fd,用于监听。其中有所有port的fd,加上另外两个UDS port的fd。
  • 如果pollfd数组准备好,则成员pollfd_validtrue,否则为false。当port的fd有变化时,这个标志可以告知监听函数,应该重建pollfd数组了。

4 port

port定义网络接口,与config.interfaces是对应的。

  • 成员list将port实例链接在一个列表中。
  • 成员name是网口名,比如eth0
  • clockport关联的clock实例。
  • 成员trp是port的transport实例,负责收发网络包。
  • 成员timestamping为打时间戳的方式,如TS_SOFTWARETS_HARDWARE等。
  • 成员fda的类型是fdarrayfdarray包含一个fd数组,数组长度是一个port需要创建的fd数。
    • FD_PORT值是fd数组中的索引。其中FD_EVENTFD_GENERAL对应收发网络包的socket;
    • FD_DELAY_TIMER~FD_UNICAST_SRV_TIMER对应定时器的fd
    • FD_RTNL对应获取端口信息的socket
  • 成员phc_index是phc设备索引。
  • 成员stateport的当前状态。
  • 成员peer_pelday_req/peer_delay_resp/peer_delay_fup保存一个ptp消息序列。后面还会详细说明。

4.1 port_state

port状态state的变换是一个状态机,由fsm_event事件驱动。

port_state定义port的状态。

4.2 state_fsm

ptp master和ptp slave的状态转换的差异由port_fsm区分。比如,ptp master 的处理函数是designated_master_fsm、ptp slave的处理函数是designed_slave_fsm等等。

4.3 event dipatch

按照clock的角色看,不同角色的时间派发方式有差异。如边界时钟的处理函数是bc_dispatch/bc_event等。普通时钟的处理函数与边界时钟相同。

5 transport

transport负责收发PTP消息。
基于PTP协议基于哪一层,有不同类型的transport。如:

  • IEEE_802_3 基于mac层,如下raw是对应的实现。
  • UDP_IPV4基于UDP协议,如下udp是对应的实现。
  • UDP_IPV6 基于UDP IPV6协议,如下udp6是对应的实现。

6 tsproc

tsproc处理PTP消息。

  • 它从PTP消息获取时间戳,计算ptp slaveptp 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_filtermmedian_filter

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。
  • 调用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。
  • 遍历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。

8 port_open()

port_open()创建端口。

  • malloc()创建port实例。
  • 根据clock角色指定port的事件处理函数。这里是ordinary_clock,所以指定bc_dispatch/bc_event。bcboundary 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=NOOPmaster_only=true,则是designated_master_fsm
    • 如果指定BMCA=NOOPslave_only=true,则是designated_slave_fsm
  • 调用port_clear_fda()清除port->fdarray。
  • 调用timerfd_create()创建port->fault_fd。

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各个计时器的超时时间。

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。

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数组中。

12 open_socket()

open_socket()创建PTP socket,并配置。

  • 调用socket()创建socket实例,并调用set_socket_opt()设置选项,如SO_PRIORITY
  • 调用raw_configure()进一步配置其他选项,如加入组播组。

socket套接字网卡绑定和优先级调整

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()。

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事件,其实没有什么其他处理了。

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。

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()发送。

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(),重新选择最佳外部时钟。

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()发送。

19 bc_event() SYNC (PTP SLAVE端)

收到SYNC消息,be_event()处理。

  • 调用process_sync()。其中,
    • 调用port_syfufsm()处理,计算与PTP MASTER的时间差。

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。

21 port_syfufsm()

port_syfufsm()根据时间戳数据计算时间差,并将PTP SLAVEPTP MASTER调整到一致(不是很精确的一致)。

  • 它调用port_synchronze(),后者又调用clock_synchronize()。其中,
    • 调用servo_sample(),从累积数据中得到时间差
    • 调用clockadj_set_freq()和clockadj_step(),调整本地时间。

22 PTP消息序列

对于gPTP,每个PTP消息序列应该有3个消息:PDelay_ReqPDelay_RespPDelay_Resp_Follow_Up

步骤如下。

  • PTP Slave(这里是官方Orin盒子)发送PDelay_Req消息。源MAC地址是ptp slave的网卡地址。SourcePortID + sequenceId唯一标识这个消息序列。
  • PTP Master(这里是H_Orin A面)发送PDelay_Resp消息。源MAC地址是master的网卡地址。这里的SourcePortID+sequenceIdPDelay_Req消息的相同,所以是同一个消息序列。它携带时间戳T2。
  • PTP Master(这里是H orin A面)发送PDelay_Resp_Follow_Up消息。源MAC地址是master的网卡地址。这里的SourcePortID+sequenceIdPDelay_Req消息的相同,所以是同一个消息序列。它携带时间戳T3。

在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。

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。

25 bc_event() PDELAY_RESP (PTP SLAVE端)

收到PTP MASTER发送的pdelay_resp_msg消息,bc_event()处理。

  • 将消息保存在port.peer_delay_resp。
  • 调用process_pdelay_resp()。对于two_step模式,因为4个时间戳还不全,现在还不会做计算。

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的时间差了。

27 port_peer_delay()

port_peer_delay根据4个时间戳计算时间差。

  • 调用pid_eq()检查是否portIdentity是否匹配
  • 计算t4 - t1
  • 计算 t3 - t2
  • 调用tsproc_update_delay()计算时间差。调用clock_peer_delay()更新本地时间戳。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容