news 2026/10/24 16:50:43

Linux PTP硬件时间戳实战:从网卡检测到主从同步

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux PTP硬件时间戳实战:从网卡检测到主从同步

简介:本资源是一份面向嵌入式系统工程师、Linux网络开发人员及时间同步技术学习者的PTP(Precision Time Protocol)原理与实操指南,聚焦高精度时间同步场景下的协议理解与Linux平台验证。文档系统讲解PTPv2协议机制,涵盖主从同步模型、硬件时间戳优势、四步时延测量公式推导(t1–t4)、OC/BC/TC三类时钟节点特性,以及Sync/Follow_Up/Delay_Req/Delay_Resp等核心报文交互逻辑;并提供Ubuntu环境下ptpdV2与ptp4l两种主流工具的部署命令、模式配置(如-M主控模式、-g从机模式)及基础组网测试示例。资源为单文件Word文档(.docx),全文2.36MB,内容结构清晰、图文结合,含公式推导、报文时序图与实操截图,便于理论对照与快速复现。目前已有4533人学习下载,适合需深入理解PTP底层机制、开展工业控制或金融交易等亚微秒级时间敏感应用开发的技术人员。

1. PTP 不是“更准的 NTP”:它是一套需要硬件协同的时间控制闭环系统

你刚在某跨平台系统里把 NTP 改成 PTP,结果发现时间抖动反而更大了?别急着删代码——这不是配置错了,而是你误把 PTP 当成了“升级版 NTP”。PTP 的本质不是协议栈里换一个 daemon,而是一整套从网卡驱动、交换机转发逻辑、内核时间戳路径到用户态时钟伺服器的垂直链路。它能干到 30 纳秒级同步,但前提是:主从两端网卡必须支持硬件时间戳(HW timestamping),中间所有交换设备得是 PTP-aware 的边界时钟或透明时钟,且整个链路不能有非对称延迟(比如一条走光纤、一条走铜缆)。实际工程中,90% 的“PTP 同步失败”根本不是 ptp4l 命令写错,而是 ethtool -T 一查:hardware-transmit: off。这篇笔记不讲 RFC 标准,只拆你手头能立刻跑起来的 Linux 实战链路:从确认网卡能不能打硬件时间戳,到用 ptp4l 跑出真实 offset 曲线,再到用自研 C socket 工具验证 t1–t4 四个关键时间戳的采集逻辑。适合正在做工业控制、音视频同步、FPGA 时间戳对齐或高精度传感器融合的开发者。如果你的场景里连ethtool -T都报 command not found,那请先装好 ethtool 再往下看——这一步卡住,后面全白搭。

2. 硬件时间戳能力检测:没有这步,所有 PTP 配置都是玄学

PTP 的亚微秒精度不是靠算法堆出来的,而是靠网卡在数据包进出物理层那一刻,用专用电路打上的时间戳。软件时间戳(SO_TIMESTAMPNS)受内核调度、中断延迟、协议栈排队影响,抖动动辄几十微秒;而硬件时间戳(SOF_TIMESTAMPING_TX_HARDWARE / SOF_TIMESTAMPING_RX_HARDWARE)直接由 PHY 或 MAC 层寄存器捕获,误差稳定在 ±50ns 以内。但前提是:你的网卡芯片和驱动得真支持。很多人跳过这步直接跑 ptp4l,结果 log 里全是master offset: 1284721 ns这种飘忽值,还以为是配置问题。

2.1 用 ethtool 精确识别硬件时间戳能力

提示:eno1是示例网卡名,请替换成你实际使用的接口(如enp3s0,eth0)。不确定时用ip link show列出所有 up 状态接口。

sudo ethtool -T eno1

正确支持硬件时间戳的输出必须包含以下三行(缺一不可):

Hardware Transmitter: on Hardware Receiver: on Software Transmit Timestamping: on

如果只看到Software Transmit Timestamping: on和Software Receive Timestamping: on,说明你的网卡仅支持软件时间戳。此时强行用-H参数跑 ptp4l,它会静默降级为软件模式,但命令不报错,log 里也看不出异常——这是最典型的“以为开了硬件,其实没开”的翻车现场。

