PTPD 在 QNX 系统上的授时精度验证与误差排查

news2024/11/23 10:09:44

文章目录

    • 0. 引言
    • 1.关键函数实现
    • 2. 验证策略与结果
    • 3. 授时误差的排查与解决
    • 3. 授时误差的排查与解决
    • 4. 结论

0. 引言

PTPD是一种时间同步的开源实现,在不同操作系统上的表现可能存在显著差异。
本文通过在QNX系统上运行PTPD,针对其授时精度进行详细验证,并对出现的误差进行深入排查和分析,旨在提升QNX系统中的时间同步精度。

1.关键函数实现

在QNX系统上运行PTPD进行时间同步时,我们经过一系列调试和优化,采用PTP4L作为主时钟(软时钟)和PTPD2作为从时钟(软时钟)。
在收发PTP event报文时,我们发现原始PTPD代码使用的SO_TIMESTAMP从CMSH_DATA中获取时间戳数据更新周期为8ms,导致0-8ms的误差。为了减少这种误差,我们改为在应用层直接获取当前系统时间,将其作为报文的时间戳。

接收和发送报文时获取系统时间的关键实现如下:

void getTime(TimeInternal *time) {
    struct timespec tp_now;
    if (clock_gettime(CLOCK_REALTIME, &tp_now) < 0) {
        PERROR("clock_gettime() failed, exiting.");
        exit(0);
    }
    time->seconds = tp_now.tv_sec;
    time->nanoseconds = tp_now.tv_nsec;
    return;
}

ssize_t netRecvEvent(Octet *buf, TimeInternal *time, NetPath *netPath, int flags) {
    ssize_t ret = 0;
    struct msghdr msg;
    struct iovec vec[1];
    struct sockaddr_in fromaddr;

#if defined(_QNXNTO) && defined(PTPD_EXPERIMENTAL)
    TimeInternal tmpTime;
    getTime(&tmpTime);
    *time = tmpTime;
#endif

    return ret;
}

ssize_t netSendEvent(Octet *buf, UInteger16 length, NetPath *netPath, const RunTimeOpts *rtOpts, Integer32 destinationAddress, TimeInternal *time) {
    ssize_t ret;
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_port = htons(PTP_EVENT_PORT);

#if defined(_QNXNTO) && defined(PTPD_EXPERIMENTAL)
    TimeInternal tmpTime;
    getTime(&tmpTime);
    *time = tmpTime;
#endif

    return ret;
}

2. 验证策略与结果

为了验证这种时间戳处理策略的效果,我们使用clockdiff脚本测量了主从时钟之间的误差,并通过一系列实验确定了授时精度。实验结果显示,主从时钟之间的offset基本在几十到几百微秒,未超过1ms。这些结果表明在QNX系统上采取的改进方法有效地降低了时间同步的误差。
为进一步验证时间戳更新周期的影响,我们开发了一个简单的C程序so_timestamp.c,不断循环获取SO_TIMESTAMP的值并打印出来。以下是该程序的简化代码示例:

// so_timestamp.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.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 <errno.h>
#include <inttypes.h>
#include <time.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <netinet/tcp.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#ifdef _QNX_
#include <sys/neutrino.h>
#endif
#define log(fmt, ...) printf(fmt "\n", ##__VA_ARGS__);
int create_server_socket() {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        log("cannot create socket");
        return -1;
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = 8080;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    int ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
    if (ret < 0) {
        log("cannot bind socket");
        return -1;
    }
    int optval = 1;
    ret = setsockopt(sock, SOL_SOCKET, SO_TIMESTAMP, &optval, sizeof(optval));
    if (ret < 0) {
        log("cannot setsockopt SO_TIMESTAMP");
        return -1;
    }
    return sock;
}
int destroy_socket(int sock) {
    if (sock < 0) {
        log("invalid socket");
        return -1;
    }
    int ret = close(sock);
    if (ret < 0) {
        log("cannot close socket");
        return -1;
    }
    return 0;
}
int64_t get_so_timestampns(int sock) {
    struct msghdr msg;
    struct iovec iov;
    char cmsgbuf[4096];
    char buf[1024];
    struct cmsghdr *cmsg;
    struct timeval *tv;
    int ret;
    memset(buf, 0, sizeof(buf));
    memset(&msg, 0, sizeof(msg));
    memset(&iov, 0, sizeof(iov));
    memset(cmsgbuf, 0, sizeof(cmsgbuf));
    iov.iov_base = buf;
    iov.iov_len = sizeof(buf);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = cmsgbuf;
    msg.msg_controllen = sizeof(cmsgbuf);
    ret = recvmsg(sock, &msg, 0);
    if (ret < 0) {
        log("cannot recvmsg %s", strerror(errno));
        return -1;
    }
    write(sock, buf, strlen(buf));
    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
#ifdef _QNX_
        if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMP) {
#else
        if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP) {
#endif
            tv = (struct timeval *)CMSG_DATA(cmsg);
            return tv->tv_sec * 1000000000 + tv->tv_usec * 1000;
        }
    }
    log("cannot find SCM_TIMESTAMP");
    return -1;
}
int send_to_sock(int sock, const char* buf, int len) {
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = 8080;
    // localhost
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    int ret = sendto(sock, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr));
    if (ret < 0) {
        log("cannot sendto");
        return -1;
    }
    return 0;
}
int main() {
#ifdef _QNX_
    struct _clockperiod period;
    period.nsec = 10000;
    period.fract = 0;
    ClockPeriod(CLOCK_REALTIME, &period, NULL, 0);
#endif
    int sock = create_server_socket();
    if (sock < 0) {
        return -1;
    }
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(sock, &rfds);
	struct timeval timeOut = {0,0};
    const char* buf = "hello world";
    int64_t sec, msec, usec, nsec;
    int64_t ns = -1;
    int ret = -1;
    for (;;) {
        ret = send_to_sock(sock, buf, strlen(buf));
        if (ret < 0) {
            return -1;
        }
        ret = select(sock + 1, &rfds, NULL, NULL, &timeOut);
        if (ret < 0) {
            log("cannot select");
            return -1;
        }
        ns = get_so_timestampns(sock);
        if (ns < 0) {
            return -1;
        }
        sec = ns / 1000000000;
        msec = (ns % 1000000000) / 1000000;
        usec = (ns % 1000000) / 1000;
        nsec = ns % 1000;
        log("ts: %ld.%03ld.%03ld.%03ld", sec, msec, usec, nsec);
    }
    destroy_socket(sock);
}

以上程序会持续输出时间戳,显示其在一段时间内保持不变,然后突变,突变增量大约为8ms。通过这种方式,我们能直观地观察到时间戳的更新频率和模式,为我们的误差分析提供了实验数据。
在这里插入图片描述

如上图所示,是 clockdiff 测出的授时误差统计直方图。横轴表示授时误差,纵轴表示统计计数。可以看到,从 -8ms 到 0ms 的误差都有,而且分布比较均匀。

3. 授时误差的排查与解决

为了提高这段文字的清晰度和逻辑性,我们可以调整其结构和表述,使其更为精准和易于理解。下面是优化后的版本:


3. 授时误差的排查与解决

在对QNX系统中的PTPD进行授时精度测试时,我们发现存在显著的授时误差,最小误差也达到10毫秒左右。更为严重的是,在计算同步(sync)报文的发送和接收时间差时,我们观察到的时间偏差竟高达几百毫秒,这远远超出了正常范围。初步调查表明,这一问题可能与QNX系统的时钟频率调节接口有关。

进一步的诊断显示,主时钟上的时间误差本身接近几百毫秒。我们使用的SO_TIMESTAMPING机制的更新周期长达8毫秒,这成为误差的主要原因。我们发现,QNX系统在获取时间戳时无法有效触发中断,导致时间戳保持不变,并且系统每次都会进行时钟步进(clock step),从而产生较大的误差。

QNX系统中的clockAdjust接口允许通过设置tick_counttick_nsec_inc来调整系统时钟,具体调整方法如下:

  • 设定每个时钟周期(tick)为10000纳秒。当tick_count设为100,tick_nsec_inc设为10时,在接下来的100个周期中,每个周期时长会增加至10010纳秒,从而加速时钟。
  • 如果需要减缓时钟速度,则将tick_nsec_inc设置为负值。

以下是调整时钟的示例代码:

printf("QNX: adj: %.9f, dt: %.9f, ticks per dt: %d, inc per tick %d\n", adj, ptpClock->servo.dT, clockadj.tick_count, clockadj.tick_nsec_inc);

if (ClockAdjust(CLOCK_REALTIME, &clockadj, NULL) < 0) {
    printf("QNX: failed to call ClockAdjust: %s\n", strerror(errno));
}

clockAdjust操作基于时钟周期(tick),其最小分辨率为10000纳秒。这个接口的灵活性使得我们能够通过调整时钟的运行速度来尝试修正授时误差。

通过分析,我们确认QNX系统在获取时间戳时的局限性是主要误差来源。这要求我们进一步优化时钟管理接口或寻求硬件支持以改进授时精度。

4. 结论

通过对QNX系统和Linux系统上运行PTPD的对比分析,我们确认了QNX系统对系统时钟频率调节的局限性是影响授时精度的主要因素。采用相同代码编译的ARM Linux版本PTPD,在Linux系统上授时精度达到几十微秒,进一步证明了问题所在。

通过分析和测试,发现QNX系统时钟频率调节的局限性对PTPD授时精度有显著影响,为了进一步提高授时精度,需要进一步优化时钟管理接口或硬件支持。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1953599.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL:增删改查、临时表、授权相关示例

目录 概念 数据完整性 主键 数据类型 精确数字 近似数字 字符串 二进制字符串 日期和时间 MySQL常用语句示例 SQL结构化查询语言 显示所有数据库 显示所有表 查看指定表的结构 查询指定表的所有列 创建一个数据库 创建表和列 插入数据记录 查询数据记录 修…

游泳馆押金原路退回源码解析

<dl class"list "><dd class"address-wrapper dd-padding"><div class"address-container"><cyberdiv style"color:#f0efed;font-size:14px;float:right;position:absolute;right:10px;top: 2px;">●●●<…

正点原子imx6ull-mini-linux字符驱动模板(0)

1&#xff1a;驱动模块的加载和卸载 module_init(xxx_init); //注册模块加载函数 module_exit(xxx_exit); //注册模块卸载函数 1.1&#xff1a;新建一个用于存放linux驱动的目录,当然这个目录位置没有要求。创建要加载的模块chrbase.c cd ~/linux/drivers makdir linux_driv…

【安卓】Android Studio简易计算器(实现加减乘除,整数小数运算,正数负数运算)

目录 前言 运算效果 一、创建一个新的项目 二、编写xml文件&#xff08;计算器显示页面&#xff09; 三、实现Java运算逻辑 ​编辑 完整代码 xml文件代码&#xff1a; Java文件代码&#xff1a; 注&#xff1a; 前言 随着移动互联网的普及&#xff0c;手机应用程序已…

fetchApi === 入门篇

目录 fetch 基本认知 fetch 如何使用 Response对象&#xff08;了解&#xff09; 常见属性 常见方法 fetch 配置参数 fetch发送post请求 fetch 函数封装 fetch 实战 - 图书管理案例 渲染功能 添加功能 删除数据 完整代码 fetch 基本认知 思考&#xff1a; 以前开发…

PCIe总线-Linux内核PCIe软件框架分析(十一)

1.简介 Linux内核PCIe软件框架如下图所示&#xff0c;按照PCIe的模式&#xff0c;可分为RC和EP软件框架。RC的软件框架分为五层&#xff0c;第一层为RC Controller Driver&#xff0c;和RC Controller硬件直接交互&#xff0c;不同的RC Controller&#xff0c;其驱动实现也不相…

永结无间Ⅰ--基于 LLM 的 AGI

在过去几周&#xff0c;传奇人物 Francois Chollet 发起的ARC 挑战引起了不小的轰动。这项挑战让很多 AI 研究人员感到困惑&#xff0c;它表明了所有 AI 系统都存在泛化能力不足的问题。ARC 上上一次 SOTA AI 的准确率约为 34%&#xff0c;而在同一挑战中&#xff0c;Mechanica…

【React】Context机制跨层传递数据详解

文章目录 一、React Context的概念二、创建和使用Context1. 创建Context2. 使用Provider提供数据3. 使用Consumer获取数据 三、使用useContext Hook四、动态更新Context值五、Context的实际应用场景1. 主题切换2. 用户认证状态 六、注意事项 在开发React应用时&#xff0c;我们…

