学习IEEE 1588-2008标准文档的一种方法是找一个PTP开源项目,通过阅读源码的方式加深对标准的理解。linuxptp是Linux系统上最流行的PTP开源实现,本文对linuxptp进行代码分析,并给出代码和IEEE 1588-2008文档的映射。
1、linuxptp项目简介
linuxptp提供了以下工具实现时钟同步:
- ptp4l:遵循IEEE 1588-2008标准文档规范,实现了BC(Boundary Clock)、OC(Ordinary Clock)和TC(Transparent Clock);
- phc2sys:用于同步当前设备上的两个时钟,譬如让System Clock与PHC (PTP Hardware Clock)保持同步;
- pcm:在ptp4l运行期间对其进行配置。
ptp4l支持SW(软件时间戳)与HW(硬件时间戳)。如果采用HW,ptp4l实现PHC与BMC(Best Master Clock)的同步,而phc2sys实现System Clock与PHC的同步;如果采用SW,ptp4l实现System Clock与BMC的同步,不需要运行phc2sys。
2、ptp4l
2.1 数据结构
//clock.c
struct clock {
struct defaultDS dds; //描述当前clock的属性
int time_source; //grandmaster时钟的时间来源(如GPS),该值仅用于信息展示,不参于BMC算法计算,参考《IEEE 1588-2008V2》7.6.2.6小节
struct currentDS cur; //时钟同步相关的属性
struct parent_ds dad; //parent与grandmaster时钟的属性
struct timePropertiesDS tds; //timescale的属性
LIST_HEAD(ports_head, port) ports; //port列表
}
//ddt.h
struct ClockQuality {
UInteger8 clockClass; //如果值<=127,那么该clock不能成为slave,参考《IEEE 1588-2008V2》7.6.2.4小节
Enumeration8 clockAccuracy; //标记时钟的精度,用于执行BMC算法,参考《IEEE 1588-2008V2》7.6.2.5小节
UInteger16 offsetScaledLogVariance; //时钟的稳定性,用于执行BMC算法,参考《IEEE 1588-2008V2》7.6.3.2小节
};
//ds.h
struct defaultDS {
UInteger8 flags; //twoStepFlag和slaveOnly的标记位
UInteger16 numberPorts; //当前设备的port个数,参考《IEEE 1588-2008V2》7.6.2.7小节
UInteger8 priority1; //用于执行BMC算法,数值越小优先级越高,参考《IEEE 1588-2008V2》7.6.2.2小节
struct ClockQuality clockQuality;
UInteger8 priority2; //类似priority1,参考《IEEE 1588-2008V2》7.6.2.3小节
struct ClockIdentity clockIdentity; //时钟id,参考《IEEE 1588-2008V2》7.6.2.1小节
UInteger8 domainNumber; //ptp域id,参考《IEEE 1588-2008V2》7.1小节
};
struct currentDS {
UInteger16 stepsRemoved; //当前时钟到gransmaster时钟的距离,参考《IEEE 1588-2008V2》8.2.2.2小节
TimeInterval offsetFromMaster; //当前时钟与master时钟的时间差值,参考《IEEE 1588-2008V2》8.2.2.3小节
TimeInterval meanPathDelay; //当前时钟到master时钟的传播耗时,meanPathDelay的计算可参考《IEEE 1588-2008V2》11.3与11.4小节
}
struct parentDs {
struct PortIdentity parentPortIdentity;
UInteger8 parentStats; //TRUE代表observedParentOffsetScaledLogVariance和observedParentClockPhaseChangeRate是有效值,参考《IEEE 1588-2008V2》7.6.4.2小节
UInteger16 observedParentOffsetScaledLogVariance; //对parent时钟的精度的估计,参考《IEEE 1588-2008V2》7.6.4.3小节
Integer32 observedParentClockPhaseChangeRate; //对parent时钟的相位变化频率的估计,参考《IEEE 1588-2008V2》7.6.4.4小节
UInteger8 grandmasterPriority1; //grandmaster时钟的priority1属性,参考《IEEE 1588-2008V2》8.2.3.8小节
struct ClockQuality grandmasterClockQuality; //grandmaster时钟的ClockQuality属性,参考《IEEE 1588-2008V2》8.2.3.7小节
UInteger8 grandmasterPriority2; //grandmaster时钟的priority2属性,参考《IEEE 1588-2008V2》8.2.3.9小节
struct ClockIdentity grandmasterIdentity; //grandmaster时钟的id,参考《IEEE 1588-2008V2》8.2.3.6小节
};
struct timePropertiesDS {
Integer16 currentUtcOffset; //TAI与UTC时间的差值,单位为秒,参考《IEEE 1588-2008V2》8.2.4.2小节
UInteger8 flags; //currentUtcOffsetValid、leap59、leap61、timeTraceable、frequencyTraceable和ptpTimescale标记位,参考《IEEE 1588-2008V2》8.2.4.3-8.2.4.8小节
Enumeration8 timeSource; //grandmaster时钟的时间来源(如GPS),参考《IEEE 1588-2008V2》8.2.4.9小节
} ;
struct portDS {
struct PortIdentity portIdentity;
Enumeration8 portState; //port的当前状态,可选值见下面Table 1
Integer8 logMinDelayReqInterval; //DelayReq的超时间隔的最小值,参考《IEEE 1588-2008V2》8.2.5.3.2小节
TimeInterval peerMeanPathDelay; //delayMechanism为P2P模式时,当前port到相邻port的传播耗时;如果delayMechanism为E2E模式,该值应该为0,参考《IEEE 1588-2008V2》8.2.5.3.3小节
Integer8 logAnnounceInterval; //发送Announce消息的间隔,参考《IEEE 1588-2008V2》8.2.5.4.1小节
UInteger8 announceReceiptTimeout; //接收Announce消息超时时长,参考《IEEE 1588-2008V2》8.2.5.4.2小节
Integer8 logSyncInterval; //发送Sync消息的间隔,参考《IEEE 1588-2008V2》8.2.5.4.1小节
Enumeration8 delayMechanism; //测量传播耗时的机制,可选值为E2E和P2P,参考《IEEE 1588-2008V2》8.2.5.4.4小节
Integer8 logMinPdelayReqInterval; //接收PdelayReq消息超时时长,参考《IEEE 1588-2008V2》8.2.5.4.2小节8.2.5.4.5小节
UInteger8 versionNumber; //PTP版本
} ;
2.2 工作流程
初始化本地时钟流程:
struct clock *clock_create()
{
if (phc_index >= 0)
c->clkid = phc_open(phc); //打开phc API,用于操作PTP硬件时钟,参考上面图1
/*servo根据offsetFromMaster调整本地时钟的频率。
由于延迟波动,计算出来的offsetFromMaster与实际值会有偏差,servo算法用于减少这种偏差,默认servo算法为PI Controller*/
c->servo = servo_create();
c->tsproc = tsproc_create(); //创建filter对offset进行平滑,可选filter有移动平均滤波器、移动中值滤波器等,参考上面图1
STAILQ_FOREACH(iface, &config->interfaces, list)
clock_add_port(c, phc_device, phc_index, timestamping, iface); //创建ports
LIST_FOREACH(p, &c->ports, list)
port_dispatch(p, EV_INITIALIZE); //初始化port
}
void port_dispatch(struct port *p)
{
p->dispatch(p); //如果是BC(Boundary Clock)或OC(Ordinary Clock),执行bc_dispatch
}
void bc_dispatch(struct port *p)
{
port_state_update(p); //事件导致state machine更新状态,见下面图3
port_e2e_transition(p); //根据当前状态,更新定时器,参考《IEEE 1588-2008V2》7.7小节
}
int port_state_update(struct port *p)
{
if (PS_INITIALIZING == next)
port_initialize(p);
}
int port_initialize(struct port *p)
{
transport_open(); //如果是udp port,执行udp_open
port_set_announce_tmo(); //设置接收announce message超时定时器,参考《IEEE 1588-2008V2》7.7.3.1小节
}
int udp_open()
{
efd = open_socket(name, mcast_addr, EVENT_PORT, ttl); //打开event interface句柄,见上图1
gfd = open_socket(name, mcast_addr, GENERAL_PORT, ttl); //打开general interface句柄,见上图1
sk_timestamping_init(efd, interface_label(iface), ts_type, TRANS_UDP_IPV4,
interface_get_vclock(iface)); //通知内核对event message要打时间戳
/*设置event message和general message的dscp优先级,《IEEE 1588-2008V2》并未规定dscp,但AES67与RAVENNA标准对此有要求*/
sk_set_priority(efd, AF_INET, event_dscp);
sk_set_priority(gfd, AF_INET, general_dscp);
}
处理事件流程:
int clock_poll(struct clock *c)
{
clock_check_pollfd(c); //更新c->pollfd队列,将各个port的socket和timer文件描述符都加入到队列中
poll(c->pollfd);
LIST_FOREACH(p, &c->ports, list) {
event = port_event(p); //处理事件
port_dispatch(p, event); //根据state machine更新状态,更新定时器
}
handle_state_decision_event(c); //执行bmc计算
}
enum fsm_event port_event(struct port *p)
{
return p->event(p); //如果是BC(Boundary Clock)或OC(Ordinary Clock),执行bc_event
}
enum fsm_event bc_event(struct port *p)
{
switch (fd_index) {
case FD_ANNOUNCE_TIMER:
case FD_SYNC_RX_TIMER:
//定时器超时,失去同步
fc_clear(p->best); //移除master的announce message
port_set_announce_tmo(p); //重置接收announce message超时定时器,参考《IEEE 1588-2008V2》7.7.3.1小节
delay_req_prune(p); //移除过期的delay req message
return EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES; //事件导致state machine更新状态,见下面图3
case FD_DELAY_TIMER:
port_set_delay_tmo(p); //更新发送Delay_Req message或Pdelay_Req message的定时器,参考《IEEE 1588-2008V2》7.7.2.4小节和7.7.2.5小节
delay_req_prune(p); //移除过期的delay req message
port_delay_request(p); //如果是e2e机制,发送Delay_Req message;如果是p2p机制,发送Pdelay_Req message
return EV_NONE;
case FD_QUALIFICATION_TIMER:
return EV_QUALIFICATION_TIMEOUT_EXPIRES; //在PRE_MASTER等待了足够的时间,状态变为MASTER,见下面图3
case FD_MANNO_TIMER:
port_set_manno_tmo(p); //更新发送Announce message的定时器,参考《IEEE 1588-2008V2》7.7.2.2小节
port_tx_announce(p); //发送Announce message
return EV_NONE;
case FD_SYNC_TX_TIMER:
port_set_sync_tx_tmo(p); //更新发送Sync message的定时器,参考《IEEE 1588-2008V2》7.7.2.3小节
port_tx_sync(p); //发送Sync message
return EV_NONE;
case FD_RTNL:
rtnl_link_status(); //监控网卡状态变化
if (网卡状态变更)
return EV_FAULT_DETECTED;
else
return EV_NONE;
}
transport_recv(p->trp, fd, msg); //接收message,如果是event message,记录接收时间戳
switch (msg_type(msg)) {
case SYNC:
process_sync(p, msg); //处理Sync message,处理流程见下面图4,参考《IEEE 1588-2008V2》9.5.4小节
break;
case DELAY_REQ:
process_delay_req(p, msg); //处理Delay_Req message,处理流程见下面图5,参考《IEEE 1588-2008V2》9.5.6小节
break;
case PDELAY_REQ:
process_pdelay_req(p, msg); //处理Pdelay_Req message,发送Pdelay_Resp message和Pdelay_Resp_Follow_Up message,参考《IEEE 1588-2008V2》9.5.14和9.5.15小节
break;
case PDELAY_RESP:
process_pdelay_resp(p, msg); //处理Pdelay_Resp message,如果是one-step模式,计算链路传播耗时,参考《IEEE 1588-2008V2》11.4小节
break;
case FOLLOW_UP:
process_follow_up(p, msg); //处理Follow_Up message,处理流程见下面图6,参考《IEEE 1588-2008V2》9.5.5小节
break;
case DELAY_RESP:
process_delay_resp(p, msg); //处理Delay_Resp message,处理流程见下面图7,参考《IEEE 1588-2008V2》9.5.7小节
break;
case PDELAY_RESP_FOLLOW_UP:
process_pdelay_resp_fup(p, msg); //处理Pdelay_Resp_Follow_Up message,计算链路传播耗时,参考《IEEE 1588-2008V2》11.4小节
break;
case ANNOUNCE:
process_announce(p, msg); //处理Announce message,处理流程见下面图8,参考《IEEE 1588-2008V2》9.5.3小节
break;
case SIGNALING:
process_signaling(p, msg); //处理Signaling message,参考《IEEE 1588-2008V2》16.1小节
break;
case MANAGEMENT:
clock_manage(p->clock, p, msg); //处理Management message,参考《IEEE 1588-2008V2》15.3小节
break;
}
}
BMC计算流程:
//BMC计算流程概述见《IEEE 1588-2008V2》9.3.2.2小节
void handle_state_decision_event(struct clock *c)
{
LIST_FOREACH(piter, &c->ports, list) {
fc = port_compute_best(piter); //port根据收到的Announce message选出最优的foreign clock
if (c->dscmp(&fc->dataset, &best->dataset) > 0) //比较各个port的最优foreign clock,选出全局最优foreign clock,dscmp对比流程见下面图9和图10,参考《IEEE 1588-2008V2》9.3.4小节
best = fc;
}
c->best = best;
LIST_FOREACH(piter, &c->ports, list)
bmc_state_decision(c, piter, c->dscmp); //port根据新选出来的best clock更新各自的状态,更新逻辑见下面图11,参考《IEEE 1588-2008V2》9.3.3小节
}