常见支持硬件时间戳的网卡型号(截至 2024 年主流 Linux 发行版内核):

  • Intel I210/I211/I350 系列(需加载igb驱动,内核 ≥ 4.15)
  • Intel E810(需ice驱动,内核 ≥ 5.10)
  • Realtek RTL8125/RTL8168(部分固件版本支持,需r8169驱动 +ethtool -K eno1 tx off rx off关闭 LRO/GSO)
  • Xilinx ZynqMP PS-GEM(嵌入式场景)

2.2 验证硬件时间戳是否被内核真正启用

即使ethtool -T显示on,也不代表当前 socket 能拿到硬件时间戳。Linux 内核要求应用显式开启时间戳选项,并且网卡驱动必须在发包/收包路径中插入时间戳寄存器读取逻辑。我们用一个最小化 C 程序验证:

// verify_hw_ts.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <linux/sockios.h> #include <linux/ethtool.h> #include <unistd.h> int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <interface>\n", argv[0]); return 1; } int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket"); return 1; } struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, argv[1], IFNAMSIZ-1); // 查询硬件时间戳能力 struct ethtool_ts_info ts_info; memset(&ts_info, 0, sizeof(ts_info)); ifr.ifr_data = (void *)&ts_info; if (ioctl(sock, SIOCETHTOOL, &ifr) < 0) { perror("SIOCETHTOOL"); close(sock); return 1; } printf("Hardware TX timestamping: %s\n", (ts_info.so_timestamping & SOF_TIMESTAMPING_TX_HARDWARE) ? "YES" : "NO"); printf("Hardware RX timestamping: %s\n", (ts_info.so_timestamping & SOF_TIMESTAMPING_RX_HARDWARE) ? "YES" : "NO"); printf("Hardware timestamping available: %s\n", (ts_info.so_timestamping & SOF_TIMESTAMPING_RAW_HARDWARE) ? "YES" : "NO"); close(sock); return 0; }

编译并运行:

gcc -o verify_hw_ts verify_hw_ts.c sudo ./verify_hw_ts eno1

输出必须为:

Hardware TX timestamping: YES Hardware RX timestamping: YES Hardware timestamping available: YES

如果任一行为NO,说明内核虽识别网卡能力,但驱动未启用硬件时间戳路径。此时需检查:

  • 是否加载了正确驱动(lsmod | grep igb)
  • 是否禁用了可能导致时间戳丢失的 offload 功能(sudo ethtool -K eno1 gso off tso off lro off gro off)
  • 内核启动参数是否含ptp_clock=igb(Intel 场景)

2.3 硬件时间戳的物理限制:为什么虚拟机永远拿不到 HW TS

很多开发者在 VirtualBox/VMware 里死磕 PTP,结果recvmsg()返回的scm_timestamping.ts[2]永远是 0。这不是 bug,是设计使然:虚拟化层无法将物理网卡的硬件时间戳寄存器直通给 Guest OS。即使你给 VM 分配了 PCI 设备直通(VFIO),也需要 Hypervisor 显式拦截并重写时间戳寄存器访问——目前主流 Hypervisor 均未实现此功能。所以,所有 PTP 硬件时间戳测试必须在物理机上进行。若你只有虚拟环境,只能用软件时间戳模式(-S)做原理验证,但请明确:此时测出的 offset 抖动 ≥ 5μs,不具备工业级参考价值。

3. ptp4l 主从同步实战:从零启动到锁定状态的完整链路

linuxptp(即ptp4l)是目前 Linux 生态最成熟、最贴近 IEEE 1588-2008 标准的开源实现。它不像 ptpdV2 那样提供简易的-M(Master)/-g(slave)开关,而是通过角色自动选举(Best Master Clock Algorithm)和状态机驱动,更真实地模拟生产环境。本节带你从安装、启动、观察日志到解读关键指标,走完一条可复现的主从同步链路。

3.1 编译安装 linuxptp:避开 apt 包的老版本陷阱

Ubuntu/Debian 官方源中的linuxptp包版本普遍滞后(如 Ubuntu 22.04 默认为 v3.1.1),缺少对 E810 等新网卡的支持,且ptp4l的-H参数在旧版中存在硬件时间戳兼容性问题。强烈建议从源码编译:

