PTP 时间同步排查:状态正常,业务时间为什么仍不可用

概述: 设备显示“PTP 已锁定”,采样时间戳却仍有偏差;抓包能看到 Sync 和 Follow_Up,业务程序却报告时间失联。这两种现象都说明:同步状态 和 时间可用性 不是同一个判断。本文先交代一台示例设备的授时链路,再用三个现场案例说明怎样分段排查,最后讨论状态和跨进程状态管理应该怎么改。本文把应用层状态归成“正常、保持、失联”三类;你的设备可能叫 SYNC_OK、TIME_VALID、HOLDOVER,名称不同,判断链路是一样的。

这套示例设备是一台嵌入式传感器,内部至少运行三个进程:PTP 进程、时间服务进程、业务程序。PTP 进程负责收报文和估计偏差;时间服务进程负责把时间发给业务程序;业务程序用时间给采样数据打时间戳。三个进程之间的状态并不天然一致,现场问题也往往出在这里。

先统一几个叫法,后面不再反复解释:

  • PTP 进程:指 ptp4l、ptpd2 这类负责收 PTP 报文、算偏差的后台程序。
  • 时间服务进程:指把时间发给业务程序的那个进程。
  • 业务程序:指最终用时间给采样数据打戳的程序。
  • 完整状态数据:指一次读走的一整份状态,避免读到一半被别的进程改掉。
  • 时钟调节:指根据主从偏差调整本地时钟的频率和相位。

下面把带时间和偏差的消息叫 时间消息,把只报告锁定状态、主时钟信息的消息叫 状态通知。不同实现的命名不同,按这个语义对应即可。

1. 现象:同步状态和时间可用性不是一回事

现场常见两类互相矛盾的现象:

现象设备看到的状态业务看到的结果
状态正常,时间仍有偏差SYNC_OK 或 TIME_VALID采样时间戳偏几十微秒到几毫秒
报文正常,业务却报失联PTP 报文计数在增长时间服务不再更新,业务停用时间戳

第一类问题说明:状态只能回答“时间服务认为能不能用”,不能回答“业务拿到的时间准不准”。第二类问题说明:网络授时和设备内部的时间传递是两段链路,前一段正常,不代表后一段正常。

本文把应用层状态归成三类:

状态含义业务上怎么用
正常协议锁定,时间可用时间戳可用,但仍要看偏差和连续性
保持暂未锁定,本地时钟短时继续走慎用,时间可观察,但主从可能已经不同步
失联没有有效时间消息不可用,采样时间戳不可信

这三类不是 IEEE 1588 的状态编号,而是给业务判断“能不能用”的最小集合。协议层和时钟调节层还有更细的状态,后面会说明为什么没有全部暴露出来。

1.1 为什么只归成三种状态

三种状态的划分标准是业务可用性:正常是能用,保持是暂时可读但不可信,失联是不能用。协议层实际有 INIT、LISTENING、UNCALIBRATED、SLAVE、MASTER、FAULTY 等状态,时钟调节层也有“样本不足、正在稳定、发生跳变”等细分状态。把它们全部暴露给业务,业务很难判断该不该用时间。工程上常见的做法是:内部保留细状态,对外只发布“正常、保持、失联”,具体原因放进原因字段。

如果设备需要在启动、切主、保持、故障之间做更细的控制,可以在内部多拆几种状态;对外仍然可以归并成这三类。三种不是标准答案,而是本文建议的最小对外集合。

先问三个问题,后面的排查才有落点:

  1. 设备说的“同步正常”是哪一层的判断,协议层、时钟调节层,还是业务接口?
  2. 业务程序读的是系统时间、网卡硬件时钟,还是时间服务进程发布的另一份完整状态数据?
  3. 网络报文正常时,设备内部的时间通道是否也在正常更新?

2. 示例设备:PTP 在做什么,时间怎么送到业务

在画链路之前,先补三块背景:PTP 在算什么、时间戳在哪打、Linux 下几个程序怎么分工。

2.1 PTP 在算什么

主时钟是网络里被选为时间源的设备,其他设备是从时钟。PTP 不是把主时钟的时间直接抄给从设备。报文在路上要花时间,主从时钟走得也不一样快。PTP 要估计两件事:

  • 当前差多少,也就是偏差(offset)。
  • 走得快慢差多少,也就是频率偏差。

以常见的双步(Follow_Up 携带发送时刻)、端到端(E2E)测量为例,主从之间交换四个时刻:

时刻事件谁记录
t1主时钟发出 Sync主时钟
t2从设备收到 Sync从时钟
t3从设备发出 Delay_Req从时钟
t4主时钟收到 Delay_Req主时钟

忽略上下行路径不对称时,偏差可以写成:

offset = [(t2 - t1) - (t4 - t3)] / 2

所以时间戳准不准、上下行延迟是否对称,都会进入结果。PTP 进程会持续采样,不是收到一帧就锁定;这也解释了后面为什么“样本不足”会让状态回到“保持”。

2.2 时间戳在哪打

时间戳来源记录的时刻工程上要注意
应用读当前时间应用处理报文的时刻不是报文到达网卡的时刻,排队和调度会算进去
内核软件时间戳内核或驱动处理报文时比应用读早,但仍在软件路径里
MAC/PHY 硬件时间戳报文实际进出网卡最接近链路,需要网卡和驱动支持

支持硬件时间戳的网卡通常带一个硬件时钟,也就是 PHC(PTP Hardware Clock)。PTP 进程用 PHC 记录报文时刻并调整它;phc2sys 可以把 PHC 同步到系统时间;pmc 用来查询状态;date 读的是系统时间。这四条路径不是一回事。

如果业务程序读系统时间,而 phc2sys 没跑或没同步,PTP 显示锁定也不代表 date 对。排查时先确认业务程序读的是哪一个时钟,再看对应的工具。

2.3 示例设备的原状

