<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>802.1Qbv on Tech Snippets - 嵌入式技术笔记</title>
    <link>https://tech-snippets.xyz/tags/802.1qbv/</link>
    <description>Recent content in 802.1Qbv on Tech Snippets - 嵌入式技术笔记</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Wed, 03 Jun 2026 19:00:00 +0800</lastBuildDate>
    <atom:link href="https://tech-snippets.xyz/tags/802.1qbv/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>TSN 时间敏感网络实战：为嵌入式实时系统打造可证明的确定性以太网</title>
      <link>https://tech-snippets.xyz/posts/tsn-embedded-realtime-ethernet-guide/</link>
      <pubDate>Wed, 03 Jun 2026 19:00:00 +0800</pubDate>
      <guid>https://tech-snippets.xyz/posts/tsn-embedded-realtime-ethernet-guide/</guid>
      <description>前言：实时系统为什么越来越离不开网络 过去谈嵌入式实时系统，很多工程师第一反应是中断延迟、任务优先级、定时器精度和 RTOS 调度器。只要 CPU 够快、ISR 写得短、关键任务优先级足够高，系统就能在板子内部把时序控制住。但这几年现场设备的形态已经变了：电机驱动、视觉传感器、PLC、边缘 AI 控制器、远程 IO、机械臂关节模块不再挤在同一块 PCB 上，它们通过以太网组成一个分布式控制系统。此时，实时性的瓶颈不只在内核，也在网络。