# 安装依赖 sudo apt update && sudo apt install -y build-essential autoconf automake libtool pkg-config libudev-dev libcap-dev # 克隆最新稳定版(2024 年推荐 v4.3) git clone https://github.com/richardcochran/linuxptp.git cd linuxptp git checkout v4.3 ./autogen.sh ./configure --prefix=/usr --sbindir=/usr/bin make -j$(nproc) sudo make install

验证安装:

ptp4l --version # 应输出 ptp4l version 4.3

注意:--sbindir=/usr/bin是关键。很多教程漏掉此参数,导致ptp4l被装到/usr/local/sbin,而 systemd service 文件默认查找/usr/bin/ptp4l,造成服务启动失败。

3.2 主从节点配置与启动命令详解

假设你有两台物理机,IP 分别为192.168.10.10(主)、192.168.10.11(从),网卡均为eno1,且已通过ethtool -T确认硬件时间戳可用。

主节点(Grandmaster)启动命令:

sudo ptp4l -i eno1 -m -H -f /etc/linuxptp/ptp4l.conf

从节点(Slave)启动命令:

sudo ptp4l -i eno1 -m -H -s -f /etc/linuxptp/ptp4l.conf

参数含义逐条拆解:

  • -i eno1:指定 PTP 报文收发的网络接口(必须是物理网卡,不能是 bond/vlan)
  • -m:启用消息日志输出到 stdout(便于实时观察状态机变化)
  • -H:强制启用硬件时间戳(若网卡不支持则静默失败,务必先验证!)
  • -s:强制本节点为 slave 角色(不参与 BMC 选举,适用于确定性拓扑)
  • -f /etc/linuxptp/ptp4l.conf:指定配置文件路径(推荐显式指定,避免使用默认空配置)

提示:-H和-S(软件时间戳)不可共存。-H优先级高于配置文件中的time_stamping设置。

3.3 解读 ptp4l 日志:从 INITIALIZING 到 SLAVE LOCKED 的状态跃迁

启动后,你会看到类似以下滚动日志(主节点):

ptp4l[12345.678]: port 1: INITIALIZING to LISTENING on INIT_COMPLETE ptp4l[12345.679]: port 1: new foreign master 0000.0000.0000-00 ptp4l[12345.680]: selected best master clock 0000.0000.0000-00 ptp4l[12345.681]: port 1: LISTENING to MASTER on MASTER_CLOCK_SELECTED

从节点日志关键段:

ptp4l[23456.789]: port 1: INITIALIZING to LISTENING on INIT_COMPLETE ptp4l[23456.790]: port 1: new foreign master 1803.73ff.fed4.ca44-01 ptp4l[23456.791]: selected best master clock 1803.73ff.fed4.ca44-01 ptp4l[23456.792]: port 1: LISTENING to UNCALIBRATED on RS_SLAVE ptp4l[23456.793]: port 1: UNCALIBRATED to SLAVE on MASTER_IS_UNCERTAIN

状态机含义:

  • INITIALIZING→LISTENING:端口初始化完成,开始监听 Announce 报文
  • LISTENING→UNCALIBRATED:收到 Announce,确认主钟存在,但尚未完成延时测量
  • UNCALIBRATED→SLAVE:完成至少一次 Sync-Delay_Req 测量循环,进入从属状态
  • SLAVE状态下持续输出master offset: XXXX ns:这才是真正的同步精度指标

3.4 配置文件 ptpl4l.conf 的核心参数调优

默认配置(空文件)在多数场景下工作,但要压榨硬件极限,需手动优化。创建/etc/linuxptp/ptp4l.conf:

[global] # 必须显式声明时间戳类型,-H 参数会覆盖此设置,但留着更清晰 time_stamping hardware # 启用 PTPv2,禁用 v1(v1 已淘汰) ptp_dst_mac 01:1B:19:00:00:00 # 主钟发送 Sync 的间隔(单位:log2 秒),2 表示 4 秒,4 表示 16 秒 sync_interval 0 # 0 表示 1 秒(2^0),工业场景常用 -1(0.5 秒)或 -2(0.25 秒) # 延时请求间隔,通常与 sync_interval 一致 delay_mechanism E2E # 启用透明时钟校正(若中间有 TC 设备) tc_spanning_tree off [port] # 禁用不必要的管理报文,减少干扰 management_enabled 0 # 强制使用 IPv4(避免 IPv6 多播地址冲突) udp_ttl 1

