Linux网络之TC (Traffic Control) 网络流控子系统分析
Linux网络之TC (Traffic Control) 网络流控子系统分析
基于 Linux Kernel v7.1.0-rc6 分析
一、概念总览
1.1 什么是 TC
TC(Traffic Control)是 Linux 内核中的网络流量控制框架,位于网络设备驱动层之上、协议栈之下。它在数据包的 发送路径(Egress)和接收路径(Ingress) 上提供了一个可编程的包处理管线,用于实现:
- 流量整形(Shaping):限制发送速率,平滑突发流量
- 流量 policing:对超过速率限制的包进行丢弃或标记
- 包分类(Classification):根据包头字段(五元组、mark、接口等)将包分类到不同的流
- 队列调度(Scheduling):决定哪个包先被发送,实现 QoS 优先级
- 包标记/修改(Marking/Editing):修改包头字段(DSCP、VLAN tag 等)
- 包重定向(Redirect):将包转发到其他设备
- 包丢弃(Dropping):基于策略丢弃数据包
1.2 核心组件
TC 框架由三层组件构成,形成分层处理管线:
┌─────────────────────────────────────────────────┐
│ Classifier (分类器) - 识别包属于哪个流 │
│ 例: flower, u32, fw, bpf, matchall │
├─────────────────────────────────────────────────┤
│ Action (动作) - 对匹配的包执行操作 │
│ 例: mirred, gact, skbedit, pedit, nat, bpf │
├─────────────────────────────────────────────────┤
│ Qdisc (队列规则) - 包的排队与调度 │
│ 例: pfifo_fast, tbf, htb, fq_codel, prio │
└─────────────────────────────────────────────────┘
这三个组件通过 tcf_block(过滤块) 和 tcf_chain(过滤链) 组织在一起。
1.3 关键数据结构
| 结构体 | 定义位置 | 作用 |
|---|---|---|
struct Qdisc |
include/net/sch_generic.h:69 |
队列规则实例,每个 TX 队列挂载一个 |
struct Qdisc_ops |
include/net/sch_generic.h:307 |
队列规则的操作函数集(enqueue/dequeue/init/destroy) |
struct tcf_proto |
include/net/sch_generic.h:426 |
分类器实例,按优先级链成链表 |
struct tcf_proto_ops |
include/net/sch_generic.h:357 |
分类器的操作函数集(classify/init/destroy) |
struct tcf_result |
include/net/sch_generic.h:345 |
分类结果,包含 classid 或 goto 目标 |
struct tcf_chain |
include/net/sch_generic.h:469 |
过滤链,持有 tcf_proto 链表头 |
struct tcf_block |
include/net/sch_generic.h:485 |
过滤块,包含多条链,可被多个 qdisc 共享 |
struct qdisc_skb_head |
include/net/sch_generic.h:62 |
Qdisc 内部的 sk_buff 队列头 |
struct mini_Qdisc |
include/net/sch_generic.h:1455 |
轻量级 Qdisc,用于 ingress/clsact 快速路径 |
二、设计原理
2.1 分层架构设计
TC 采用分层抽象设计,核心思路是将”包的分类”与”包的调度”解耦:
┌──────────────┐
│ 用户空间 │
│ tc 命令行 │
└──────┬───────┘
│ Netlink (RTM_NEWQDISC, RTM_NEWTFILTER)
▼
┌──────────────────────────────────────────────┐
│ 控制平面 (Control Plane) │
│ sch_api.c: tc_modify_qdisc() │
│ cls_api.c: tc_ctl_filter() │
│ → 创建/修改/删除 Qdisc、Filter、Action │
└──────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ 数据平面 (Data Plane) │
│ 发送: __dev_queue_xmit() → __dev_xmit_skb()│
│ → q->enqueue() → q->dequeue() │
│ → sch_direct_xmit() → dev_hard_start_xmit() │
│ │
│ 接收: __netif_receive_skb_core() │
│ → sch_handle_ingress() → tc_run() │
│ → tcf_classify() │
└──────────────────────────────────────────────┘
2.2 Qdisc 树形层次结构
TC 支持 Qdisc 的层级嵌套(hierarchy),形成一棵树:
root qdisc (1:)
/ \
class (1:1) class (1:2)
/ \ |
qdisc(1:10) qdisc(1:20) qdisc(1:20)
(htb) (sfq) (tbf)
| | |
class class leaf
(1:10:1) (1:10:2) (1:20:1)
- Root Qdisc:设备的根队列规则,每个 TX 队列一个
- Class:Qdisc 内部的逻辑分类,用于带宽分配
- Leaf Qdisc:挂在 class 下的子队列规则,真正缓存包
这种层次结构通过 struct Qdisc_class_ops 实现,其中的 graft 和 leaf 回调负责子 qdisc 的挂载和查询。
2.3 Classifier 链式匹配
分类器通过 struct tcf_proto 的 next 指针形成有序链表,按 prio(优先级)排序:
filter_chain → [u32 prio=1] → [flower prio=2] → [matchall prio=3] → NULL
匹配过程(__tcf_classify() in cls_api.c:1712):
- 遍历链表中的每个
tcf_proto - 检查协议类型是否匹配(
tp->protocolvsskb->protocol) - 调用具体分类器的
classify()回调 - 如果返回
TC_ACT_RECLASSIFY,则从头重新开始 - 如果返回
TC_ACT_GOTO_CHAIN,则跳转到指定链 - 返回分类结果(classid 或 action verdict)
2.4 快速路径 vs 慢速路径
TC 设计了快速路径(Fast Path)优化,避免在无分类器时的开销:
ingress/clsact 的快速路径(mini_Qdisc):
- 使用
struct mini_Qdisc(sch_generic.h:1455),只包含filter_list、block和统计信息 - 不需要完整的
struct Qdisc开销 - 在
tc_run()中直接通过tcf_classify()匹配,不经过完整 Qdisc 流程
TX 的 bypass 优化(TCQ_F_CAN_BYPASS):
- 当 Qdisc 标记了
TCQ_F_CAN_BYPASS且队列为空时 - 包可以绕过入队/出队,直接调用
sch_direct_xmit()发送 - pfifo_fast 默认启用此优化
2.5 锁机制设计
TC 采用多级锁确保并发安全:
| 锁 | 保护对象 | 获取方式 |
|---|---|---|
qdisc->q.lock (seqlock) |
Qdisc 队列操作 | qdisc_lock() |
dev->tx_global_lock |
设备发送 | HARD_TX_LOCK() |
chain->filter_chain_lock |
过滤链 | mutex_lock() |
tp->lock |
分类器私有数据 | spin_lock() |
No-lock Qdisc(TCQ_F_NOLOCK)使用 seqlock + MISSED 标志位实现无锁快速路径:
qdisc_run_begin()尝试spin_trylock(),失败则设置__QDISC_STATE_MISSED- 当前持有者在
qdisc_run_end()中检查 MISSED,若有则调用__netif_schedule()重新调度
2.6 统计信息收集
Qdisc 通过 bstats(基础统计)和 qstats(队列统计)收集数据:
- per-CPU 统计(
TCQ_F_CPUSTATS):每个 CPU 维护独立的统计计数器,避免锁竞争 - 全局统计:直接更新
q->bstats和q->qstats - 通过
gnet_stats框架在 dump 时聚合
三、数据包处理流程
3.1 发送路径(Egress)
数据包从上层协议栈发出,经过 TC 处理后到达网卡驱动:
协议栈 (TCP/IP)
│
▼
dev_queue_xmit() // 公开 API 入口
│
▼
__dev_queue_xmit() // net/core/dev.c:4766
│
├── 1. sch_handle_egress() // Egress clsact 过滤 (dev.c:4807)
│ │
│ ├── tcx_run() // BPF egress 程序
│ └── tc_run() // TC 分类器执行
│ │
│ └── tcf_classify() // cls_api.c:1805
│ │
│ └── 遍历 tcf_proto 链表
│ 对每个 proto 调用 tp->ops->classify()
│ 返回 TC_ACT_OK/SHOT/REDIRECT/...
│
├── 2. 选择 TX 队列
│ netdev_core_pick_tx() → txq
│
├── 3. 获取该队列的 Qdisc
│ q = rcu_dereference_bh(txq->qdisc)
│
├── 4. 如果 Qdisc 有 enqueue(非 noqueue)
│ │
│ ▼
│ __dev_xmit_skb() // dev.c:4180
│ │
│ ├── [No-lock Qdisc 快速路径]
│ │ 如果 TCQ_F_CAN_BYPASS && 队列空 && 获得运行锁:
│ │ → sch_direct_xmit() 直接发送
│ │
│ ├── [普通路径 - TCQ_F_NOLOCK]
│ │ 使用 llist defer_list 延迟入队:
│ │ 1. llist_add() 无锁添加到 defer_list
│ │ 2. 获取 root_lock
│ │ 3. llist_del_all() 取出所有延迟包
│ │ 4. 如果 CAN_BYPASS 且队列空:
│ │ → sch_direct_xmit() 直接发送
│ │ 5. 否则: dev_qdisc_enqueue() 入队 + qdisc_run()
│ │
│ └── [传统路径 - 有锁 Qdisc]
│ 1. dev_qdisc_enqueue() → q->enqueue(skb, q, to_free)
│ 2. qdisc_run(q) → __qdisc_run()
│
└── 5. [无 Qdisc 的设备(loopback, tunnels)]
→ dev_hard_start_xmit() 直接发送
**dev_qdisc_enqueue()**(dev.c:4168):
static int dev_qdisc_enqueue(struct sk_buff *skb, struct Qdisc *q,
struct sk_buff **to_free,
struct netdev_queue *txq)
{
rc = q->enqueue(skb, q, to_free) & NET_XMIT_MASK;
// 调用具体 Qdisc 的入队操作
// 例如 pfifo_fast_enqueue(), htb_enqueue(), tbf_enqueue()
}
**__qdisc_run()**(sch_generic.c:440):
void __qdisc_run(struct Qdisc *q)
{
int quota = READ_ONCE(net_hotdata.dev_tx_weight);
while (qdisc_restart(q, &packets, quota)) {
quota -= packets;
if (quota <= 0) {
// 配额用完,调度下次处理
if (q->flags & TCQ_F_NOLOCK)
set_bit(__QDISC_STATE_MISSED, &q->state);
else
__netif_schedule(q);
break;
}
}
}
出队 + 发送循环(sch_generic.c:420):
static bool qdisc_restart(struct Qdisc *q, int *packets, int quota)
{
skb = dequeue_skb(q, &validate, packets, budget);
// dequeue_skb 先检查 gso_skb(重发队列),
// 再调用 q->dequeue(q) 从实际队列出队
// 支持批量出队(bulk dequeue)
return sch_direct_xmit(skb, q, dev, txq, root_lock, validate);
// → validate_xmit_skb_list() (GSO/checksum)
// → dev_hard_start_xmit() (调用驱动 ndo_start_xmit)
}
3.2 接收路径(Ingress)
数据包从网卡驱动接收,经过 TC Ingress 处理后到达上层协议栈:
网卡驱动
│
▼
__netif_receive_skb_core() // net/core/dev.c:6010+
│
├── 1. ptype_all 处理(tcpdump 抓包点)
│
├── 2. sch_handle_ingress() // dev.c:4460
│ │
│ ├── tcx_run() // BPF ingress 程序
│ └── tc_run() // TC 分类器执行
│ │
│ ├── mini_qdisc_bstats_cpu_update()
│ │
│ └── tcf_classify(skb, miniq->block, miniq->filter_list, &res)
│ │
│ └── 遍历 tcf_proto 链表
│ 调用 tp->ops->classify() (如 fl_classify, u32_classify)
│ 返回 verdict
│
├── 3. 处理 verdict
│ TC_ACT_REDIRECT → skb_do_redirect() → 重定向到其他设备
│ TC_ACT_SHOT → kfree_skb_reason() → 丢弃
│ TC_ACT_STOLEN → consume_skb() → 消费(不传递上层)
│ TC_ACT_OK → 继续正常接收路径
│
└── 4. 继续正常接收(rx_handler → 协议分发 → socket)
Ingress 的特殊设计:
- Ingress 使用
mini_Qdisc而非完整struct Qdisc,开销更小 mini_Qdisc直接持有filter_list(tcf_proto 链表头),跳过 Qdisc 的 enqueue/dequeue 流程- 不需要排队,因为 ingress 的处理目标是分类 + 动作,而非整形
3.3 tc_run() 分类器执行流程
// net/core/dev.c:4386
static int tc_run(struct tcx_entry *entry, struct sk_buff *skb,
enum skb_drop_reason *drop_reason)
{
struct mini_Qdisc *miniq = rcu_dereference_bh(entry->miniq);
// 全局旁路检查
if (!static_branch_likely(&tcf_sw_enabled_key))
return TC_ACT_UNSPEC;
// 块级旁路检查
if (tcf_block_bypass_sw(miniq->block))
return TC_ACT_UNSPEC;
// 更新统计
mini_qdisc_bstats_cpu_update(miniq, skb);
// 核心:调用 tcf_classify() 遍历分类器链
ret = tcf_classify(skb, miniq->block, miniq->filter_list, &res, false);
// 处理 verdict
switch (ret) {
case TC_ACT_SHOT:
mini_qdisc_qstats_cpu_drop(miniq);
break;
case TC_ACT_OK:
case TC_ACT_RECLASSIFY:
skb->tc_index = TC_H_MIN(res.classid);
break;
}
return ret;
}
3.4 Flower 分类器匹配过程
以最常用的 cls_flower 为例(cls_flower.c:320):
int fl_classify(struct sk_buff *skb, const struct tcf_proto *tp,
struct tcf_result *res)
{
struct cls_fl_head *head = rcu_dereference_bh(tp->root);
// 遍历所有 mask(每个 mask 对应一组过滤规则)
list_for_each_entry_rcu(mask, &head->masks, list) {
// 1. 清零并提取 skb 关键字段
fl_clear_masked_range(&skb_key, mask);
// 2. 使用 dissector 解析包头
skb_flow_dissect_meta(skb, &mask->dissector, &skb_key);
skb_key.basic.n_proto = skb_protocol(skb, false);
skb_flow_dissect_tunnel_info(skb, &mask->dissector, &skb_key);
skb_flow_dissect(skb, &mask->dissector, &skb_key, ...);
// 3. 在 mask 的哈希表中查找匹配的过滤器
f = fl_mask_lookup(mask, &skb_key);
if (f && !tc_skip_sw(f->flags)) {
*res = f->res; // 设置分类结果(classid)
return tcf_exts_exec(skb, &f->exts, res);
// 执行过滤器关联的 actions(mirred, gact, etc.)
}
}
return -1; // 无匹配
四、Qdisc 创建与管理
4.1 初始化流程
dev_init_scheduler() // sch_generic.c:1478
│
├── dev->qdisc = &noop_qdisc // 根 qdisc 设为 noop
├── 每个 TX 队列: qdisc = noop_qdisc
└── ingress 队列: qdisc = noop_qdisc
dev_activate() // sch_generic.c:1264
│
├── attach_default_qdiscs() // 为设备挂载默认 qdisc
│ │
│ ├── 单队列设备 → attach_one_default_qdisc()
│ │ 创建 default_qdisc_ops (通常 pfifo_fast)
│ │
│ └── 多队列设备 → qdisc_create_dflt(&mq_qdisc_ops)
│ 创建 MQ qdisc,每个子队列挂载默认 qdisc
│
└── transition_one_qdisc() // 将 sleeping qdisc 切换为 active
rcu_assign_pointer(txq->qdisc, new_qdisc)
4.2 用户空间配置
通过 tc 命令行工具,经由 Netlink 与内核交互:
tc qdisc add dev eth0 root htb default 30
│
│ RTM_NEWQDISC
▼
tc_modify_qdisc() // sch_api.c:1795
│
└── __tc_modify_qdisc()
│
├── 查找父 qdisc (p)
├── 查找现有 qdisc (q) 或创建新的
│
└── qdisc_create() // sch_api.c:1244
│
├── qdisc_lookup_ops(kind) // 通过名字查找 Qdisc_ops
├── qdisc_alloc(dev_queue, ops) // 分配 Qdisc 实例
├── ops->init(sch, opt) // 初始化 Qdisc
└── qdisc_hash_add(sch) // 加入全局哈希表
tc filter add dev eth0 protocol ip parent 1:0 flower \
src_ip 10.0.0.1 dst_ip 10.0.0.2 ip_proto tcp action mirred egress redirect dev eth1
│
│ RTM_NEWTFILTER
▼
tc_ctl_filter() // cls_api.c
│
├── tcf_block_get() // 获取/创建 tcf_block
├── tcf_chain_lookup() // 查找 tcf_chain
└── tp->ops->change() // 创建过滤器规则
(例如 fl_change() 创建 flower 规则)
4.3 Qdisc 生命周期
创建: qdisc_create() → ops->init()
│
▼
激活: dev_activate() → transition_one_qdisc()
rcu_assign_pointer(txq->qdisc, qdisc)
│
▼
运行: __dev_xmit_skb() → enqueue/dequeue 循环
│
▼
停用: dev_deactivate() → qdisc_deactivate()
set_bit(__QDISC_STATE_DEACTIVATED)
rcu_assign_pointer(txq->qdisc, &noop_qdisc)
│
▼
销毁: qdisc_destroy() → ops->destroy()
qdisc_put() → kfree_rcu()
五、主要 Qdisc 实现
5.1 pfifo_fast(默认 Qdisc)
- 定义:
sch_generic.c:942 - 算法:三优先级带 FIFO(3 bands),按
skb->priority映射到 band - 特性:支持
TCQ_F_CAN_BYPASS快速路径 - 入队:
pfifo_fast_enqueue()→skb_array_produce()到对应 band - 出队:
pfifo_fast_dequeue()→ 从 band 0 开始依次尝试消费
5.2 TBF(Token Bucket Filter)
- 定义:
sch_tbf.c - 算法:令牌桶算法,两个 TBF 叠加
- 外层 TBF(rate R, depth B):控制稳态速率和突发
- 内层 TBF(peak rate P, depth M):限制短时间突发
- 公式:任意时间窗口
[t_i, t_k]内,发送总量 ≤ B + R × (t_k - t_i) - 实现:维护
tokens(令牌)和ptokens(峰值令牌),通过hrtimer定时补充
5.3 HTB(Hierarchical Token Bucket)
- 定义:
sch_htb.c - 算法:分层令牌桶,支持层级带宽借用
- 类模式(
htb_cmode):HTB_CAN_SEND:有令牌,可以直接发送HTB_MAY_BORROW:自身无令牌,但可向父类借用HTB_CANT_SEND:无法发送也无法借用
- 特点:支持 strict priority + rate limiting,适合 ISP/企业级 QoS
5.4 FQ-CoDel(Fair Queuing + Controlled Delay)
- 定义:
sch_fq_codel.c - 算法:基于 CoDel 主动队列管理 + 多流公平排队
- 核心思想:
- 按 flow hash 分流到多个子队列(公平性)
- 每个子队列使用 CoDel 算法管理队列延迟
- 目标延迟 5ms,检测到 overlimit 时丢包
- 效果:解决 bufferbloat 问题,改善交互式应用延迟
5.5 其他常用 Qdisc
| Qdisc | 文件 | 用途 |
|---|---|---|
prio |
sch_prio.c | 严格优先级调度,将包分到多个 band |
red |
sch_red.c | Random Early Detection 主动丢弃 |
sfq |
sch_sfq.c | Stochastic Fair Queuing 公平排队 |
netem |
sch_netem.c | 模拟网络损伤(延迟、丢包、乱序) |
cake |
sch_cake.c | Common Applications Kept Enhanced,综合优化 |
taprio |
sch_taprio.c | Time-Aware Priority Shaper,TSN/802.1Qbv |
clsact |
sch_ingress.c | 同时支持 ingress 和 egress 过滤 |
六、Classifier 实现
6.1 主要分类器
| 分类器 | 文件 | 匹配方式 |
|---|---|---|
flower |
cls_flower.c | 基于 L2-L4 流字段(五元组、VLAN、tunnel 等) |
u32 |
cls_u32.c | 基于包头任意偏移的 32 位模式匹配 |
fw |
cls_fw.c | 基于 skb->mark 值匹配 |
bpf |
cls_bpf.c | 运行 BPF 程序进行自定义分类 |
matchall |
cls_matchall.c | 匹配所有包(用于硬件卸载) |
route |
cls_route.c | 基于路由信息分类 |
basic |
cls_basic.c | 基础分类器,配合 ematch 使用 |
6.2 Flower 分类器详解
Flower 是现代 Linux 中最常用的分类器,支持丰富的匹配字段:
// 五元组匹配
TCA_FLOWER_KEY_ETH_DST // 源/目的 MAC
TCA_FLOWER_KEY_IPV4_SRC/DST // 源/目的 IPv4
TCA_FLOWER_KEY_IPV6_SRC/DST // 源/目的 IPv6
TCA_FLOWER_KEY_TCP_SRC/DST // 源/目的端口
TCA_FLOWER_KEY_IP_PROTO // 协议号
// VLAN / Tunnel
TCA_FLOWER_KEY_VLAN_ID/PRIO // VLAN ID 和优先级
TCA_FLOWER_KEY_ENC_* // 隧道封装字段
// Conntrack
TCA_FLOWER_KEY_CT_STATE // 连接追踪状态
TCA_FLOWER_KEY_CT_MARK // 连接追踪标记
6.3 Action 系统
Action 是分类器匹配后执行的操作:
// 核心动作定义 (pkt_cls.h)
TC_ACT_OK // 通过,继续处理
TC_ACT_SHOT // 丢弃
TC_ACT_RECLASSIFY // 重新分类
TC_ACT_PIPE // 继续执行下一个 action
TC_ACT_STOLEN // 消费(不释放,但不再传递)
TC_ACT_QUEUED // 入队
TC_ACT_REDIRECT // 重定向
TC_ACT_TRAP // 陷阱到 CPU
常用 Action 实现:
act_mirred(act_mirred.c):镜像或重定向包act_gact(act_gact.c):通用动作(丢弃/通过/随机)act_skbedit(act_skbedit.c):修改 skb 字段(priority、mark、queue_mapping)act_pedit(act_pedit.c):编辑包头任意字段act_nat(act_nat.c):NAT 操作act_bpf(act_bpf.c):运行 BPF 程序
七、硬件卸载(Offload)
TC 支持将分类和动作卸载到支持的网卡硬件中执行:
用户空间 tc filter add ... skip_sw
│
▼
tc_setup_flow_offload() // cls_api.c
│
├── tp->ops->reoffload() // 分类器卸载
│ 例如 fl_reoffload() → flow_indr_add_offload()
│
└── 设备驱动回调
ndo_setup_tc(TC_SETUP_CLSFLOWER)
→ 驱动将规则安装到硬件
卸载标志:
TCA_CLS_FLAGS_SKIP_SW:不使用软件路径TCA_CLS_FLAGS_SKIP_HW:不卸载到硬件TCA_CLS_FLAGS_IN_HW:规则已卸载到硬件
八、关键流程总结
8.1 发送路径完整调用链
dev_queue_xmit(skb)
→ __dev_queue_xmit(skb)
→ sch_handle_egress(skb) // Egress 分类
→ tc_run(entry, skb)
→ tcf_classify(skb, block, tp, res)
→ tp->ops->classify(skb, tp, res) // 具体分类器
→ tcf_exts_exec(skb, exts, res) // 执行 actions
→ txq = netdev_core_pick_tx(dev, skb) // 选择 TX 队列
→ q = txq->qdisc // 获取 Qdisc
→ __dev_xmit_skb(skb, q, dev, txq)
→ dev_qdisc_enqueue(skb, q) // 入队
→ q->enqueue(skb, q, to_free) // Qdisc 入队操作
→ qdisc_run(q)
→ __qdisc_run(q)
→ qdisc_restart(q)
→ dequeue_skb(q) // 出队
→ q->dequeue(q) // Qdisc 出队操作
→ sch_direct_xmit(skb) // 发送到驱动
→ dev_hard_start_xmit(skb)
→ ndo_start_xmit(skb, dev) // 驱动发送
8.2 接收路径完整调用链
__netif_receive_skb_core(skb)
→ sch_handle_ingress(skb) // Ingress 分类
→ tc_run(entry, skb)
→ tcf_classify(skb, block, tp, res)
→ tp->ops->classify(skb, tp, res) // 具体分类器
→ switch(sch_ret):
TC_ACT_REDIRECT → skb_do_redirect(skb)
TC_ACT_SHOT → kfree_skb_reason(skb)
TC_ACT_OK → 继续
→ 协议分发 (ip_rcv, ipv6_rcv, etc.)
九、总结
Linux TC 是一个功能强大且高度可扩展的流量控制框架,其设计体现了以下核心原则:
- 分层解耦:分类、动作、调度三者独立,可自由组合
- 可扩展性:通过
Qdisc_ops、tcf_proto_ops、tcfa_act等操作函数集实现插件化 - 性能优化:快速路径 bypass、per-CPU 统计、无锁 Qdisc、硬件卸载
- 层级化:支持 Qdisc 嵌套、class 层次、chain 组织,适应复杂 QoS 场景
- RCU 友好:数据路径大量使用 RCU 读取,控制路径使用 RTNL 锁
- 硬件协同:支持将分类和动作卸载到网卡,减少 CPU 开销







