3. TSN核心机制(下):流量调度与带宽预留
上一章我们聊了时钟同步和时间同步,那是TSN的"心跳"。这一章我们来看看TSN真正的"肌肉"——流量调度和带宽预留。说白了,就是怎么让数据在正确的时间、以正确的顺序、到达正确的地方。
我在做车载项目时,最头疼的不是功能实现,而是多个ECU同时发数据时,总线上的"交通堵塞"。你想想看,摄像头要传视频,雷达要传点云,座舱要传音频,这些数据挤在一起,谁先谁后?TSN给出了答案。
3.1 时间感知整形(Qbv)——给数据排个时间表
Qbv的全称是Time-Aware Shaper,时间感知整形器。它的核心思想很简单:把时间切成一个个小窗口,每个窗口只允许特定类型的数据通过。
核心概念:Qbv通过门控列表(Gate Control List,GCL)控制每个队列的开关状态。时间到了,门打开,数据出去;时间过了,门关上,数据等着。
我记得第一次看Qbv的配置时,感觉就像在看一个复杂的交通信号灯系统。每个队列对应一个"车道",GCL就是"红绿灯"的时序表。
3.1.1 Qbv的工作原理
每个端口有8个优先级队列(0-7)。每个队列都有一个门(gate),门的状态只有两种:开(open)或关(close)。GCL定义了每个时间点,哪些门开,哪些门关。
// GCL配置示例(伪代码)
GCL Entry 0: 时间偏移 0ms, 队列7开, 其他关
GCL Entry 1: 时间偏移 1ms, 队列6开, 其他关
GCL Entry 2: 时间偏移 2ms, 队列5开, 其他关
GCL Entry 3: 时间偏移 3ms, 队列4开, 其他关
GCL Entry 4: 时间偏移 4ms, 队列3开, 其他关
GCL Entry 5: 时间偏移 5ms, 队列2开, 其他关
GCL Entry 6: 时间偏移 6ms, 队列1开, 其他关
GCL Entry 7: 时间偏移 7ms, 队列0开, 其他关
这个配置意味着:每1ms切换一个队列。时间敏感的数据(比如控制指令)放在高优先级队列,就能在固定的时间窗口内被发送出去。
我的经验:实际项目中,GCL的粒度通常是纳秒级的。我曾经在一个ADAS项目中,把GCL的周期设为125μs,每个窗口只有15.625μs。这个精度对时钟同步的要求非常高,稍有偏差就会导致数据错位。
3.1.2 Qbv的避坑指南
我曾经踩过一个坑:GCL的周期和PTP的同步周期没有对齐。结果就是,时钟同步的瞬间,GCL刚好在切换窗口,导致一个关键的控制帧被延迟了整整一个窗口周期。嗯,从那以后,我养成了一个习惯——GCL的周期必须是PTP同步周期的整数倍。
注意事项:
- GCL的基时间必须与PTP的全局时间同步
- 保护带(Guard Band)要留够,防止前一个窗口的数据侵占下一个窗口的时间
- 窗口切换时,正在发送的帧允许完成,但新帧不能开始发送
3.2 帧抢占(Qbu)——让紧急数据插个队
Qbv虽然好,但有个问题:如果一个长帧(比如1500字节的IP包)正在发送,突然来了一个紧急的控制帧,怎么办?等长帧发完?那延迟就不可控了。
Qbu(Frame Preemption)就是解决这个问题的。它允许一个正在发送的帧被"打断",先发送紧急帧,然后再恢复发送原来的帧。
3.2.1 Qbu的工作机制
帧抢占的核心是"切分"。发送端把一个长帧切成多个小片段(fragment),每个片段后面留一个"可打断点"。当紧急帧到达时,发送端在当前片段发送完成后,插入紧急帧。
| 特性 | 说明 |
|---|---|
| 可抢占帧 | 长度大于124字节的非时间敏感帧 |
| 不可抢占帧 | 长度小于等于124字节的帧,或标记为"express"的帧 |
| 片段大小 | 最小64字节(含前导码和帧间距) |
| 恢复机制 | 接收端重组片段,恢复原始帧 |
我个人觉得,Qbu就像高速公路上的"应急车道"。平时不用,但关键时刻能救命。在车载网络中,紧急制动指令的延迟要求是微秒级的,没有Qbu,光靠Qbv很难满足。
关键点:Qbu需要发送端和接收端都支持。如果接收端不支持,发送端就不能启用抢占。我在项目中遇到过兼容性问题——有些老款ECU的MAC层不支持帧抢占,导致整个网络无法启用Qbu。
3.3 入口管制(Qci)——把"坏孩子"挡在门外
Qci(Per-Stream Filtering and Policing)是TSN的"门卫"。它负责检查每个数据流是否遵守约定——带宽有没有超标?帧间隔对不对?如果违规,就采取行动:丢弃、标记或降级。
3.3.1 Qci的过滤规则
Qci的过滤是基于流的。每个流由目的MAC地址、VLAN ID和优先级唯一标识。对于每个流,可以配置以下规则:
- 带宽上限:每秒最多发送多少字节
- 突发大小:一次最多连续发送多少字节
- 帧间隔:两帧之间的最小时间间隔
- 帧大小:最小和最大帧长
我曾经在一个项目中,发现某个ECU的软件bug导致它每隔1ms就发送一个广播帧。这个广播帧占用了大量带宽,导致其他关键数据被延迟。后来用Qci限制了该ECU的发送速率,问题就解决了。
实用技巧:Qci的配置要"宽进严出"。入口处可以稍微宽松一点,避免误杀正常数据;出口处要严格,确保网络质量。我一般把入口的带宽上限设为预留带宽的110%,出口设为100%。
3.4 带宽预留(Qav)——给关键数据"划地盘"
Qav(Credit-Based Shaper)是TSN的"交通警察"。它不直接控制时间窗口,而是通过"信用值"来调节每个队列的发送速率。
3.4.1 Qav的工作原理
每个队列有一个信用值(credit)。信用值随时间线性增加,发送数据时线性减少。当信用值为正时,队列可以发送数据;为负时,必须等待。
// Qav信用值计算(简化版)
// 每发送一个字节,信用值减少 1/发送速率
// 每空闲一个时间单位,信用值增加 1/空闲速率
if (队列非空 && 信用值 >= 0) {
发送数据();
信用值 -= 发送字节数 / 发送速率;
} else {
信用值 += 空闲时间 / 空闲速率;
}
这个机制的好处是:带宽分配是"软"的。如果某个队列没有数据,它的信用值会一直增加,等有数据时就能"爆发"发送。这比Qbv的"硬"时间窗口更灵活。
3.4.2 Qav的带宽计算
假设总带宽为100Mbps,我们要给三个流分配带宽:
| 流 | 预留带宽 | 发送速率 | 空闲速率 |
|---|---|---|---|
| 控制流 | 20 Mbps | 100 Mbps | 20 Mbps |
| 视频流 | 50 Mbps | 100 Mbps | 50 Mbps |
| 普通数据 | 30 Mbps | 100 Mbps | 30 Mbps |
注意看,发送速率都是100Mbps(线速),但空闲速率不同。这意味着:当队列有数据时,它可以以线速发送;当队列空闲时,它的信用值增长速度不同。这样,控制流虽然只预留了20Mbps,但需要时能"抢"到全部带宽。
重要提醒:Qav不能单独保证确定性延迟。它只能保证平均带宽。要保证最坏情况下的延迟,必须和Qbv配合使用。我一般把Qav用于非时间敏感但需要带宽保障的流(比如视频),把Qbv用于时间敏感的流(比如控制指令)。
3.5 四种机制的协同工作
好了,四种机制都讲完了。它们不是孤立的,而是协同工作的。我画了一张图来说明它们的关系:
从图中可以看出,数据流的处理流程是:
- Qci先做入口检查,把违规的流量挡在外面
- 通过检查的数据进入Qav管理的优先级队列,每个队列有自己的信用值
- Qbv根据GCL控制每个队列的发送窗口
- 在发送过程中,Qbu允许紧急帧抢占正在发送的长帧
这四种机制配合起来,就能实现车载网络最核心的需求:确定性延迟和带宽保障。
实战建议:我在项目中一般这样分配:
- 控制指令(如制动、转向):Qbv + 最高优先级队列,周期125μs
- 传感器数据(如摄像头、雷达):Qav + 高优先级,预留带宽
- 诊断和OTA:Qav + 低优先级,尽力而为
- 所有流都经过Qci过滤,防止异常流量