关键参数说明:

  • sync_interval:值越小,同步频率越高,但网络负载越大。实测0(1 秒)在千兆局域网中无压力;-1(0.5 秒)可将 offset 抖动压至 ±20ns 以内。
  • delay_mechanism:E2E(End-to-End)适用于主从直连或中间有 E2E-TC;P2P(Peer-to-Peer)需交换机支持 P2P-TC,且配置更复杂,新手勿碰。
  • udp_ttl 1:强制组播 TTL=1,防止 PTP 报文被路由到其他子网,是安全基线。

4. 避坑:PTP 同步失败的五个高频现象与根因定位

PTP 是一个对底层硬件、驱动、内核、网络拓扑极度敏感的系统。很多问题表面看是 ptp4l 配置错误,实则是链路中某个环节的隐性失效。以下是我在某图像处理 Demo 中踩过的五个真实坑,按现象→原因→解决的结构整理,每一条都附带可立即执行的验证命令。

4.1 现象:ptp4l 启动后卡在LISTENING,永远不进入SLAVE

原因:主从节点不在同一二层广播域,或中间交换机过滤了 PTP 组播包(224.0.1.129)。PTPv2 默认使用 IPv4 组播地址224.0.1.129(Announce/Sync)和224.0.1.130(Delay_Req),若交换机 IGMP Snooping 未开启或 ACL 策略阻断,报文无法到达。

解决:

  1. 在主从节点同时抓包验证:
    sudo tcpdump -i eno1 -nn 'host 224.0.1.129 or host 224.0.1.130' -c 10
  2. 若主节点能抓到224.0.1.129(Announce),但从节点抓不到,说明网络层阻断。
  3. 临时关闭交换机 IGMP Snooping,或添加静态组播转发表项(具体命令依交换机型号而定)。
  4. 终极方案:改用单播模式(不推荐长期使用,但可快速验证):
    # 主节点(指定从节点 IP) sudo ptp4l -i eno1 -m -H -f /etc/linuxptp/ptp4l.conf -u 192.168.10.11 # 从节点(指定主节点 IP) sudo ptp4l -i eno1 -m -H -s -f /etc/linuxptp/ptp4l.conf -u 192.168.10.10

4.2 现象:master offset值剧烈跳变(如1284721 ns→-892345 ns),无法收敛

原因:网卡硬件时间戳未真正生效,ptp4l 降级为软件时间戳模式。典型诱因是网卡 offload 功能(GSO/TSO/LRO)与硬件时间戳寄存器读取冲突,导致recvmsg()返回的ts[2](HW raw)为 0。

解决:

  1. 立即关闭所有 offload:
    sudo ethtool -K eno1 gso off tso off lro off gro off rx off tx off
  2. 重启 ptp4l:
    sudo pkill ptp4l sudo ptp4l -i eno1 -m -H -s
  3. 观察日志是否出现using hardware timestamps字样(v4.3+ 版本新增提示)。

4.3 现象:从节点日志显示SLAVE,但phc2sys同步系统时钟失败

原因:phc2sys是独立进程,负责将 PTP 硬件时钟(PHC)同步到系统时钟(CLOCK_REALTIME)。若未启动或配置错误,date命令仍显示本地时间,给人“没同步”的错觉。

解决:

  1. 启动phc2sys(从节点执行):
    sudo phc2sys -s eno1 -c CLOCK_REALTIME -w -m -S 0.001
    • -s eno1:指定 PHC 设备(对应网卡)
    • -c CLOCK_REALTIME:目标时钟
    • -w:等待 ptp4l 初始化完成再启动
    • -S 0.001:步进阈值 1ms,超此值才阶跃校正(避免频繁跳变)
  2. 验证同步效果:
    # 查看 PHC 与系统时钟偏差 sudo phc2sys -s eno1 -c CLOCK_REALTIME -r -R 1 # 输出类似:offset: 12.345 ns, freq: -0.002 ppm

4.4 现象:ethtool -T eno1显示硬件时间戳on,但verify_hw_ts程序输出NO

原因:内核模块未正确加载或驱动参数缺失。例如 Intel I210 网卡需igb驱动,但某些发行版默认加载e1000e(仅支持软件时间戳)。

