3.10 管理协议与pmc:PTP的“远程控制台“
为什么需要管理协议
运行中的PTP网络需要管理:
常见管理需求:
1. 状态监控
- 当前时钟偏差多少?
- 谁是主时钟?
- 同步状态如何?
2. 参数配置
- 修改优先级
- 调整发送间隔
- 设置域号
3. 故障诊断
- 为什么不同步?
- 消息统计
- 错误计数
4. 运维操作
- 切换主时钟
- 重启端口
- 更新配置
如何实现?
IEEE 1588定义了管理协议:
- 管理消息(Management Message)
- GET:读取参数
- SET:设置参数
- COMMAND:执行命令
管理协议基础
管理消息结构
/* msg.h中定义 */
struct management_msg {
struct PortIdentity targetPortIdentity; /* 目标端口 */
UInteger16 sequenceId; /* 序列号 */
Enumeration8 boundaryHops; /* 跳数限制 */
Enumeration8 actionField; /* 动作类型 */
Octet reserved[4]; /* 保留 */
/* TLV跟随 */
};
关键字段解析:
targetPortIdentity:
- 指定管理消息的目标
- 可以是特定端口
- 可以是广播(全0xFF)
sequenceId:
- 请求和响应配对
- 递增序列号
- 用于匹配响应
boundaryHops:
- 管理消息可以跨边界时钟
- 每经过一个边界时钟减1
- 0时丢弃,防止无限传播
actionField:
- GET:请求读取
- SET:请求设置
- RESPONSE:响应
- COMMAND:命令
- ACKNOWLEDGE:确认
管理动作类型
/* tlv.h, 第58-64行 */
enum management_action {
GET, /* 读取请求 */
SET, /* 设置请求 */
RESPONSE, /* 响应 */
COMMAND, /* 命令 */
ACKNOWLEDGE, /* 确认 */
};
动作流程:
GET操作:
客户端 → 服务端:GET DEFAULT_DATA_SET
服务端 → 客户端:RESPONSE DEFAULT_DATA_SET (data)
SET操作:
客户端 → 服务端:SET PRIORITY1 (value=128)
服务端 → 客户端:RESPONSE PRIORITY1 (value=128)
COMMAND操作:
客户端 → 服务端:COMMAND INITIALIZE
服务端 → 客户端:ACKNOWLEDGE INITIALIZE
boundary_hops的作用
管理消息传播范围:
boundary_hops = 1:
- 只到达直接连接的端口
- 不跨边界时钟
boundary_hops = 2:
- 可以经过1个边界时钟
- 到达相邻网段
boundary_hops = 255:
- 几乎无限制传播
- 可能到达整个PTP域
边界时钟处理:
1. 接收管理消息
2. boundary_hops -= 1
3. 如果boundary_hops > 0,转发
4. 如果boundary_hops = 0,处理但不转发
防止环路:
- 限制传播范围
- 避免广播风暴
pmc工具
pmc简介
pmc = PTP Management Client
作用:
- 管理PTP设备的客户端工具
- 通过管理协议与ptp4l通信
- 读取和设置参数
- 诊断问题
特点:
- 命令行界面
- 交互式或批处理模式
- 支持所有标准管理ID
- 支持LinuxPTP扩展
典型用法:
pmc -u -b 0 "GET CURRENT_DATA_SET"
pmc命令行参数
/* pmc.c, 第684-706行 */
usage: pmc [options] [commands]
Network Transport:
-2 IEEE 802.3(原始以太网)
-4 UDP IPv4(默认)
-6 UDP IPv6
-u UDS local(Unix域套接字)
Other Options:
-b [num] boundary hops,默认1
-d [num] domain number,默认0
-f [file] 从文件读取配置
-i [dev] 接口设备,默认eth0或/var/run/pmc.$pid(UDS)
-s [path] UDS服务器地址,默认/var/run/ptp4l
-t [hex] transport specific字段,默认0x0
-z 发送零长度TLV
传输方式选择:
# UDP/IPv4(最常用)
pmc -4 -i eth0 "GET CURRENT_DATA_SET"
# UDP/IPv6
pmc -6 -i eth0 "GET CURRENT_DATA_SET"
# 原始以太网
pmc -2 -i eth0 "GET CURRENT_DATA_SET"
# UDS(本地管理)
pmc -u -s /var/run/ptp4l "GET CURRENT_DATA_SET"
boundary_hops设置:
# 只管理本地端口
pmc -u -b 0 "GET TIME_STATUS_NP"
# 管理相邻设备
pmc -4 -b 1 "GET CURRENT_DATA_SET"
# 管理整个域
pmc -4 -b 255 "GET DEFAULT_DATA_SET"
交互模式
$ pmc -u
pmc> GET CURRENT_DATA_SET
/var/run/ptp4l.000468-000000 seq 0 RESPONSE MANAGEMENT CURRENT_DATA_SET
stepsRemoved 1
offsetFromMaster -3276.8
meanPathDelay 16384.0
pmc> GET TIME_STATUS_NP
/var/run/ptp4l.000468-000000 seq 0 RESPONSE MANAGEMENT TIME_STATUS_NP
master_offset -3277
ingress_time 1234567890123456789
cumulativeScaledRateOffset +0.000000000
gmPresent true
gmIdentity 00.1b.19.00.00.00.00.01
pmc> help
[action] DEFAULT_DATA_SET
[action] CURRENT_DATA_SET
[action] PARENT_DATA_SET
...
pmc> exit
批处理模式
# 执行单个命令
pmc -u "GET CURRENT_DATA_SET"
# 执行多个命令
pmc -u "GET DEFAULT_DATA_SET" "GET CURRENT_DATA_SET" "GET PARENT_DATA_SET"
# 从文件读取命令
pmc -u < commands.txt
管理ID详解
时钟管理ID
/* 常用时钟管理ID */
MID_DEFAULT_DATA_SET /* 默认数据集 */
MID_CURRENT_DATA_SET /* 当前数据集 */
MID_PARENT_DATA_SET /* 父时钟数据集 */
MID_TIME_PROPERTIES_DATA_SET /* 时间属性数据集 */
MID_PRIORITY1 /* 优先级1 */
MID_PRIORITY2 /* 优先级2 */
MID_DOMAIN /* 域号 */
MID_TIME_STATUS_NP /* 时间状态(LinuxPTP扩展) */
MID_GRANDMASTER_SETTINGS_NP /* 主时钟设置(LinuxPTP扩展) */
端口管理ID
/* 常用端口管理ID */
MID_PORT_DATA_SET /* 端口数据集 */
MID_PORT_PROPERTIES_NP /* 端口属性(LinuxPTP扩展) */
MID_PORT_STATS_NP /* 端口统计(LinuxPTP扩展) */
MID_CLOCK_DESCRIPTION /* 时钟描述 */
MID_LOG_ANNOUNCE_INTERVAL /* Announce间隔 */
MID_LOG_SYNC_INTERVAL /* Sync间隔 */
MID_DELAY_MECHANISM /* 延迟机制 */
pmc实现分析
pmc结构体
/* pmc_common.c, 第484-500行 */
struct pmc {
struct config *cfg;
UInteger16 sequence_id; /* 序列号 */
UInteger8 boundary_hops; /* 跳数 */
UInteger8 domain_number; /* 域号 */
UInteger8 transport_specific; /* 传输特定字段 */
struct PortIdentity port_identity; /* 本地端口ID */
struct PortIdentity target; /* 目标端口ID */
struct transport *transport; /* 传输层 */
struct interface *iface; /* 网络接口 */
struct fdarray fdarray; /* 文件描述符数组 */
int zero_length_gets; /* 零长度GET标志 */
...
};
命令解析
/* pmc_common.c, 第419-452行 */
static int parse_action(char *s)
{
int len = strlen(s);
if (0 == strncasecmp(s, "GET", len))
return GET;
else if (0 == strncasecmp(s, "SET", len))
return SET;
else if (0 == strncasecmp(s, "CMD", len))
return COMMAND;
else if (0 == strncasecmp(s, "COMMAND", len))
return COMMAND;
return BAD_ACTION;
}
static int parse_id(char *s)
{
int i, index = BAD_ID, len = strlen(s);
/* 检查完全匹配 */
for (i = 0; i < ARRAY_SIZE(idtab); i++) {
if (strcasecmp(s, idtab[i].name) == 0) {
return i;
}
}
/* 检查前缀匹配 */
for (i = 0; i < ARRAY_SIZE(idtab); i++) {
if (0 == strncasecmp(s, idtab[i].name, len)) {
if (index == BAD_ID)
index = i;
else
return AMBIGUOUS_ID; /* 有歧义 */
}
}
return index;
}
命令解析逻辑:
pmc命令格式:[ACTION] ID [parameters]
示例:
GET CURRENT_DATA_SET
SET PRIORITY1 128
TARGET *
解析步骤:
1. 提取ACTION(可选,默认GET)
2. 提取ID(可缩写)
3. 验证ID有效性
4. 提取参数(如果有)
5. 构造管理消息
6. 发送并等待响应
GET操作处理
/* pmc_common.c, 第163-169行 */
static void do_get_action(struct pmc *pmc, int action, int index, char *str)
{
if (action == GET)
pmc_send_get_action(pmc, idtab[index].code);
else
fprintf(stderr, "%s only allows GET\n", idtab[index].name);
}
GET操作流程:
1. 用户输入:GET CURRENT_DATA_SET
2. 解析:action=GET, id=CURRENT_DATA_SET
3. 调用:pmc_send_get_action(pmc, MID_CURRENT_DATA_SET)
4. 构造管理消息:
- actionField = GET
- managementId = MID_CURRENT_DATA_SET
- 无data字段
5. 发送消息
6. 等待响应
7. 显示结果
SET操作处理
/* pmc_common.c, 第171-404行 - 简化版 */
static void do_set_action(struct pmc *pmc, int action, int index, char *str)
{
switch (action) {
case GET:
pmc_send_get_action(pmc, code);
return;
case SET:
break;
default:
fprintf(stderr, "%s only allows GET or SET\n", idtab[index].name);
return;
}
switch (code) {
case MID_PRIORITY1:
case MID_PRIORITY2:
cnt = sscanf(str, " %*s %*s %hhu", &mtd.val);
if (cnt != 1) {
fprintf(stderr, "%s SET needs 1 value\n", idtab[index].name);
break;
}
pmc_send_set_action(pmc, code, &mtd, sizeof(mtd));
break;
}
}
SET操作示例:
# 设置优先级
pmc -u "SET PRIORITY1 128"
# 设置Grandmaster参数
pmc -u "SET GRANDMASTER_SETTINGS_NP \
clockClass 248 \
clockAccuracy 0xfe \
offsetScaledLogVariance 0xffff \
currentUtcOffset 37 \
leap61 0 \
leap59 0 \
currentUtcOffsetValid 1 \
ptpTimescale 1 \
timeTraceable 1 \
frequencyTraceable 1 \
timeSource 0xa0"
# 订阅事件
pmc -u "SET SUBSCRIBE_EVENTS_NP \
duration 60 \
NOTIFY_PORT_STATE on \
NOTIFY_TIME_SYNC on"
响应显示
/* pmc.c, 第159-682行 - 简化版 */
static void pmc_show(struct ptp_message *msg, FILE *fp)
{
struct management_tlv *mgt;
struct tlv_extra *extra;
struct TLV *tlv;
/* 获取TLV */
extra = TAILQ_FIRST(&msg->tlv_list);
tlv = (struct TLV *) msg->management.suffix;
mgt = (struct management_tlv *) msg->management.suffix;
/* 根据ID显示 */
switch (mgt->id) {
case MID_CURRENT_DATA_SET:
cds = (struct currentDS *) mgt->data;
fprintf(fp, "CURRENT_DATA_SET "
IFMT "stepsRemoved %hd"
IFMT "offsetFromMaster %.1f"
IFMT "meanPathDelay %.1f",
cds->stepsRemoved,
cds->offsetFromMaster / 65536.0,
cds->meanPathDelay / 65536.0);
break;
case MID_TIME_STATUS_NP:
tsn = (struct time_status_np *) mgt->data;
fprintf(fp, "TIME_STATUS_NP "
IFMT "master_offset %" PRId64
IFMT "ingress_time %" PRId64
IFMT "gmPresent %s"
IFMT "gmIdentity %s",
tsn->master_offset,
tsn->ingress_time,
tsn->gmPresent ? "true" : "false",
cid2str(&tsn->gmIdentity));
break;
}
}
实用场景
场景1:诊断同步问题
# 查看当前同步状态
pmc -u "GET CURRENT_DATA_SET"
# 输出:
# stepsRemoved 5
# offsetFromMaster -32768.0
# meanPathDelay 16384.0
# 分析:
# stepsRemoved=5:距离主时钟5跳
# offsetFromMaster=-32768 ns ≈ -32微秒偏差
# meanPathDelay=16384 ns ≈ 16微秒路径延迟
# 查看主时钟信息
pmc -u "GET PARENT_DATA_SET"
# 查看详细状态
pmc -u "GET TIME_STATUS_NP"
# 输出:
# master_offset: -32768
# gmPresent: true
# gmIdentity: 00.1b.19.00.00.00.00.01
场景2:切换主时钟
# 当前主时钟优先级
pmc -u "GET DEFAULT_DATA_SET"
# 输出:
# priority1: 128
# priority2: 128
# 降低当前主时钟优先级
pmc -u "SET PRIORITY1 200"
# 新的主时钟会接管(如果其他时钟优先级更高)
# 或者提高优先级成为主时钟
pmc -u "SET PRIORITY1 100"
场景3:监控端口统计
# 查看端口统计
pmc -u "GET PORT_STATS_NP"
# 输出:
# rx_Sync: 12345
# rx_Follow_Up: 12345
# rx_Announce: 123
# tx_Delay_Req: 12345
# ...
# 分析消息流量
# 查看服务统计
pmc -u "GET PORT_SERVICE_STATS_NP"
# 输出:
# announce_timeout: 0
# sync_timeout: 5
# delay_timeout: 2
# ...
场景4:配置时间属性
# 查看时间属性
pmc -u "GET TIME_PROPERTIES_DATA_SET"
# 输出:
# currentUtcOffset: 37
# leap61: 0
# leap59: 0
# ptpTimescale: 1
# timeSource: 0xa0
# 设置闰秒
pmc -u "SET GRANDMASTER_SETTINGS_NP \
clockClass 6 \
clockAccuracy 0x21 \
offsetScaledLogVariance 0x4e5d \
currentUtcOffset 37 \
leap61 0 \
leap59 1 \
currentUtcOffsetValid 1 \
ptpTimescale 1 \
timeTraceable 1 \
frequencyTraceable 1 \
timeSource 0xa0"
场景5:订阅事件通知
# 订阅端口状态和时间同步事件
pmc -u "SET SUBSCRIBE_EVENTS_NP \
duration 300 \
NOTIFY_PORT_STATE on \
NOTIFY_TIME_SYNC on"
# ptp4l会发送信号消息通知事件
# pmc会显示:
# SIGNALING NOTIFY_PORT_STATE ...
# SIGNALING NOTIFY_TIME_SYNC ...
管理错误处理
错误码
/* tlv.h, 第134-141行 */
#define MID_RESPONSE_TOO_BIG 0x0001 /* 响应太大 */
#define MID_NO_SUCH_ID 0x0002 /* 不存在的ID */
#define MID_WRONG_LENGTH 0x0003 /* 长度错误 */
#define MID_WRONG_VALUE 0x0004 /* 值错误 */
#define MID_NOT_SETABLE 0x0005 /* 不可设置 */
#define MID_NOT_SUPPORTED 0x0006 /* 不支持 */
#define MID_GENERAL_ERROR 0xFFFE /* 一般错误 */
错误处理示例
# 尝试GET不存在的ID
pmc -u "GET NON_EXISTENT_ID"
# 响应:
# MANAGEMENT_ERROR_STATUS
# error: 0x0002 (NO_SUCH_ID)
# 尝试SET只读属性
pmc -u "SET CLOCK_DESCRIPTION ..."
# 响应:
# MANAGEMENT_ERROR_STATUS
# error: 0x0005 (NOT_SETABLE)
# 值超出范围
pmc -u "SET PRIORITY1 300"
# 响应:
# MANAGEMENT_ERROR_STATUS
# error: 0x0004 (WRONG_VALUE)
pmc与ptp4l的通信
UDS通道
# ptp4l启动时创建UDS接口
ptp4l -i eth0 -m -S
# 创建的UDS路径:
# /var/run/ptp4l (读写)
# /var/run/ptp4lro (只读)
# pmc通过UDS连接
pmc -u -s /var/run/ptp4l "GET CURRENT_DATA_SET"
UDS优势:
UDS(Unix Domain Socket)特点:
1. 本地通信
- 不经过网络
- 低延迟
- 高吞吐
2. 安全
- 文件系统权限控制
- 非root用户可使用只读接口
3. 简单
- 不需要IP配置
- 不需要端口配置
适用场景:
- 本地管理
- 监控脚本
- 自动化运维
网络通道
# 通过UDP管理远程设备
pmc -4 -i eth0 -b 1 "GET CURRENT_DATA_SET"
# 注意:
# - boundary_hops决定管理范围
# - 需要网络可达
# - 可能被防火墙阻止
高级用法
脚本化监控
#!/bin/bash
# monitor.sh - PTP监控脚本
while true; do
echo "=== $(date) ==="
# 获取当前状态
pmc -u "GET TIME_STATUS_NP" | grep -E "master_offset|gmIdentity"
# 检查偏差是否过大
offset=$(pmc -u "GET TIME_STATUS_NP" | grep master_offset | awk '{print $2}')
if [ "$offset" -gt 1000000 ]; then
echo "ALERT: Large offset detected: $offset ns"
fi
sleep 10
done
批量配置
#!/bin/bash
# config.sh - 批量配置脚本
# 设置优先级
pmc -u "SET PRIORITY1 128"
pmc -u "SET PRIORITY2 128"
# 设置时钟质量
pmc -u "SET GRANDMASTER_SETTINGS_NP \
clockClass 248 \
clockAccuracy 0xfe \
offsetScaledLogVariance 0xffff \
currentUtcOffset 37 \
leap61 0 \
leap59 0 \
currentUtcOffsetValid 1 \
ptpTimescale 1 \
timeTraceable 0 \
frequencyTraceable 0 \
timeSource 0xa0"
# 订阅事件
pmc -u "SET SUBSCRIBE_EVENTS_NP \
duration 3600 \
NOTIFY_PORT_STATE on \
NOTIFY_TIME_SYNC on"
小结:管理协议的威力
核心概念:
- 管理消息:GET/SET/COMMAND
- Management ID:标识操作对象
- boundary_hops:控制传播范围
pmc工具:
- 交互模式和批处理
- 支持所有标准ID
- LinuxPTP扩展功能
常用操作:
- 状态监控
- 参数配置
- 故障诊断
- 自动化脚本
通信方式:
- UDS:本地管理
- 网络:远程管理
下集预告
管理协议解决了“如何监控和配置“,但PHC与系统时钟如何同步?
下一节,我们将分析phc2sys工具——看看如何实现PHC与系统时钟的同步。
【悬念留给3.11】
ptp4l同步的是PHC硬件时钟。
但应用使用的是系统时钟(CLOCK_REALTIME)。
phc2sys如何连接两个时钟?
它与ptp4l有什么区别和联系?
下一节,揭示phc2sys的秘密。