传统以太网追求“尽力而为”：帧能发就发，交换机按队列转发，拥塞时排队，严重时丢包。它很适合办公网络和普通数据采集，却不适合“每 1 ms 必须收到一帧传感器数据，控制器计算 200 μs，执行器再在指定窗口更新 PWM”这种硬约束场景。工业现场常见做法是上专用实时以太网，例如 EtherCAT、PROFINET IRT、SERCOS III 等。它们很好用，但生态相对封闭，协议栈、硬件、工具链往往绑定供应商。
TSN（Time-Sensitive Networking，时间敏感网络）试图解决的正是这个问题：在标准以太网上增加时间同步、流量整形、门控队列、帧抢占、流过滤和集中配置等能力，让普通以太网具备可分析、可规划、可隔离的实时传输能力。它不是一个单独协议，而是一组 IEEE 802.1 标准的组合。对于做嵌入式、RTOS、工业控制和边缘计算的团队来说，TSN 的价值不在于“把网络变快”，而在于把网络行为从概率问题变成工程问题：什么时候发、能不能进队列、最多排多久、丢包后如何隔离，都可以在设计阶段说清楚。
本文以实战视角梳理 TSN 在嵌入式实时系统中的落地方式。我们会从基础概念讲起，重点放在 802.1AS 时间同步、802.1Qbv 门控调度、802.1Qci 流过滤、Linux tc 配置和端到端时延预算。文章不会把标准条文逐条翻译，而是站在系统工程师角度回答几个更实际的问题：什么时候需要 TSN？它和 RTOS / PREEMPT_RT 如何配合？一个 1 ms 控制周期应该怎样拆成网络窗口？调试时应该看哪些指标？
一、TSN 解决的不是带宽，而是确定性 很多项目第一次遇到网络实时性问题时，会本能地升级链路：百兆换千兆，千兆换 2.5G，交换机换更大缓存。短期看，平均延迟确实下降了，但最坏情况不一定变好。原因很简单：实时系统关心的是 deadline miss，而不是平均吞吐。一个 1 ms 周期任务，前 999 次都在 80 μs 内收到帧，只有第 1000 次因为低优先级大包阻塞了 700 μs，控制回路仍然可能抖一下。
确定性网络最核心的指标通常有四个：
有界时延：从发送端应用层写入数据，到接收端应用层读到数据，最大时间可以估算。 低抖动：相邻周期到达时间的偏差可控，不能依赖“运气好”。 流隔离：关键控制流不被日志、视频、OTA、Web 配置页面等普通流量拖累。 可诊断性：当时序变差时，能定位是时钟漂移、门控窗口配置错误、驱动队列阻塞，还是应用线程没有及时取包。 TSN 的设计思路是把网络传输拆成两个层面：首先让所有节点拥有同一套时间基准，然后让交换机和网卡在同一时间基准下按照计划打开或关闭队列。这样一来，控制帧不是“抢着发”，而是在预留窗口里发；普通流量不是“完全禁止”，而是被安排在不影响关键流的时间片内发送。</description>
      <content:encoded><![CDATA[<h2 id="前言实时系统为什么越来越离不开网络">前言：实时系统为什么越来越离不开网络</h2>
<p>过去谈嵌入式实时系统，很多工程师第一反应是中断延迟、任务优先级、定时器精度和 RTOS 调度器。只要 CPU 够快、ISR 写得短、关键任务优先级足够高，系统就能在板子内部把时序控制住。但这几年现场设备的形态已经变了：电机驱动、视觉传感器、PLC、边缘 AI 控制器、远程 IO、机械臂关节模块不再挤在同一块 PCB 上，它们通过以太网组成一个分布式控制系统。此时，实时性的瓶颈不只在内核，也在网络。</p>
<p>传统以太网追求“尽力而为”：帧能发就发，交换机按队列转发，拥塞时排队，严重时丢包。它很适合办公网络和普通数据采集，却不适合“每 1 ms 必须收到一帧传感器数据，控制器计算 200 μs，执行器再在指定窗口更新 PWM”这种硬约束场景。工业现场常见做法是上专用实时以太网，例如 EtherCAT、PROFINET IRT、SERCOS III 等。它们很好用，但生态相对封闭，协议栈、硬件、工具链往往绑定供应商。</p>
<p>TSN（Time-Sensitive Networking，时间敏感网络）试图解决的正是这个问题：在标准以太网上增加时间同步、流量整形、门控队列、帧抢占、流过滤和集中配置等能力，让普通以太网具备可分析、可规划、可隔离的实时传输能力。它不是一个单独协议，而是一组 IEEE 802.1 标准的组合。对于做嵌入式、RTOS、工业控制和边缘计算的团队来说，TSN 的价值不在于“把网络变快”，而在于把网络行为从概率问题变成工程问题：什么时候发、能不能进队列、最多排多久、丢包后如何隔离，都可以在设计阶段说清楚。</p>
<p>本文以实战视角梳理 TSN 在嵌入式实时系统中的落地方式。我们会从基础概念讲起，重点放在 802.1AS 时间同步、802.1Qbv 门控调度、802.1Qci 流过滤、Linux <code>tc</code> 配置和端到端时延预算。文章不会把标准条文逐条翻译，而是站在系统工程师角度回答几个更实际的问题：什么时候需要 TSN？它和 RTOS / PREEMPT_RT 如何配合？一个 1 ms 控制周期应该怎样拆成网络窗口？调试时应该看哪些指标？</p>
<p><img alt="TSN 在嵌入式实时系统中的端到端架构" loading="lazy" src="/images/tsn-embedded-realtime-architecture.svg"></p>
<h2 id="一tsn-解决的不是带宽而是确定性">一、TSN 解决的不是带宽，而是确定性</h2>
<p>很多项目第一次遇到网络实时性问题时，会本能地升级链路：百兆换千兆，千兆换 2.5G，交换机换更大缓存。短期看，平均延迟确实下降了，但最坏情况不一定变好。原因很简单：实时系统关心的是 deadline miss，而不是平均吞吐。一个 1 ms 周期任务，前 999 次都在 80 μs 内收到帧，只有第 1000 次因为低优先级大包阻塞了 700 μs，控制回路仍然可能抖一下。</p>
<p>确定性网络最核心的指标通常有四个：</p>
<ol>
<li><strong>有界时延</strong>：从发送端应用层写入数据，到接收端应用层读到数据，最大时间可以估算。</li>
<li><strong>低抖动</strong>：相邻周期到达时间的偏差可控，不能依赖“运气好”。</li>
<li><strong>流隔离</strong>：关键控制流不被日志、视频、OTA、Web 配置页面等普通流量拖累。</li>
<li><strong>可诊断性</strong>：当时序变差时，能定位是时钟漂移、门控窗口配置错误、驱动队列阻塞，还是应用线程没有及时取包。</li>
</ol>
<p>TSN 的设计思路是把网络传输拆成两个层面：首先让所有节点拥有同一套时间基准，然后让交换机和网卡在同一时间基准下按照计划打开或关闭队列。这样一来，控制帧不是“抢着发”，而是在预留窗口里发；普通流量不是“完全禁止”，而是被安排在不影响关键流的时间片内发送。</p>
<p>这和 RTOS 中的时间触发系统很像。事件触发系统强调响应外部事件，时间触发系统则强调所有动作按时间表执行。TSN 可以理解为把时间触发思想扩展到了以太网链路上。</p>
<h2 id="二tsn-标准族里最常用的几块拼图">二、TSN 标准族里最常用的几块拼图</h2>
<p>TSN 标准很多，刚接触时容易被编号吓住。实际项目中，初期最值得关注的是下面几类能力。</p>
<h3 id="1-8021as让所有设备对齐时间">1. 802.1AS：让所有设备对齐时间</h3>
<p>802.1AS 是 TSN 场景下的时间同步标准，基于 gPTP（generalized Precision Time Protocol）。它的目标是让传感器、交换机、控制器、执行器共享高精度时间。没有统一时间，后面的门控调度就无从谈起。</p>
<p>在 Linux 上，常见工具是 <code>linuxptp</code> 套件中的 <code>ptp4l</code> 和 <code>phc2sys</code>。前者负责 PTP 协议同步，后者负责把网卡硬件时钟 PHC（PTP Hardware Clock）和系统时钟对齐。硬件是否支持时间戳很关键，如果只有软件时间戳，几十微秒甚至更大的抖动很常见；如果网卡、PHY、驱动和交换机都支持硬件时间戳，亚微秒级同步才有工程意义。</p>
<h3 id="2-8021qbv按时间打开队列的门">2. 802.1Qbv：按时间打开队列的门</h3>
<p>802.1Qbv 的核心是 TAS（Time-Aware Shaper，时间感知整形器）。交换机或网卡有多个发送队列，每个队列对应不同优先级。Qbv 会给每个队列配置一个 GCL（Gate Control List，门控列表）：在某个时间窗口打开控制队列，在另一个窗口打开普通队列。门关着时，该队列即使有包也不能发。</p>
<p>例如一个 1 ms 周期可以这样规划：</p>
<ul>
<li>0 ~ 50 μs：同步和保护窗口；</li>
<li>50 ~ 250 μs：传感器上报；</li>
<li>250 ~ 450 μs：控制器下发执行器命令；</li>
<li>450 ~ 1000 μs：普通流量、诊断、日志、Web 服务。</li>
</ul>
<p>这样的窗口不是拍脑袋定的，需要结合链路速率、最大帧长、交换机转发延迟、应用任务执行时间和安全裕量计算。后文会给出一个简单的预算脚本。</p>
<h3 id="3-8021qci别让异常流量冲垮实时窗口">3. 802.1Qci：别让异常流量冲垮实时窗口</h3>
<p>Qbv 解决“什么时候发”，Qci 解决“哪些流能进来”。在工业现场，错误配置、固件 bug、异常广播风暴都可能让某个设备突然以高频率发包。没有过滤时，即便关键队列有高优先级，交换机 CPU、端口缓存或下游接收端仍可能被拖垮。Qci 提供按流过滤、计量和门控能力，可以限制某条流的最大速率、最大帧长和合法时间窗口。</p>
<h3 id="4-8021qbu--8023br减少大包阻塞">4. 802.1Qbu / 802.3br：减少大包阻塞</h3>
<p>即使控制帧优先级很高，如果一个 1500 字节普通以太网帧已经开始在百兆链路上传输，它仍会占用约 120 μs 的线速时间。对于 1 ms 控制周期，这已经不小。帧抢占允许高优先级帧打断低优先级帧，降低非实时大包带来的阻塞时间。不过它要求链路两端硬件都支持，落地时要检查 PHY、MAC 和交换机规格。</p>
<h2 id="三一个典型嵌入式-tsn-系统长什么样">三、一个典型嵌入式 TSN 系统长什么样</h2>
<p>一个实际可落地的 TSN 系统通常包含四类节点。</p>
<p>第一类是传感器节点，例如编码器、力矩传感器、IMU、相机触发模块。它们要在采样时打时间戳，并在指定窗口把数据发出去。这里不一定跑 Linux，小 MCU + 支持 TSN 的以太网 MAC 也可以。</p>
<p>第二类是边缘控制器，可能是 ARM Cortex-A SoC、x86 工控机，也可能是带实时协处理器的异构平台。控制器一边运行实时控制任务，一边通过 TSN 接收传感器数据并下发命令。操作系统可以是 PREEMPT_RT Linux，也可以是 RTOS，还可以是 Linux + MCU 的双系统结构。</p>
<p>第三类是 TSN 交换机。它不仅转发帧，还参与时间同步、门控调度和流过滤。很多项目失败不是因为应用代码写错，而是交换机只支持普通 QoS，不支持完整 Qbv / Qci；或者支持标准但配置工具封闭，无法和自研系统集成。</p>
<p>第四类是配置与诊断系统。TSN 项目不能只靠“能 ping 通”验收，必须长期记录时钟偏差、丢包、队列占用、周期抖动、门控命中情况。生产环境里，诊断能力往往比跑通 demo 更重要。</p>
<p>（第一部分完，约2300字）</p>
<h2 id="四从需求开始先写清楚实时约束">四、从需求开始：先写清楚实时约束</h2>
<p>做 TSN 方案前，最忌讳直接打开交换机配置界面，开始填 VLAN、优先级和队列。更稳妥的做法是先把业务约束写成一张表。每条实时流至少要明确下面几个字段：</p>
<table>
<thead>
<tr>
<th>字段</th>
<th style="text-align:right">示例</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>Flow ID</td>
<td style="text-align:right"><code>sensor_to_controller</code></td>
<td>流名称，后续配置和日志都用它</td>
</tr>
<tr>
<td>周期</td>
<td style="text-align:right">1 ms</td>
<td>多久发送一次</td>
</tr>
<tr>
<td>载荷大小</td>
<td style="text-align:right">64 bytes</td>
<td>不含以太网头的业务数据</td>
</tr>
<tr>
<td>Deadline</td>
<td style="text-align:right">250 μs</td>
<td>从采样到控制器收到的最大允许时间</td>
</tr>
<tr>
<td>优先级</td>
<td style="text-align:right">VLAN PCP 5</td>
<td>映射到 TSN 队列</td>
</tr>
<tr>
<td>容忍丢包</td>
<td style="text-align:right">连续 1 帧</td>
<td>决定是否需要冗余链路或安全策略</td>
</tr>
<tr>
<td>时间戳位置</td>
<td style="text-align:right">传感器采样点</td>
<td>用来计算端到端延迟</td>
</tr>
</tbody>
</table>
<p>这张表看起来普通，但它能避免很多后期争论。比如控制算法工程师说“传感器数据要实时”，网络工程师会追问：实时是 50 μs、250 μs 还是 2 ms？是平均值还是最坏值？是否允许偶发丢一帧？如果没有这些数字，任何 TSN 配置都只是经验主义。</p>
<p>假设我们有一个简化控制系统：传感器每 1 ms 上报一次 96 字节数据，控制器要在 200 μs 内收到；控制器计算最多 180 μs，然后向执行器发送 64 字节命令；执行器必须在周期结束前 100 μs 拿到命令。链路是 1000BASE-T，经过一个 TSN 交换机。此时可以把周期切成三段：传感器上行窗口、控制计算窗口、执行器下行窗口，中间再留出保护时间。</p>
<h2 id="五端到端时延预算别忘了线速时间和保护带">五、端到端时延预算：别忘了线速时间和保护带</h2>
<p>很多软件工程师估算网络时延时，只看应用层收发时间，忽略以太网帧在线路上的传输时间。以 100 Mbps 链路为例，一个最大长度以太网帧加上前导码、帧间隔等开销，实际占用线速时间约 123 μs。即便是千兆链路，也约 12 μs。对于普通应用这点时间可以忽略，但对 250 μs deadline 的控制流，必须算进去。</p>
<p>一个粗略的单跳延迟模型可以写成：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">T_end_to_end = T_app_tx + T_qdisc + T_wire_tx + T_switch + T_wire_rx + T_driver_rx + T_app_rx
</span></span></code></pre></div><p>其中最难控制的是 <code>T_qdisc</code> 和 <code>T_switch</code>。普通以太网中，它们取决于排队和拥塞；TSN 中，我们希望通过门控窗口把它们限制在可预测范围内。</p>
<p>窗口规划时通常需要考虑三类裕量：</p>
<ol>
<li><strong>时钟同步误差</strong>：如果两个节点时钟最大偏差为 ±500 ns，窗口边界至少要留出对应裕量。</li>
<li><strong>帧阻塞时间</strong>：如果不支持帧抢占，低优先级大帧可能已经占线，需要 guard band。</li>
<li><strong>软件唤醒抖动</strong>：Linux 用户态程序即使在 PREEMPT_RT 下也会有调度抖动，实时线程需要提前准备好数据。</li>
</ol>
<p>一个简单经验是：不要把窗口设计得刚好够用。实验室里 60 μs 可跑通的窗口，到现场加上温度、负载、驱动版本和异常流量，可能就不稳。工程上宁愿把第一个版本做得保守一些，先验证可诊断性，再逐步压缩窗口。</p>
<h2 id="六linux-上的基础准备确认硬件时间戳">六、Linux 上的基础准备：确认硬件时间戳</h2>
<p>如果边缘控制器使用 Linux，第一步不是配置 Qbv，而是确认网卡是否支持硬件时间戳。可以用 <code>ethtool</code> 查看：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ethtool -T eth0
</span></span></code></pre></div><p>理想情况下，你会看到类似能力：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Capabilities:
</span></span><span class="line"><span class="cl">    hardware-transmit
</span></span><span class="line"><span class="cl">    hardware-receive
</span></span><span class="line"><span class="cl">    hardware-raw-clock
</span></span><span class="line"><span class="cl">PTP Hardware Clock: 0
</span></span><span class="line"><span class="cl">Hardware Transmit Timestamp Modes:
</span></span><span class="line"><span class="cl">    on
</span></span><span class="line"><span class="cl">Hardware Receive Filter Modes:
</span></span><span class="line"><span class="cl">    ptpv2-event
</span></span></code></pre></div><p>如果这里只显示 software timestamp，TSN 不是完全不能做，但时间同步精度和可证明性会大打折扣。很多开发板的 SoC MAC 支持 PTP，但设备树、驱动或内核配置没有打开；也有些 USB 网卡根本不支持硬件时间戳。项目早期一定要把这件事查清楚，否则后面调 <code>ptp4l</code> 只会浪费时间。</p>
<p>安装工具：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt install linuxptp ethtool iproute2
</span></span></code></pre></div><p>启动 gPTP 时，交换机或某个主时钟设备会成为 grandmaster。测试环境中可以先让控制器作为主时钟：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo ptp4l -i eth0 -m -2
</span></span><span class="line"><span class="cl">sudo phc2sys -s eth0 -c CLOCK_REALTIME -w -m
</span></span></code></pre></div><p><code>ptp4l</code> 输出里最值得看的是 <code>offset</code>、<code>freq</code> 和 <code>path delay</code>。如果 offset 长期在几十纳秒到几百纳秒内波动，说明硬件时间戳链路基本正常；如果动不动跳到几十微秒，要检查交换机是否透传 PTP、网卡是否真的启用了硬件时间戳、链路中是否混入了不支持 TSN 的普通设备。</p>
<h2 id="七用-linux-tc-taprio-配置-qbv-门控队列">七、用 Linux <code>tc taprio</code> 配置 Qbv 门控队列</h2>
<p>Linux 中配置 802.1Qbv 常用 <code>tc taprio</code>。它会把不同优先级流量映射到不同发送队列，并按照时间表打开队列。下面是一个用于理解的示例，不建议不改参数直接上生产：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo tc qdisc replace dev eth0 parent root handle <span class="m">100</span> taprio <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  num_tc <span class="m">3</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  map <span class="m">2</span> <span class="m">2</span> <span class="m">1</span> <span class="m">0</span> <span class="m">2</span> <span class="m">2</span> <span class="m">2</span> <span class="m">2</span> <span class="m">2</span> <span class="m">2</span> <span class="m">2</span> <span class="m">2</span> <span class="m">2</span> <span class="m">2</span> <span class="m">2</span> <span class="m">2</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  queues 1@0 1@1 1@2 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  base-time <span class="m">0</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  sched-entry S <span class="m">01</span> <span class="m">50000</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  sched-entry S <span class="m">02</span> <span class="m">200000</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  sched-entry S <span class="m">04</span> <span class="m">750000</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  flags 0x2 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  clockid CLOCK_TAI
</span></span></code></pre></div><p>这段配置表达的意思是：创建 3 个 traffic class；优先级映射中，某些 PCP 会进入实时队列；一个 1 ms 周期被拆成 50 μs、200 μs、750 μs 三段；每段只打开对应队列。<code>01</code>、<code>02</code>、<code>04</code> 是门控位图，不同 bit 对应不同队列。</p>
<p>生产环境里，<code>base-time</code> 不应随便填 0。它通常要设置成未来某个 TAI 时间，使所有设备从同一个周期边界开始执行 GCL。如果多台设备的时间表没有对齐，窗口看起来配置正确，实际却会错过彼此的发送时机。</p>
<p>此外，<code>taprio</code> 只是发送端排程。要让流量进入正确队列，还需要设置 VLAN PCP 或 socket priority。最常见做法是给实时流打 VLAN tag，并把 PCP 映射到对应 traffic class：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo ip link add link eth0 name eth0.10 <span class="nb">type</span> vlan id <span class="m">10</span> egress-qos-map 5:5
</span></span><span class="line"><span class="cl">sudo ip link <span class="nb">set</span> eth0.10 up
</span></span></code></pre></div><p>应用发送实时数据时，要么使用带 VLAN 的接口，要么通过 <code>SO_PRIORITY</code> 设置优先级。不同驱动对 priority 到硬件队列的映射细节不同，调试时必须结合 <code>tc -s qdisc show dev eth0</code> 看计数器是否增长在预期队列上。</p>
<h2 id="八接收端同样要实时网络准时到不代表应用准时读">八、接收端同样要实时：网络准时到，不代表应用准时读</h2>
<p>TSN 只能保证帧按计划到达网卡附近，不能自动保证用户态应用马上处理。如果接收端 Linux 系统负载很高、IRQ 绑在错误 CPU、实时线程优先级不够、socket buffer 太小，仍然会出现“抓包看网络没问题，应用层抖动很大”的情况。</p>
<p>接收端优化建议从几件小事做起：</p>
<ul>
<li>把网卡 IRQ 绑到固定 CPU，避免和非实时任务抢核；</li>
<li>对控制线程使用 <code>SCHED_FIFO</code> 或 <code>SCHED_RR</code>，并设置合理优先级；</li>
<li>使用 <code>mlockall()</code> 锁住内存，避免运行中缺页；</li>
<li>禁止实时 CPU 上跑日志压缩、数据库、Web 后台任务；</li>
<li>记录硬件时间戳和应用读取时间，分离网络抖动与软件调度抖动。</li>
</ul>
<p>PREEMPT_RT 能显著改善 Linux 内核抢占延迟，但它不是魔法。真实系统中，网络实时性往往是 TSN、内核实时性、驱动队列、应用线程模型共同决定的。只调其中一项，很容易出现局部最优。</p>
<p>（第二部分完，约2700字）</p>
<h2 id="九一个可运行的时延预算脚本">九、一个可运行的时延预算脚本</h2>
<p>TSN 配置最终要落到数字上。下面这个 Python 脚本用于做第一版窗口估算：输入链路速率、帧长、交换机跳数、应用处理时间和裕量，输出每条流大致需要的窗口长度。它不是替代专业 TSN 规划工具，而是帮助团队在需求评审阶段快速发现不合理参数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ETH_OVERHEAD_BYTES</span> <span class="o">=</span> <span class="mi">20</span> <span class="o">+</span> <span class="mi">4</span> <span class="o">+</span> <span class="mi">12</span>  <span class="c1"># preamble/SFD 近似 + FCS + IFG，工程估算用</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Flow</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">    <span class="n">payload_bytes</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="cl">    <span class="n">period_us</span><span class="p">:</span> <span class="nb">float</span>
</span></span><span class="line"><span class="cl">    <span class="n">hops</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="cl">    <span class="n">app_tx_us</span><span class="p">:</span> <span class="nb">float</span>
</span></span><span class="line"><span class="cl">    <span class="n">app_rx_us</span><span class="p">:</span> <span class="nb">float</span>
</span></span><span class="line"><span class="cl">    <span class="n">switch_us</span><span class="p">:</span> <span class="nb">float</span>
</span></span><span class="line"><span class="cl">    <span class="n">margin_us</span><span class="p">:</span> <span class="nb">float</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">wire_time_us</span><span class="p">(</span><span class="n">payload_bytes</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">link_mbps</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 以太网头 14B，VLAN 4B，保守估算加上开销</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_bytes</span> <span class="o">=</span> <span class="n">payload_bytes</span> <span class="o">+</span> <span class="mi">14</span> <span class="o">+</span> <span class="mi">4</span> <span class="o">+</span> <span class="n">ETH_OVERHEAD_BYTES</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">total_bytes</span> <span class="o">*</span> <span class="mi">8</span> <span class="o">/</span> <span class="n">link_mbps</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">estimate</span><span class="p">(</span><span class="n">flow</span><span class="p">:</span> <span class="n">Flow</span><span class="p">,</span> <span class="n">link_mbps</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">one_wire</span> <span class="o">=</span> <span class="n">wire_time_us</span><span class="p">(</span><span class="n">flow</span><span class="o">.</span><span class="n">payload_bytes</span><span class="p">,</span> <span class="n">link_mbps</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">flow</span><span class="o">.</span><span class="n">app_tx_us</span>
</span></span><span class="line"><span class="cl">        <span class="o">+</span> <span class="n">flow</span><span class="o">.</span><span class="n">hops</span> <span class="o">*</span> <span class="n">one_wire</span>
</span></span><span class="line"><span class="cl">        <span class="o">+</span> <span class="n">flow</span><span class="o">.</span><span class="n">hops</span> <span class="o">*</span> <span class="n">flow</span><span class="o">.</span><span class="n">switch_us</span>
</span></span><span class="line"><span class="cl">        <span class="o">+</span> <span class="n">flow</span><span class="o">.</span><span class="n">app_rx_us</span>
</span></span><span class="line"><span class="cl">        <span class="o">+</span> <span class="n">flow</span><span class="o">.</span><span class="n">margin_us</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">flows</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="n">Flow</span><span class="p">(</span><span class="s2">&#34;sensor_to_controller&#34;</span><span class="p">,</span> <span class="mi">96</span><span class="p">,</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">30</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">Flow</span><span class="p">(</span><span class="s2">&#34;controller_to_actuator&#34;</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">30</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">Flow</span><span class="p">(</span><span class="s2">&#34;diagnostic&#34;</span><span class="p">,</span> <span class="mi">512</span><span class="p">,</span> <span class="mi">10000</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">100</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">1000</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Link: </span><span class="si">{</span><span class="n">link</span><span class="si">}</span><span class="s2"> Mbps&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">flows</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">t</span> <span class="o">=</span> <span class="n">estimate</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">link</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">total</span> <span class="o">+=</span> <span class="n">t</span> <span class="k">if</span> <span class="n">f</span><span class="o">.</span><span class="n">period_us</span> <span class="o">&lt;=</span> <span class="mi">1000</span> <span class="k">else</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">f</span><span class="o">.</span><span class="n">name</span><span class="si">:</span><span class="s2">24s</span><span class="si">}</span><span class="s2">: window &gt;= </span><span class="si">{</span><span class="n">t</span><span class="si">:</span><span class="s2">7.2f</span><span class="si">}</span><span class="s2"> us&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;1ms cycle reserved: </span><span class="si">{</span><span class="n">total</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> us, free: </span><span class="si">{</span><span class="mi">1000</span><span class="o">-</span><span class="n">total</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> us&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>运行示例：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python3 tsn_budget.py
</span></span></code></pre></div><p>你会看到百兆和千兆链路之间的差异。百兆不是不能做实时控制，但窗口会明显变紧，尤其当帧较大、跳数较多、还不支持帧抢占时，guard band 会吃掉不少周期。千兆链路的好处不只是吞吐高，也在于同样大小帧的线速占用更短，给调度窗口留下更多余量。</p>
<p>这里有个容易忽略的点：预算脚本要和实测数据闭环。比如估算交换机转发延迟是 4 μs，但实际抓硬件时间戳发现某个端口在高温下偶尔出现 12 μs，就要把模型改掉。实时系统最怕“文档上可行，现场偶发失败”。</p>
<h2 id="十应用层发送示例设置优先级与时间戳">十、应用层发送示例：设置优先级与时间戳</h2>
<p>下面是一个简化的 C 语言 UDP 发送端，演示如何设置 socket priority、绑定 CPU、锁内存，并按 1 ms 周期发送数据。真实项目中还要加入错误处理、硬件时间戳读取、VLAN 接口绑定和配置管理，这里保留核心逻辑。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define _GNU_SOURCE
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;arpa/inet.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;errno.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;pthread.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;sched.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdint.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;string.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;sys/mman.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;sys/socket.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;time.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">add_ns</span><span class="p">(</span><span class="k">struct</span> <span class="n">timespec</span> <span class="o">*</span><span class="n">ts</span><span class="p">,</span> <span class="kt">long</span> <span class="n">ns</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ts</span><span class="o">-&gt;</span><span class="n">tv_nsec</span> <span class="o">+=</span> <span class="n">ns</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">tv_nsec</span> <span class="o">&gt;=</span> <span class="mi">1000000000L</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">ts</span><span class="o">-&gt;</span><span class="n">tv_sec</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">ts</span><span class="o">-&gt;</span><span class="n">tv_nsec</span> <span class="o">-=</span> <span class="mi">1000000000L</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">mlockall</span><span class="p">(</span><span class="n">MCL_CURRENT</span> <span class="o">|</span> <span class="n">MCL_FUTURE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">cpu_set_t</span> <span class="n">set</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">CPU_ZERO</span><span class="p">(</span><span class="o">&amp;</span><span class="n">set</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">CPU_SET</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">set</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sched_setaffinity</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">set</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">set</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">sched_param</span> <span class="n">sp</span> <span class="o">=</span> <span class="p">{.</span><span class="n">sched_priority</span> <span class="o">=</span> <span class="mi">70</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="nf">pthread_setschedparam</span><span class="p">(</span><span class="nf">pthread_self</span><span class="p">(),</span> <span class="n">SCHED_FIFO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="nf">socket</span><span class="p">(</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">SOCK_DGRAM</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">fd</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">perror</span><span class="p">(</span><span class="s">&#34;socket&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">prio</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">setsockopt</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">SO_PRIORITY</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">prio</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">prio</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">sockaddr_in</span> <span class="n">dst</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">dst</span><span class="p">.</span><span class="n">sin_family</span> <span class="o">=</span> <span class="n">AF_INET</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">dst</span><span class="p">.</span><span class="n">sin_port</span> <span class="o">=</span> <span class="nf">htons</span><span class="p">(</span><span class="mi">5005</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">inet_pton</span><span class="p">(</span><span class="n">AF_INET</span><span class="p">,</span> <span class="s">&#34;192.168.10.20&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dst</span><span class="p">.</span><span class="n">sin_addr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span> <span class="n">payload</span><span class="p">[</span><span class="mi">96</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">timespec</span> <span class="n">next</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">clock_gettime</span><span class="p">(</span><span class="n">CLOCK_TAI</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">next</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">add_ns</span><span class="p">(</span><span class="o">&amp;</span><span class="n">next</span><span class="p">,</span> <span class="mi">1000000L</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">seq</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">memcpy</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">seq</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">seq</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="nf">clock_nanosleep</span><span class="p">(</span><span class="n">CLOCK_TAI</span><span class="p">,</span> <span class="n">TIMER_ABSTIME</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">next</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">ssize_t</span> <span class="n">n</span> <span class="o">=</span> <span class="nf">sendto</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">payload</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                           <span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">dst</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dst</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">n</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">errno</span> <span class="o">!=</span> <span class="n">EINTR</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">perror</span><span class="p">(</span><span class="s">&#34;sendto&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">seq</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">add_ns</span><span class="p">(</span><span class="o">&amp;</span><span class="n">next</span><span class="p">,</span> <span class="mi">1000000L</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>编译时可以使用：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcc -O2 -Wall tsn_sender.c -o tsn_sender -pthread
</span></span><span class="line"><span class="cl">sudo ./tsn_sender
</span></span></code></pre></div><p>这个示例有两个工程细节值得注意。第一，周期调度使用 <code>CLOCK_TAI</code>，它更适合和 PTP / TSN 时间体系对齐；如果系统没有正确同步，使用哪个 clock 都救不了。第二，<code>SO_PRIORITY=5</code> 只是告诉内核这条流的优先级，最终能否进入预期硬件队列，还取决于 qdisc、驱动和 VLAN 映射。调试时不要相信“我设置了”，要相信计数器和抓包结果。</p>
<h2 id="十一调试-checklist从时间同步到应用抖动">十一、调试 checklist：从时间同步到应用抖动</h2>
<p>TSN 项目的调试最好按层推进，不要一上来就看应用效果。下面是一套实用 checklist：</p>
<h3 id="1-物理链路与硬件能力">1. 物理链路与硬件能力</h3>
<ul>
<li><code>ethtool eth0</code> 确认速率、双工和自协商结果；</li>
<li><code>ethtool -T eth0</code> 确认硬件时间戳能力；</li>
<li>检查交换机型号是否明确支持 802.1AS、802.1Qbv、802.1Qci；</li>
<li>确认所有链路中没有混入普通办公交换机。</li>
</ul>
<h3 id="2-时间同步">2. 时间同步</h3>
<ul>
<li><code>ptp4l -m</code> 观察 offset 是否稳定；</li>
<li><code>pmc</code> 查询 grandmaster 身份，确认主时钟符合设计；</li>
<li>断开 grandmaster 后观察系统是否进入 holdover，以及漂移速度是否可接受；</li>
<li>记录冷启动后达到稳定同步所需时间。</li>
</ul>
<h3 id="3-队列与门控">3. 队列与门控</h3>
<ul>
<li><code>tc -s qdisc show dev eth0</code> 查看各队列计数器；</li>
<li>抓包确认 VLAN PCP 是否正确；</li>
<li>故意发送大流量普通包，确认实时流延迟不明显变差；</li>
<li>修改窗口大小，验证抖动变化符合预期。</li>
</ul>
<h3 id="4-应用线程">4. 应用线程</h3>
<ul>
<li>使用 <code>cyclictest</code> 测内核调度延迟；</li>
<li>检查实时线程是否真的运行在 <code>SCHED_FIFO</code>；</li>
<li>用 <code>perf top</code> 或 <code>ftrace</code> 找出偶发长延迟来源；</li>
<li>把网络硬件时间戳和应用处理时间同时写入日志。</li>
</ul>
<p>如果只能选一个指标长期监控，我建议选“端到端年龄”（data age）：从传感器采样时间戳到控制器使用这份数据时的时间差。它比单纯网络延迟更贴近控制系统真实风险，因为它包含采样、发送、网络、接收、调度和应用处理全过程。</p>
<h2 id="十二常见坑与解决思路">十二、常见坑与解决思路</h2>
<p><strong>坑 1：交换机号称支持 TSN，但只支持一部分标准。</strong> 采购前要看 datasheet 的细项，尤其是 Qbv、Qci、AS、帧抢占是否都支持，以及每端口队列数量、GCL 条目数量、时间粒度是多少。只写“TSN ready”没有意义。</p>
<p><strong>坑 2：PTP offset 很漂亮，应用层仍然抖。</strong> 这通常说明网络时间同步没问题，但接收线程、IRQ、CPU 隔离或内存策略有问题。把硬件时间戳和用户态时间戳分开记录，很快能看出抖动发生在哪一段。</p>
<p><strong>坑 3：窗口太窄，实验室偶尔成功。</strong> 这种配置最危险，因为 demo 看起来能跑。建议把压力流量、温度、CPU 满载、日志写盘、链路重协商等异常情况加入验收。</p>
<p><strong>坑 4：把 TSN 当成安全机制。</strong> Qci 能过滤异常流量，但它不是完整安全方案。设备认证、配置签名、网络分区、最小权限和固件更新安全仍然要单独设计。</p>
<p><strong>坑 5：忽视配置版本管理。</strong> TSN 配置包括交换机 GCL、VLAN、Linux qdisc、应用周期、PTP 域号。任何一项改了都可能影响实时性，必须像代码一样进 Git，并和固件版本绑定。</p>
<h2 id="十三什么时候不该上-tsn">十三、什么时候不该上 TSN</h2>
<p>TSN 很强，但不是所有项目都需要。下面几种场景可以先谨慎评估：</p>
<ul>
<li>控制周期在几十毫秒以上，普通 QoS 加冗余已经足够；</li>
<li>系统只有单板内部通信，用 SPI、CAN FD、共享内存更简单；</li>
<li>团队没有能力维护时间同步和网络诊断，只想买一个“实时网络黑盒”；</li>
<li>硬件供应链不稳定，无法保证每批网卡和交换机都支持同样 TSN 能力。</li>
</ul>
<p>工程选型不是越高级越好。TSN 的收益来自标准化和确定性，但成本是更复杂的配置、更严格的测试和更长的 bring-up 周期。如果项目只需要软实时数据采集，普通以太网、UDP、QoS 和应用层缓冲可能就够了。只有当 deadline miss 会导致控制质量下降、设备停机甚至安全风险时，TSN 的复杂度才真正值得。</p>
<h2 id="总结">总结</h2>
<p>TSN 的核心价值，是把分布式嵌入式系统中的网络时序从“尽力而为”变成“按计划执行”。802.1AS 提供统一时间，802.1Qbv 让关键队列按时间窗口发送，802.1Qci 把异常流量挡在门外，帧抢占进一步降低大包阻塞。它们组合起来，才能支撑工业控制、机器人、车载网络、实时音视频和边缘 AI 控制等场景。</p>
<p>落地 TSN 时，不要从配置命令开始，而要从需求表、时延预算和可观测性开始。先定义每条流的周期、deadline、载荷、优先级和容错策略，再选择硬件，验证 PTP，配置门控队列，最后把网络时间戳和应用处理时间纳入长期监控。TSN 不是替代 RTOS 或 PREEMPT_RT 的东西，而是把实时系统的边界扩展到了网络。只有网络、内核、驱动和应用一起设计，端到端确定性才站得住。</p>
<p>如果用一句话概括：TSN 不是让以太网“更快”，而是让它“准时”。对于真正的实时系统，准时往往比更快重要得多。</p>
<p>（全文完，约7600字）</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