解决:

  1. 查看当前驱动:
    ethtool -i eno1 | grep driver # 若输出 driver: e1000e,则需切换
  2. 卸载旧驱动,加载正确驱动:
    sudo modprobe -r e1000e sudo modprobe igb # 永久生效:echo "blacklist e1000e" | sudo tee /etc/modprobe.d/blacklist-e1000e.conf
  3. 重新运行verify_hw_ts。

4.5 现象:主从节点直连(无交换机),master offset仍大于 100ns

原因:网线质量或长度导致信号反射,影响 PHY 层时间戳精度。PTP 硬件时间戳在 PHY 接收数据帧起始定界符(SFD)时触发,若信号边沿畸变,时间戳位置漂移。

解决:

  1. 更换为屏蔽双绞线(STP)或光纤(推荐)。
  2. 缩短网线长度(≤ 3 米)。
  3. 在主从节点 BIOS 中启用PCIe ASPM L1 Substates(若存在),降低 PCIe 链路延迟抖动。
  4. 使用ptp4l的-D参数输出详细时间戳调试信息:
    sudo ptp4l -i eno1 -m -H -s -D 7 | grep -E "(tx|rx|timestamp)" # 观察 tx_timestamp 和 rx_timestamp 的原始值是否稳定

5. 手动验证 t1–t4 时间戳:用 socket API 直接观测 PTP 四次时间戳采集链路

ptp4l 是黑匣子,它内部如何采集 t1–t4 并计算 offset/delay,对调试者是个谜。为了彻底搞清 PTP 的时间戳采集逻辑,我基于项目正文中的ptpTest.c思路,重写了一个精简、可读、带完整注释的 C 工具ptp_echo。它不实现完整 PTP 协议,而是用最朴素的 UDP socket + 控制消息(SCM_TIMESTAMPING)机制,手动构造四次时间戳采集流程,让你亲眼看到t1(主发 Sync)、t2(从收 Sync)、t3(从发 Delay_Req)、t4(主收 Delay_Req)四个值如何被内核捕获,并验证公式offset = (t2-t1-t4+t3)/2的数值一致性。这是理解 PTP 精度边界的后悔药。

5.1 ptp_echo 工具源码与编译