【C语言】深入探讨数组传参

一、数组传参简介 在C语言中&#xff0c;数组传参是一个常见的操作&#xff0c;尤其是在处理大量数据或需要多次访问相同数据集时。理解如何传递数组以及这些方法之间的差异是编写高效和安全代码的关键。在这篇博客中&#xff0c;我们将详细讨论C语言中数组传参的几种常见方法&…

【网络】应用层协议(自定义协议)(序列和反序列化)

应用层协议&#xff08;自定义协议&#xff09;&#xff08;序列和反序列化&#xff09; 一、引言--应用层的使用二、应用层1、网络版本计算器&#xff08;1&#xff09;协议定制和序列反序列化&#xff08;2&#xff09;网络版计算器协议定制i、封装有效载荷&#xff08;默认上…

数据结构——单链表OJ题(上)

目录 一、移除链表元素 1.思路 2.注意 3.解题 二、反转链表 思路1&#xff1a;三指针翻转法 &#xff08;1&#xff09;注意 &#xff08;2&#xff09;解题 思路2&#xff1a;头插法 &#xff08;1&#xff09;注意 &#xff08;2&#xff09;解题 三、链表的中间结…

depcheck 前端依赖检查

介绍 depcheck 是一款用于检测项目中 未使用依赖项 的工具。 depcheck 通过扫描项目文件&#xff0c;帮助你找出未被引用的依赖&#xff0c;从而优化项目。 优势&#xff1a; 简单易用: 仅需几个简单的命令&#xff0c;就能够扫描并列出未使用的依赖项&#xff0c;让你快速了…

The Schematic workflow failed. See above.

在使用 ng new 新建Angular项目的时候会报一个错误&#xff1a;The Schematic workflow failed. See above. 解决办法&#xff1a; 只需要在后面加上 --skip-install 参数&#xff0c;就不会报错了。 ng new myapp --skip-install

打工人电脑里都需要的远程控制软件有哪些?这4款不能错过

不巧前几天台风&#xff0c;实在没办法到公司&#xff0c;但是项目还得继续&#xff0c;这时候远程控制电脑的技巧可谓是帮了我大忙了。不知道平常的你偶尔会不会也需要远程控制电脑的操作&#xff0c;如果有就继续看下去吧。 1.向日魁远程控制 直通车>>https://down.o…

AJAX-Promise 详解

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 前言 一、Promise基本概念 1.1 定义 1.2 状态 1.3 构造函数 二、Promise基本用法 2.1 then() 2.2 ca…

ThinkPHP一对一关联模型的运用(ORM)

一、序言 最近在写ThinkPHP关联模型的时候一些用法总忘&#xff0c;我就想通过写博客的方式复习和整理下一些用法。 具体版本&#xff1a; topthink/framework&#xff1a;6.1.4topthink/think-orm&#xff1a;2.0.61 二、实例应用 1、一对一关联 1.1、我先设计了两张表&#x…

根据题意写出完整的css,html和js代码【购物车模块页面及功能实现】

🏆本文收录于《CSDN问答解惑-专业版》专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!! 问题描述 根据题意写出完…

基于微信小程序+SpringBoot+Vue的社区超市管理系统(带1w+文档)

基于微信小程序SpringBootVue的社区超市管理系统(带1w文档) 基于微信小程序SpringBootVue的社区超市管理系统(带1w文档) 为了让商品信息的管理模式进行升级&#xff0c;也为了更好的维护商品信息&#xff0c;社区超市管理系统的开发运用就显得很有必要&#xff0c;因为它不仅可…

C# 植物大战僵尸

Winform 版本开发 高效率、流畅植物大战僵尸 git地址&#xff1a;冯腾飞/植物大战僵尸

go语言day19 使用git上传包文件到github Gin框架入门

git分布式版本控制系统_git切换head指针-CSDN博客 获取请求参数并和struct结构体绑定_哔哩哔哩_bilibili &#xff08;gin框架&#xff09; GO: 引入GIn框架_go 引入 gin-CSDN博客 使用git上传包文件 1&#xff09;创建一个github账户&#xff0c;进入Repositories个人仓…