%%{init: {"theme":"base","themeVariables":{"fontFamily":"sans-serif","fontSize":"18px","lineColor":"#455A64"},"flowchart":{"htmlLabels":true,"padding":24,"nodeSpacing":45,"rankSpacing":55}}}%%
flowchart LR
    GM["主时钟
GNSS 或上位机"] --> SW["交换机"] SW --> NIC["从设备网卡
硬件时间戳 / PHC"] NIC --> PTP["PTP 进程"] PTP --> CLK["网卡硬件时钟或系统时间"] PTP -->|"状态与偏差"| TS["时间服务进程"] CLK -->|"当前时间"| TS TS -->|"完整状态数据"| APP["业务程序"] PTP <-->|"请求-应答通道"| APP classDef master fill:#EDE9FE,stroke:#6D28D9,stroke-width:2px,color:#4C1D95 classDef net fill:#E0F2FE,stroke:#0284C7,stroke-width:2px,color:#0C4A6E classDef proto fill:#FEF3C7,stroke:#D97706,stroke-width:2px,color:#78350F classDef dist fill:#DCFCE7,stroke:#16A34A,stroke-width:2px,color:#14532D classDef app fill:#FCE7F3,stroke:#DB2777,stroke-width:2px,color:#831843 class GM master class SW,NIC net class PTP,CLK proto class TS dist class APP app linkStyle default stroke:#64748B,stroke-width:1.5px

图 1 示例设备的原状。网络授时和设备内时间传递是两段链路,PTP 进程与业务程序之间还有一条自定义请求-应答通道。

这张图是示例设备的原状,不是推荐架构;推荐的状态管理方式放在第 5 章。

第一段是网络授时:主时钟发 Sync、Follow_Up 等报文,网卡或 PHY 记录报文进出时刻,PTP 进程估计主从偏差,调整网卡硬件时钟或系统时间。硬件时间戳、软件时间戳和 phc2sys 的部署方式不同,这一段的具体路径也不同。

第二段是设备内传递:PTP 进程把状态、偏差和时间交给时间服务进程,时间服务进程再发给业务程序。业务程序不一定直接读系统时间;在这台示例设备里,业务程序读的是时间服务进程发布的一份完整状态数据,另外还有一条请求-应答通道用于状态交互。

把两段分开看,就能解释很多“报文正常但业务失联”的现象:pmc 能读到偏差,只证明第一段和 PTP 进程还在工作;业务程序是否拿到时间,还要看第二段。

所以,“网络对时正常”只说明第一段正常。业务程序仍可能因为时间服务状态没更新、进程间通信积压或业务程序退出而拿不到时间;排查时不要把 pmc 正常当成整条链路正常。

2.4 观测点

观测点在哪执行能证明什么不能证明什么
date设备系统系统时间与参考时间的偏差量级PTP 协议是否锁定
pmc port_stats_npPTP 进程报文是否到达 PTP 进程时间服务进程是否收到完整状态数据
pmc time_status_npPTP 进程PTP 进程看到的偏差和主时钟信息业务程序拿到的时间是否可用
状态查询接口时间服务进程时间服务进程对外发布的状态PTP 协议层内部的真实原因
tcpdump网卡或交换机镜像口链路上有没有 PTP 报文报文有没有被 PTP 进程处理
Recv-Q / 重连计数PTP 进程与业务程序之间内部通道是否积压PTP 报文是否丢失

3. 三个现场案例

每个案例都按同一顺序展开:原来怎么连接,最先观察到什么,哪项检查缩小了范围,修改后怎么验证。

3.1 缓冲读写粒度不一致:状态在“正常”和“保持”之间跳

原来怎么连接。 处理端从采集端的 DMA 缓冲读取数据,PTP 进程和处理任务共享 CPU。处理端把单次读取粒度减半,但采集端的缓冲仍按原大小配置。

最先观察到什么。 PTP 状态在“正常”和“保持”之间以百毫秒级周期跳变;PTP 进程日志里,时钟调节算法频繁报样本不足;相邻 Sync 的间隔大约翻倍;CPU 占用明显波动。

哪项检查缩小了范围。 PTP 参数没有改动,改回缓冲配置后状态恢复,说明问题不在协议参数,而在业务读取路径。处理端每次只读一半,剩余数据触发重试,重试占用 CPU,PTP 进程的报文处理被延迟,时钟调节算法拿不到足够样本,状态被压回“保持”。

修改后怎么验证。 把 DMA 缓冲和读取粒度同步调整后,状态稳定在“正常”,现场记录的平均偏差在十微秒量级。这个数值只对应这套测试条件,不能当成所有平台的通用精度。当时没有采集线程调度跟踪,所以“PTP 进程抢不到 CPU”是合理解释,不是已经证实的完整因果链;已确认的是缓冲配置不一致,以及修改后状态恢复。

这个案例的教训不是“所有缓冲大小必须相等”,而是:读写双方对格式、边界和单次读取大小的约定要一起改。

3.2 授上了,但固定偏 37 秒

UTC 和 TAI 是两套时间尺度:UTC 有闰秒,TAI 没有。到目前为止,TAI − UTC = 37 秒。PTP 报文可以携带 UTC 偏移;如果主时钟和从设备对“当前用哪套时间尺度”理解不一致,就会出现固定整数秒的偏差。

原来怎么连接。 主时钟通过 PTP 给设备授时,业务程序读系统时间。现场同时能看到 PTP 状态“正常”和 pmc 显示的小偏差。

最先观察到什么。 设备 date 显示的时间和主时钟的 UTC 时间固定差 37 秒,不是随机漂移。

哪项检查缩小了范围。 clockdiff 显示固定整数秒偏差,基本可以排除网络抖动。继续查时间基准:系统时间按 UTC 理解,网卡硬件时钟可能按 TAI 计时。announce 是主时钟周期性广播自身状态和 UTC 偏移的报文;如果它携带 UTC 偏移 37,而从设备又按这个值做补偿,就可能把 37 秒重复加一次。

修改后怎么验证。 让两端使用同一套时间基准:确认 ptpTimescale 是 UTC 还是 TAI,检查 ptp4l 启动参数是否带 -O 0,再把 utc_offset 配成与主时钟一致的值。修改后 clockdiff 不再出现固定 37 秒,业务使用的时间与主时钟一致。

这里要区分两个对象:设备时间指 date 或业务程序读到的系统时间,主时钟时间指主时钟的 UTC 时间。pmc 的偏差小,只说明主从协议估计接近;如果时间基准定义不同,业务使用的时间仍可能整秒偏差。

3.3 运行约 30 小时后失联:Recv-Q 慢慢涨满

TCP 是字节流。Recv-Q 表示已经到达、但应用还没读走的字节数。发送方只发不读,接收队列就会一直涨;队列满后,对端的 send 会阻塞或失败,形成背压。

原来怎么连接。 除了标准 PTP 报文,示例设备的 PTP 进程还维护一条本机请求-应答通道,用来和业务程序交换时间与状态。这条通道不是标准 PTP 报文。

最先观察到什么。 现场运行 30 多小时后报 PTP 失联,必须下电或重启 PTP 进程才能恢复。pmc get port_stats_np 正常,说明 PTP 报文交互没有断;netstat -ant 显示 PTP 进程与业务程序之间的 TCP socket 的 Recv-Q 持续增长,最大到数 MB。

哪项检查缩小了范围。 PTP 报文正常,排除网络和主时钟;Recv-Q 增长,把问题指向进程间通信。PTP 进程只发请求、不读应答,应答在自己的接收队列里累积;队列满后,业务程序的 send 阻塞或失败,停止处理新请求;PTP 进程的发送队列随后也满,停止发送,时间通道整体停摆。

%%{init: {"theme":"base","themeVariables":{"fontFamily":"sans-serif","fontSize":"18px"}}}%%
sequenceDiagram
    participant P as PTP 进程
    participant A as 业务程序
    P->>A: 请求(每秒若干次)
    A->>P: 固定 6 字节应答
    Note over P: 从不 recv
应答在 Recv-Q 累积 Note over A: 发送缓冲写满
send 阻塞或失败 Note over P,A: 请求-应答停摆 Note over P: 外部看到时间失联

图 2 应答未读取导致的长时失联。PTP 报文正常,不代表 PTP 进程与业务程序之间的时间通道正常。

修改后怎么验证。 在 PTP 进程里显式读取业务程序的应答,并处理“一次读到半条或多条应答”的情况;缓冲区写满时丢弃无法识别的数据并重新同步。修复后把同步频率提高到约 1000 次/秒做加速测试,累计千万次,Recv-Q 始终为 0;再给 CPU 加上接近满负载的压力,时间传递仍然正常。加速测试不能代替长时间运行、温度变化和偶发断链测试。

4. 通用排查:先分段,再选工具

4.1 三段判断

排查分三段:网络与协议、时钟调节、设备内传递。每段先确认“有没有数据、数据是否在更新”,再看具体命令。

常用命令和判读如下。命令旁边的“在哪执行”比命令本身更重要:

pmc 是 linuxptp 自带的管理工具,用来向 PTP 进程查询状态;clockdiff 用来比较两台机器的时间差;ss/netstat 用来看 socket 队列。

命令在哪执行看什么典型异常
date设备系统当前系统时间明显不是当前时间,先别调 PTP 参数
pmc -u -b1 -f <ptp-config> 'get port_stats_np'PTP 进程rx_sync 等计数计数不增长,先查链路和配置
pmc -u -b1 -f <ptp-config> 'get time_status_np'PTP 进程master_offset、主时钟信息偏差很大,或多次采样不变
clockdiff <sensor-ip>主时钟侧或设备侧与对端的偏差形态固定整数秒、毫秒级跳动、down
ss -tanp / netstat -antPTP 进程所在设备Recv-Q、连接状态队列持续增长,连接反复重建
tcpdump -i eth0 -nn 'ether proto 0x88f7'网卡或镜像口Sync、Follow_Up、Delay_Req只有单向报文,或完全没有报文
状态查询接口时间服务进程应用层状态、原因、最后更新时间状态长期不变,或与 pmc 矛盾

0x88F7 是 PTP 的以太网类型;PTP 走 UDP 时使用 319 和 320 端口。抓包前先确认现场用的是二层还是 UDP 封装,别抓错过滤器。

4.2 判断路径

%%{init: {"theme":"base","themeVariables":{"fontFamily":"sans-serif","fontSize":"18px","lineColor":"#455A64"},"flowchart":{"htmlLabels":true,"padding":20,"nodeSpacing":40,"rankSpacing":45}}}%%
flowchart TD
    A["业务程序报时间异常"] --> B{"业务时间与参考时间偏差大?"}
    B -->|"是"| C["pmc 看偏差与报文计数"]
    B -->|"否"| D["查时间服务与进程间通信"]
    C -->|"rx_sync 不增长"| E["链路、配置、交换机、白名单"]
    C -->|"报文增长但偏差异常"| F["时钟调节、主时钟、CPU 负载"]
    D -->|"状态失联"| G["PTP 进程/时间服务存活、Recv-Q"]
    D -->|"状态保持"| H["调节样本、主时钟切换"]
    D -->|"状态正常但业务仍错"| I["时间基准、时间换算、业务程序读取"]
    E --> J["修正后复测"]
    F --> J
    G --> J
    H --> J
    I --> J

    classDef start fill:#EDE9FE,stroke:#6D28D9,stroke-width:2px,color:#4C1D95
    classDef check fill:#E0F2FE,stroke:#0284C7,stroke-width:2px,color:#0C4A6E
    classDef fix fill:#FEF3C7,stroke:#D97706,stroke-width:2px,color:#78350F
    classDef done fill:#DCFCE7,stroke:#16A34A,stroke-width:2px,color:#14532D
    class A start
    class B,C,D check
    class E,F,G,H,I fix
    class J done
    linkStyle default stroke:#64748B,stroke-width:1.5px

图 3 从业务现象回到链路的判断路径。业务程序报告失联、但协议偏差正常时,直接走“时间服务与进程间通信”分支。

4.3 八种症状先看哪一段

症状先看哪段下一步
完全授不上时网络与协议pmc port_stats_np 看 rx_sync 是否增长
授上但偏 37 秒时间基准clockdiff 看是否为固定整数秒
授上但偏 8 小时时间基准查 UTC 与本地时区是否混用
授上但抖动到毫秒时钟调节/负载看偏差分布、CPU 占用和报文间隔
长时间运行后偶发失联设备内传递看 Recv-Q、重连计数和业务时间更新
rogue peer delay response 后静默网络抓 0x88F7,查交换机是否支持 gPTP
received ... without timestamp网络/驱动查网卡初始化和上下电时序
多从设备建立同步超过 1 分钟网络抓 pdelay 报文,查交换机转发

5. 状态设计改进:只让一个进程改状态

三个案例暴露的不只是 PTP 参数问题,还有状态管理问题。案例原有实现有四个典型缺陷:

原实现的问题现象后果
时间消息和状态通知都能改同一个状态一个把状态拉回“正常”,另一个又压回“保持”状态抖动,日志无法解释
状态只有一个字段协议、时钟调节、链路、进程间通信的状态被压在一起看到“保持”或“失联”不知道先查哪层
状态没有有效期和启动批次进程重启或通道积压后仍可能沿用旧值业务程序拿着过期时间继续打戳
业务程序各自判断状态PTP 进程、时间服务、业务程序各有一份判断监控页面和业务实际使用的时间不一致

改动方向不是把所有状态塞进一个巨大的状态机,而是分层:

  • 各模块只上报自己看到的情况:协议是否锁定、时钟调节是否稳定、链路是否通、进程心跳是否正常。
  • 时间服务进程统一汇总、防抖、判超时和恢复,只有它能改设备级状态。
  • 业务程序只读时间服务进程发布的一份完整状态数据,不自己拼状态。
%%{init: {"theme":"base","themeVariables":{"fontFamily":"sans-serif","fontSize":"18px","lineColor":"#455A64"},"flowchart":{"htmlLabels":true,"padding":24,"nodeSpacing":45,"rankSpacing":55}}}%%
flowchart LR
    P["PTP 进程
协议状态与偏差"] -->|"上报"| A["时间服务进程
设备级状态
唯一修改者"] S["时钟调节
稳定结果"] -->|"上报"| A L["链路与心跳
Link up/down"] -->|"事实"| A A -->|"只读状态数据"| B["业务程序 A"] A -->|"只读状态数据"| C["业务程序 B"] A -->|"状态变化通知"| M["监控与诊断"] classDef fact fill:#E0F2FE,stroke:#0284C7,stroke-width:2px,color:#0C4A6E classDef owner fill:#DCFCE7,stroke:#16A34A,stroke-width:2px,color:#14532D classDef reader fill:#FEF3C7,stroke:#D97706,stroke-width:2px,color:#78350F classDef mon fill:#FCE7F3,stroke:#DB2777,stroke-width:2px,color:#831843 class P,S,L fact class A owner class B,C reader class M mon linkStyle default stroke:#64748B,stroke-width:1.5px

图 4 只让一个进程改状态。各模块只上报自己看到的情况,时间服务进程统一汇总,业务程序只读完整状态数据。

建议规则要写清楚,不能和原实现混着解释:

状态原实现建议规则
正常收到有效时间消息,映射建立协议锁定、时钟调节稳定、连续 N 个有效样本、状态数据未过期
保持协议报告未锁定,时间消息可能仍在更新短时保持或正在稳定,时间仍可观察,但业务不应无条件信任
失联超过 30 秒没有 PTP 数据有效时间消息超过有效截止时间

建议规则里,“保持”同时承担短时保持(holdover)和正在稳定的角色。没有保持能力的平台可以把保持时间设为 0,链路断开就直接进入“失联”;有保持能力时,先让业务程序知道“时间还在,但不可信”,超过有效截止时间再停用。

%%{init: {"theme":"base","themeVariables":{"fontFamily":"sans-serif","fontSize":"18px","lineColor":"#455A64"}}}%%
stateDiagram-v2
    [*] --> INIT: 上电或冷启动
    state "正常" as NORMAL
    state "保持" as HOLD
    state "失联" as DOWN
    INIT --> HOLD: 收到状态通知但尚未稳定
    INIT --> NORMAL: 连续 N 个有效样本且协议锁定
    NORMAL --> HOLD: 时钟调节未稳定或主时钟无效
    HOLD --> NORMAL: 连续 N 个有效样本且协议锁定
    NORMAL --> DOWN: 有效时间消息超过有效截止时间
    HOLD --> DOWN: 有效时间消息超过有效截止时间
    DOWN --> HOLD: 恢复收到有效时间消息但尚未稳定
    DOWN --> NORMAL: 恢复并连续 N 个有效样本
    NORMAL --> NORMAL: 有效样本持续更新

    classDef good fill:#DCFCE7,stroke:#16A34A,stroke-width:2px,color:#14532D
    classDef warn fill:#FEF3C7,stroke:#D97706,stroke-width:2px,color:#78350F
    classDef bad fill:#FEE2E2,stroke:#DC2626,stroke-width:2px,color:#7F1D1D
    classDef init fill:#E0F2FE,stroke:#0284C7,stroke-width:2px,color:#0C4A6E
    class INIT init
    class NORMAL good
    class HOLD warn
    class DOWN bad

图 5 建议规则下的应用层状态机。“正常”需要连续 N 个有效样本,“失联”只由有效时间消息超过有效截止时间触发。

图中的 N 不是 1。单条消息只能说明链路还在,不能说明时钟已经稳定;N 由同步周期和调节算法决定。启动阶段如果没有有效时间消息,也要按有效截止时间进入“失联”;状态通知只更新原因,不能把设备从“失联”拉回来。

这套设计不需要另外做一套投票或选主。PTP 协议自己会选主时钟,设备级状态只需要一个本地汇总者。

6. 边界与结论

“正常”不是精度证书,“保持”也不是“完全不可用”。状态只说明业务能不能用,偏差分布和业务使用时间仍要单独验收,告警也要区分“保持”和“失联”。

30 秒是应用层阈值,不是 PTP 标准;不同实现、不同配置模式的状态路径也不同。性能优化要全链路对齐,DMA 缓冲、读取粒度、共享内存布局任何一处改动,都可能改变 PTP 的报文处理节奏。约 30 小时才复发的 Recv-Q 问题,靠 1 小时测试发现不了;改完一项,至少复测状态稳定性和时间传递,并安排覆盖已知故障周期的长稳测试。

附录 A:工业设备常见的时间同步位置

这部分是背景参考,和前面的排障步骤没有直接依赖,赶时间可以跳过。

场景常见同步方式传感器侧做什么状态怎么暴露
工业以太网端站IEEE 802.1AS / gPTPPHY 或 MAC 附近打时间戳,端站参与同步同步状态、主时钟标识、偏差
PROFINET IRT 设备PROFINET 专用时间同步按周期同步,诊断上报同步丢失、设备诊断
EtherCAT 从站Distributed Clocks本地时钟与参考时钟对齐工作时钟与同步状态
IO-Link 传感器通常由 master 打时间戳传感器本身不跑 PTPmaster 或 PLC 记录时间
普通模拟量/数字量传感器采集模块打时间戳传感器不参与网络对时采集模块状态

共同点不是状态名称相同,而是三层分离:时间戳尽量靠近硬件生成,协议栈负责估计偏差,应用只读一个带有效期的状态。小体积传感器通常把时间戳放在采集端或主站,而不是在传感器里跑完整 PTP。

附录 B:状态数据字段与进程间通信读取要点

时间服务进程发布的一份完整状态数据至少要有这些字段:

字段作用
seq版本号,判断读取期间状态数据有没有被改写
state正常/保持/失联,由协议状态、时钟调节状态和数据新鲜度共同决定
reason原因码,例如无主时钟、调节样本不足、链路断开、通道超时
since进入当前状态的单调时刻
epoch时间服务启动或时钟源切换的启动批次号
last_time_msg_at最后一条有效时间消息的单调时刻;状态通知不刷新它
valid_untillast_time_msg_at + timeout,超过后进入“失联”

进程间通信读取有三条底线:

  1. recv 前先检查缓冲区是否已满,避免把 recv(..., 0, ...) 的返回值 0 误判成对端关闭。
  2. 一次读取可能拿到半条或多条应答,必须按完整帧解析;缓冲区写满且无法识别时,丢弃并重新同步。
  3. EAGAIN/EWOULDBLOCK、EINTR、连接关闭和真正的错误要分开处理;生产代码建议使用环形缓冲区。

参考资料

延伸阅读