Files
Inbox/系统基座文件/2/2.6/2.6.2 多级数据打点策略 (Multi-Level Timestamping Strategy).md
2025-12-11 07:24:36 +08:00

6.6 KiB
Raw Permalink Blame History

tags, date created, date modified
tags date created date modified
星期三, 十一月 26日 2025, 9:49:46 晚上 星期三, 十一月 26日 2025, 10:06:18 晚上

2.6.2 多级数据打点策略 (Multi-Level Timestamping Strategy)

基线核心宗旨“As Early As Possible (越早越好)”。 时间戳必须尽可能在物理层PHY或链路层MAC生成以消除操作系统中断调度、驱动处理和协议栈拷贝带来的不定长抖动Jitter


1. 约束输入与对齐 (Constraints & Alignment)

基于 2.1.1 审计结果(网迅 WX1860AL4 网卡)和 2.6.1 确立的时钟基线,我们需要对齐以下约束:

  1. 硬件能力 (Hardware Cap)
    • 网迅 NIC:需确认驱动是否支持 SO_TIMESTAMPING 接口读取硬件 RX 时间戳。通常企业级网卡Intel X710/Mellanox均支持国产网卡需实测验证。若不支持需回退到内核软打点。
  2. 协议支持 (Protocol)
    • UDP 数据包本身不携带“发送时间”(除非应用层协议写了)。因此我们依赖的是接收端打点 (Ingress Timestamping)
  3. 数据结构关联
    • 生成的纳秒级时间戳必须填入 RawDataPacket 的 Header并最终映射到 Protobuf 的 timestamp_us

2. 权衡分析与选项呈现 (Trade-off Matrix)

我们按照“离物理线路的距离”定义三个打点层级:

选项 A. 用户态打点 (User-space) B. 内核软打点 (Kernel SW) (基线) C. 硬件硬打点 (Hardware HW) (理想)
生成位置 recvmsg() 返回后的应用层代码。 网卡驱动收到中断SKB (Socket Buffer) 创建时刻。 网卡 PHY 芯片接收到前导码Preamble时刻。
抖动来源 极大。受 CPU 调度、软中断处理、内存拷贝影响,抖动可达 10-100µs。 中等。受中断响应延迟影响,抖动约 1-10µs。 极小。几乎无抖动 (< 100ns)。
PTP 依赖 依赖系统时钟 (CLOCK_REALTIME)。 依赖系统时钟。 依赖网卡 PHC (PTP Hardware Clock) 与系统时钟的同步 (phc2sys)。
实现复杂度 低。调用 Clock::now() 即可。 中。需配置 Socket 选项并解析辅助数据 (CMSG)。 高。需硬件支持,且需处理 PHC 到 UTC 的转换。
适用场景 功能调试、非实时业务。 当前国产环境最稳妥的基线 相控阵雷达、高频交易。

3. 基线确立与实施规范

考虑到国产网卡驱动的成熟度风险,我们确立 “优先硬件,兜底内核,严禁用户态” 的分级策略。

3.1 策略分级定义 (Hierarchy Definition)

  • Priority 1 (HW): 尝试启用 SO_TIMESTAMPING_RX_HARDWARE
    • 如果网卡支持,这是绝对真值。
    • 注意:硬件时间戳通常是 PHC 时间TAI需在用户态根据 ptp4l 的 offset 转换为 UTC。
  • Priority 2 (SW): 回退到 SO_TIMESTAMPNS (内核接收时间)。
    • 这是工程基线。它反映了数据包进入 Linux 网络栈的第一时间点,消除了应用程序调度的延迟。
  • Priority 3 (App): 严禁作为生产标准。仅在上述两者皆失败时,使用 2.6.1 定义的 HighPrecisionClock::now() 补救,并标记数据质量为 LOW_PRECISION

3.2 实现规范:辅助数据解析 (CMSG Parsing)

DataReceiver 的 I/O 线程中,必须改造 recvmmsg 的调用方式,以提取内核附带的时间戳元数据。

  • Socket 配置

    int flags = SO_TIMESTAMPNS; // 请求内核软件时间戳 (纳秒级)
    // 如果确认网卡支持硬件打点,则改为:
    // int flags = SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE;
    setsockopt(sockfd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags));
    
  • 接收逻辑 (核心代码范式)

    void UdpReceiver::receiveLoop() {
        struct mmsghdr msgs[BATCH_SIZE];
        struct iovec iovecs[BATCH_SIZE];
        char cmsg_buf[BATCH_SIZE][256]; // 存放辅助数据的缓冲区
    
        // … 初始化 msgs, iovecs, cmsg …
    
        int n = recvmmsg(sockfd, msgs, BATCH_SIZE, 0, nullptr);
    
        for (int i = 0; i < n; ++i) {
            uint64_t timestamp_ns = 0;
    
            // 解析辅助数据 (Control Message)
            struct cmsghdr *cmsg;
            for (cmsg = CMSG_FIRSTHDR(&msgs[i].msg_hdr); cmsg; cmsg = CMSG_NXTHDR(&msgs[i].msg_hdr, cmsg)) {
                // 优先提取硬件时间戳,若无则提取软件时间戳
                if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPNS) {
                    struct timespec *ts = (struct timespec *)CMSG_DATA(cmsg);
                    timestamp_ns = ts->tv_sec * 1000000000ULL + ts->tv_nsec;
                    break; 
                }
            }
    
            // 兜底:如果内核没给时间戳,立刻用 TSC 软时钟打点
            if (timestamp_ns == 0) {
                timestamp_ns = HighPrecisionClock::now();
            }
    
            // 固化:写入 RawDataPacket Header
            // 这一刻起,这个时间戳就是数据的"法定出生时间"
            auto packet = MakePacket(std::move(payload), seq_id);
            packet.header.timestamp_us = timestamp_ns / 1000; 
            // … 推送队列 …
        }
    }
    

3.3 全链路时间戳传递 (Propagation)

这个“出生时间戳” (timestamp_us) 必须在系统中神圣不可侵犯:

  • 接收端 (DataReceiver):生成并写入 RawDataPacket.header
  • 处理端 (SignalProcessor):继承该时间戳。虽然 FFT 处理是在 5ms 后进行的,但数据的物理意义依然是“那个时刻的回波”。
  • 输出端 (DisplayController):将该时间戳写入 Protobuf 的 timestamp_us 字段发送给显控。
    • 注意:显控端看到的延迟 = Now() - timestamp_us。这个延迟包含了:内核排队 + 信号处理耗时 + 航迹关联耗时 + 序列化耗时 + 网络传输耗时。这正是我们想要的真实物理延迟

总结2.6.2 基线图谱

维度 核心基线 技术细节
打点源 内核软打点 (SO_TIMESTAMPNS) 兼顾精度与兼容性,消除用户态抖动。
理想源 硬件打点 (HW RX) 若网卡支持,优先升级至此 (需实测)。
获取方式 recvmmsg + CMSG 从 Socket 辅助数据中提取,而非调用 time()
语义 生成时间 (Generation Time) 代表信号到达系统的物理时刻,全链路透传,禁止修改。