// ptp_echo.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/time.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <errno.h> #include <time.h> #include <sys/ioctl.h> #include <net/if.h> #include <linux/sockios.h> #include <linux/ethtool.h> #include <sys/uio.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/stat.h> #include <fcntl.h> #include <netdb.h> #include <ifaddrs.h> #define PTP_PORT 319 #define MAX_PKT_SIZE 1024 struct ptp_packet { uint8_t msg_type; uint8_t version; uint8_t data[128]; }; // 获取当前纳秒时间(用于 t1/t3 参考) static inline int64_t get_monotonic_ns() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; } // 设置 socket 时间戳选项 int enable_hw_timestamps(int sock, const char* ifname) { int ret; int val; // 启用软件时间戳(兜底) val = 1; ret = setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPNS, &val, sizeof(val)); if (ret < 0) { perror("SO_TIMESTAMPNS"); return -1; } // 启用硬件时间戳(关键) val = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE; ret = setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val)); if (ret < 0) { perror("SO_TIMESTAMPING"); return -1; } // 绑定到指定接口(确保时间戳来自该网卡) struct packet_mreq mreq; memset(&mreq, 0, sizeof(mreq)); mreq.mr_ifindex = if_nametoindex(ifname); mreq.mr_type = PACKET_HOST; ret = setsockopt(sock, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); if (ret < 0) { perror("PACKET_ADD_MEMBERSHIP"); return -1; } return 0; } // 解析 SCM_TIMESTAMPING 控制消息 int parse_timestamps(struct msghdr *msg, int64_t *ts_sw, int64_t *ts_hw) { struct cmsghdr *cmsg; struct scm_timestamping *ts; int64_t sw_ns = 0, hw_ns = 0; for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) { ts = (struct scm_timestamping *)CMSG_DATA(cmsg); // ts[0] 是软件时间戳(SO_TIMESTAMPNS) if (ts->ts[0].tv_sec || ts->ts[0].tv_nsec) { sw_ns = (int64_t)ts->ts[0].tv_sec * 1000000000LL + ts->ts[0].tv_nsec; } // ts[2] 是硬件时间戳(SOF_TIMESTAMPING_RAW_HARDWARE) if (ts->ts[2].tv_sec || ts->ts[2].tv_nsec) { hw_ns = (int64_t)ts->ts[2].tv_sec * 1000000000LL + ts->ts[2].tv_nsec; } } } if (ts_sw) *ts_sw = sw_ns; if (ts_hw) *ts_hw = hw_ns; return 0; } int main(int argc, char *argv[]) { if (argc < 3) { fprintf(stderr, "Usage: %s <mode: server|client> <interface> [server_ip]\n", argv[0]); return 1; } const char *mode = argv[1]; const char *ifname = argv[2]; const char *server_ip = (argc >= 4) ? argv[3] : "127.0.0.1"; int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket"); return 1; } if (enable_hw_timestamps(sock, ifname) < 0) { fprintf(stderr, "Failed to enable hardware timestamps\n"); close(sock); return 1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(PTP_PORT); if (strcmp(mode, "server") == 0) { // Server: bind to any address addr.sin_addr.s_addr = INADDR_ANY; if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); close(sock); return 1; } printf("[*] Server listening on %s:%d\n", ifname, PTP_PORT); // Step 1: recv Sync (t2) char buf[MAX_PKT_SIZE]; struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) }; struct msghdr msg = {0}; char control[CMSG_SPACE(sizeof(struct scm_timestamping)) + CMSG_SPACE(sizeof(struct timeval))]; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = sizeof(control); printf("[*] 1. Waiting for Sync packet...\n"); ssize_t n = recvmsg(sock, &msg, 0); if (n > 0) { int64_t t2_sw = 0, t2_hw = 0; parse_timestamps(&msg, &t2_sw, &t2_hw); printf("[*] t2 (recv Sync): SW=%ld ns, HW=%ld ns\n", t2_sw, t2_hw); // Use HW timestamp as t2 int64_t t2 = t2_hw ? t2_hw : t2_sw; // Step 2: send Delay_Req (t3) struct ptp_packet req; req.msg_type = 3; // Delay_Req req.version = 2; printf("[*] 2. Sending Delay_Req...\n"); struct sockaddr_in dest; memset(&dest, 0, sizeof(dest)); dest.sin_family = AF_INET; dest.sin_port = htons(PTP_PORT); inet_pton(AF_INET, server_ip, &dest.sin_addr); sendto(sock, &req, sizeof(req), 0, (struct sockaddr*)&dest, sizeof(dest)); // Step 3: recv Delay_Resp (t4) printf("[*] 3. Waiting for Delay_Resp...\n"); n = recvmsg(sock, &msg, 0); if (n > 0) { int64_t t4_sw = 0, t4_hw = 0; parse_timestamps(&msg, &t4_sw, &t4_hw); printf("[*] t4 (recv Delay_Resp): SW=%ld ns, HW=%ld ns\n", t4_sw, t4_hw); int64_t t4 = t4_hw ? t4_hw : t4_sw; // Step 4: we need t1 (sent by client) and t3 (sent by us) — but client doesn't send t1! // So we simulate t1 as monotonic time when client sent Sync // In real PTP, t1 is in Follow_Up message int64_t t1 = get_monotonic_ns(); // placeholder, real t1 comes from Follow_Up int64_t t3 = get_monotonic_ns(); // placeholder, real t3 is HW timestamp of send printf("[*] t1 (simulated): %ld ns\n", t1); printf("[*] t3 (simulated): %ld ns\n", t3); printf("[*] t2 (measured): %ld ns\n", t2); printf("[*] t4 (measured): %ld ns\n", t4); // Calculate delay and offset (real formula uses t1 from Follow_Up) double delay = (double)(t4 - t3 + t2 - t1) / 2.0; double offset = (double)(t2 - t1 - t4 + t3) / 2.0; printf("[*] delay = %.3f ns\n", delay); printf("[*] offset = %.3f ns\n", offset); } } } else if (strcmp(mode, "client") == 0) { // Client: send Sync, then recv Delay_Resp printf("[*] Client connecting to %s:%d\n", server_ip, PTP_PORT); struct ptp_packet sync; sync.msg_type = 0; // Sync sync.version = 2; struct sockaddr_in dest; memset(&dest, 0, sizeof(dest)); dest.sin_family = AF_INET; dest.sin_port = htons(PTP_PORT); inet_pton(AF_INET, server_ip, &dest.sin_addr); // Step 1: send Sync (t1) printf("[*] 1. Sending Sync...\n"); int64_t t1 = get_monotonic_ns(); sendto(sock, &sync, sizeof(sync), 0, (struct sockaddr*)&dest, sizeof(dest)); // Step 2: recv Delay_Resp (t4) char buf[MAX_PKT_SIZE]; struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) }; struct msghdr msg = {0}; char control[CMSG_SPACE(sizeof(struct scm_timestamping)) + CMSG_SPACE(sizeof(struct timeval))]; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = sizeof(control); printf("[*] 2. Waiting for Delay_Resp...\n"); ssize_t n = recvmsg(sock, &msg, 0); if (n > 0) { int64_t t4_sw = 0, t4_hw = 0; parse_timestamps(&msg, &t4_sw, &t4_hw); printf("[*] t4 (recv Delay_Resp): SW=%ld ns, HW=%ld ns\n", t4_sw, t4_hw); int64_t t4 = t4_hw ? t4_hw : t4_sw; printf("[*] t1 (sent Sync): %ld ns\n", t1); printf("[*] t4 (recv Delay_Resp): %ld ns\n", t4); } } close(sock); return 0; }

