解决什么问题
到这里,你已经知道 Qbv 的窗口为什么要有 cycle、base time、guard band,也知道 queueMaxSDU 为什么能保护窗口。但把这些概念变成真实配置时,会卡在一个很具体的地方:词对不上。
课文说“周期”,交换机界面写“门控循环时间(分子/分母)”;课文说“门控表”,Linux 里是一行行 `sched-entry`;课文说“每队列最大帧”,设备页面叫“SDU-table”,标准里又叫 `queueMaxSDU`。同一个东西,三套名字。
这一节不引入任何新机制,只做一件事:把同一个 Qbv 概念,在交换机配置界面、Linux `tc-taprio`、IEEE 802.1Q 标准对象三个地方认出来。 跨过这一步,“懂 Qbv”才会变成“能配 Qbv”。
同一件事的三套说法
下面这张表是本节的核心。左边是本章一直在用的概念,右边是你在真实世界会遇到的三种写法。
| 概念(本章用语) | 交换机配置界面常见字段 | Linux `tc-taprio` | IEEE 802.1Q 标准对象 |
|---|---|---|---|
| 周期 cycle time | 门控循环时间(分子/分母) | `cycle-time`(纳秒);省略时为各 `sched-entry` 时长之和 | `AdminCycleTime`(Numerator/Denominator) |
| 周期起点 base time | 循环开始基准时间(秒 + 纳秒) | `base-time`(纳秒,基于 `clockid`) | `AdminBaseTime`(PTPtime:seconds + nanoseconds) |
| 门控表 GCL | GCL 门控列表(每行:时长 + 各门开关) | 一串 `sched-entry S <gatemask> <interval>` | `AdminControlList`(GateState + TimeInterval) |
| 某队列的门 开/关 | 该流量类的 gate 位 | gate mask 里对应的一个 bit | `gateStatesValue` 的一位 |
| 每队列最大帧 | 流量类 N 最大 SDU(SDU-table) | `max-sdu`(最多 16 个值的数组) | `queueMaxSDU` |
| 优先级→队列映射 | PCP / TC 映射表 | `num_tc` + `map` + `queues` | Traffic Class Table |
| express / preemptable | 抢占使能 | `fp`(逐 TC) | `framePreemptionStatusTable` |
记住一个对应关系就够开路:界面里能配的每一项,`tc-taprio` 里几乎都有一个同义参数,标准里再有一个同义对象。 三者描述的是同一台设备的同一个调度行为。
读懂一条真实的 tc-taprio 命令
把抽象的表落到一条能跑的命令上。下面是一个 8 个流量类、带 `max-sdu` 的硬件 offload 配置(改写自 `tc-taprio(8)` 手册示例):
tc qdisc replace dev eth0 parent root handle 100 taprio \
num_tc 8 \
map 0 1 2 3 4 5 6 7 \
queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 \
max-sdu 0 0 0 0 0 200 0 0 \
base-time 200 \
sched-entry S 80 20000 \
sched-entry S a0 20000 \
sched-entry S 5f 60000 \
flags 0x2逐段拆开:
- `num_tc 8` / `map ...` / `queues ...`:声明 8 个流量类,并定义“报文优先级 → 流量类 → 硬件队列”怎么映射。这对应界面里的 PCP/TC 映射表。
- `base-time 200`:调度表在 `clockid` 的第 200 纳秒这个绝对时刻启动。即使这是个过去时刻,设备会自动加上整数倍 cycle,对齐到下一个周期边界再启动。
- `sched-entry S <gatemask> <interval>`:这就是 GCL 的一行。`S` 是 SetGateStates;`interval` 是这一行持续多少纳秒;`gatemask` 是十六进制按位掩码,bit0 = TC0。
- 三行算下来,cycle time = 20000 + 20000 + 60000 = 100us(没单独写 `cycle-time` 时,它就是各行之和)。
- `max-sdu ... 200 ...`:第 5 个流量类(TC5)最大 SDU 限制为 200 字节,其余为 0(= 不额外限制,可到端口 MTU)。这正是 SW0 那张 SDU-table 的一行。
- `flags 0x2`:整张表交给网卡硬件循环执行(full-offload);此时 `clockid` 要省略,因为硬件直接用 NIC 的 PTP 时钟。
把 gate mask 翻成人话:
| sched-entry | gatemask(hex) | 二进制 | 这段时间哪些 TC 的门开着 |
|---|---|---|---|
| `S 80 20000` | `80` | `1000 0000` | 只有 TC7 |
| `S a0 20000` | `a0` | `1010 0000` | TC7 + TC5 |
| `S 5f 60000` | `5f` | `0101 1111` | TC0–TC4 + TC6 |
把交换机界面的字段对回来
现在反过来,拿一台真实交换机的 Qbv 配置页(以本课常用的 SW0 界面为例)对照标准与 `tc-taprio`:
| SW0 界面字段 | 示例值 | 它其实是 | 对应到 tc-taprio |
|---|---|---|---|
| 门控循环时间(分子 / 分母) | 2 / 3 | `AdminCycleTime` 的有理数 = 2/3 秒 | `cycle-time 666666667`(≈ 666.67ms,纳秒整数) |
| 循环开始基准时间(秒 / 纳秒) | 4 / 23 | `AdminBaseTime` = 4s + 23ns 这个绝对时刻 | `base-time 4000000023` |
| 流量类 0..7 最大 SDU(SDU-table 一行) | 2,0,0,0,0,0,3,… | 该端口的 `queueMaxSDU` | `max-sdu 2 0 0 0 0 0 3 ...` |
两个容易困惑的设计点,在这里一次说清:
- 为什么 cycle time 用分子/分母? 标准把 `AdminCycleTime` 定义成有理数(分子÷分母,单位秒),是为了精确表示 1/3、1/1500 这类无限小数周期,避免各设备用浮点各自取整、长期累积漂移。Linux `tc-taprio` 不暴露有理数,而是直接收整数纳秒——所以界面上的 2/3 秒,到 `tc` 里要先算成 `666666667` 纳秒。
- 为什么 base time 拆成秒 + 纳秒? 这是 PTP 的 `PTPtime` 结构(秒 + 纳秒两段),因为它表达的是一个基于共同时间的绝对时刻,不是“开机后多久”。`tc-taprio` 把这两段合成一个纳秒整数,语义完全一样。
顺带一提:SW0 的 SDU-table 里出现的 2、3、4 这类字节值,小于以太网最小载荷 46B,是测试占位值;真实部署应填该队列允许的最大业务帧长,例如 1500。
一个判断练习
下面这条命令想配一个 cycle = 100us、关键流量(TC6)独占前 30us 的调度,但它有三个错误。试着在看答案前找出来:
tc qdisc replace dev eth0 parent root handle 100 taprio \
num_tc 8 map 0 1 2 3 4 5 6 7 queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 \
base-time 0 \
sched-entry S 6 30000 \
sched-entry S 255 80000 \
clockid CLOCK_REALTIME问题:
- 1gate mask 写错了。 想让 TC6 独占,掩码应是 `40`(`0100 0000`),而不是 `6`(`0000 0110` = TC1+TC2)。第二行想“全开”应是 `ff`,而不是十进制 `255`——`tc` 按十六进制解析 gatemask,`255` 会被读成 `0x255`,超出 8 个 TC 的范围。
- 2cycle 不自洽。 30000 + 80000 = 110us,不是声称的 100us。各 `sched-entry` 之和就是 cycle,声称值必须和它对上。
- 3时钟用错。 `CLOCK_REALTIME` 会跳变(NTP 调整、闰秒),TSN 调度必须用 `CLOCK_TAI` 且经 gPTP 同步;否则 `base-time 0` 这个绝对时刻在各设备根本不一致。
能一眼看出这三类错(掩码按位、cycle 自洽、时钟来源),你就具备了审一份真实 Qbv 配置的最低能力。
带来了什么新问题
这张对照表能让你认出字段,但它只是起点,不能替代具体设备手册。不同厂商对同一个概念的命名、单位和默认值并不统一:有的 `queueMaxSDU` 按 L2 payload 计,有的把头部也算进去;有的界面用纳秒,有的用 tick 或时隙数;offload 能力、支持的 `sched-entry` 条数上限也各不相同。
所以正确的用法是:用这张表把概念锚定到“大概是哪个字段”,再到目标设备手册里核对单位、范围和默认值。下一节就把概念真正下发到一块网卡上,让你看到“配置生效”到底长什么样。
检查点
- 给定 `sched-entry S c0 50000`,这一段有哪些 TC 的门是开的?这段持续多久?
- SW0 界面把 cycle time 写成分子/分母 `1 / 8000`,换算成 `tc-taprio` 的 `cycle-time` 是多少纳秒?
- 一份配置 `base-time` 写了一个很大的未来时刻,且设备未做 PTP 同步。它能开始调度吗?会有什么风险?