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 实现,其中的 graftleaf 回调负责子 qdisc 的挂载和查询。

2.3 Classifier 链式匹配

分类器通过 struct tcf_protonext 指针形成有序链表,按 prio(优先级)排序:

filter_chain → [u32 prio=1] → [flower prio=2] → [matchall prio=3] → NULL

匹配过程(__tcf_classify() in cls_api.c:1712):

  1. 遍历链表中的每个 tcf_proto
  2. 检查协议类型是否匹配(tp->protocol vs skb->protocol
  3. 调用具体分类器的 classify() 回调
  4. 如果返回 TC_ACT_RECLASSIFY,则从头重新开始
  5. 如果返回 TC_ACT_GOTO_CHAIN,则跳转到指定链
  6. 返回分类结果(classid 或 action verdict)

2.4 快速路径 vs 慢速路径

TC 设计了快速路径(Fast Path)优化,避免在无分类器时的开销:

ingress/clsact 的快速路径mini_Qdisc):

  • 使用 struct mini_Qdiscsch_generic.h:1455),只包含 filter_listblock 和统计信息
  • 不需要完整的 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 QdiscTCQ_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->bstatsq->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_mirredact_mirred.c):镜像或重定向包
  • act_gactact_gact.c):通用动作(丢弃/通过/随机)
  • act_skbeditact_skbedit.c):修改 skb 字段(priority、mark、queue_mapping)
  • act_peditact_pedit.c):编辑包头任意字段
  • act_natact_nat.c):NAT 操作
  • act_bpfact_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 是一个功能强大且高度可扩展的流量控制框架,其设计体现了以下核心原则:

  1. 分层解耦:分类、动作、调度三者独立,可自由组合
  2. 可扩展性:通过 Qdisc_opstcf_proto_opstcfa_act 等操作函数集实现插件化
  3. 性能优化:快速路径 bypass、per-CPU 统计、无锁 Qdisc、硬件卸载
  4. 层级化:支持 Qdisc 嵌套、class 层次、chain 组织,适应复杂 QoS 场景
  5. RCU 友好:数据路径大量使用 RCU 读取,控制路径使用 RTNL 锁
  6. 硬件协同:支持将分类和动作卸载到网卡,减少 CPU 开销