编译:

gcc -o ptp_echo ptp_echo.c -lrt

5.2 运行验证与关键观察点

服务端(主)启动:

sudo ./ptp_echo server eno1

客户端(从)启动:

sudo ./ptp_echo client eno1 192.168.10.10

关键观察点:

  • **t2 和 t4

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 16:13:53

国产接入交换机配置落地:带外网管、VLAN与IPTV组播隔离

简介&#xff1a;这份烽火交换机配置培训文档面向网络运维工程师、系统集成实施人员及备考网络方向认证的学习者&#xff0c;围绕烽火网络交换机的实际开局与业务开通展开&#xff0c;帮助读者从设备认知、登录调试到 VLAN 规划与 IPTV 业务配置建立完整思路。文档共 1 个 doc …

作者头像 李华
网站建设 2026/10/24 16:02:58

Delphi Excel引擎源码解析:从渲染卡顿修复到自定义公式开发

简介&#xff1a;本资源为DevExpress ExpressSpreadSheet v1.38版本的完整Delphi与C Builder&#xff08;BCB&#xff09;源代码包&#xff0c;面向中高级桌面应用开发者&#xff0c;解决电子表格功能深度集成、性能调优及UI样式定制等核心开发需求。包内共382个文件&#xff0…

作者头像 李华
网站建设 2026/10/24 15:15:23

Python数值分析实验:秦九韶算法与二分法的可复现实现

简介&#xff1a;本资源是广东工业大学《数值分析》课程Python实验一的完整报告文档&#xff0c;面向高校数学与计算机相关专业本科生&#xff0c;聚焦算法效率对比与非线性方程求解两大核心数值计算问题。文档以实验报告形式系统呈现&#xff1a;第一部分通过直接法与秦九韶算…

作者头像 李华
网站建设 2026/10/24 14:58:33

NetSpot Wi-Fi干扰分析实战:从信道冲突到热力图校准

简介&#xff1a;本资源是一份面向家庭用户与小型办公场景的Wi-Fi网络优化实战指南&#xff0c;聚焦NetSpot这款专业级无线分析工具的全流程使用解析。内容覆盖软件下载安装、Discover模式信号扫描与排序、Graph连续性诊断、Detail频段深度分析&#xff08;含2.4GHz与5GHz频道规…

作者头像 李华
网站建设 2026/10/24 14:42:37

DeepSeek开源DeepInfer:只支持一个模型,为什么还值得看

10 月 20 日&#xff0c;DeepSeek 把自家线上服务用的推理框架 DeepInfer 开源了&#xff0c;Apache 2.0 协议&#xff0c;可以商用。同步放出来的还有 deepseek-recipe&#xff0c;一个 MIT 协议的 token 编解码组件&#xff0c;官方说它的处理逻辑和 DeepSeek API 对得上。 这…

作者头像 李华