Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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的秘密。