惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Forbes - Security
Forbes - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
L
LangChain Blog
量子位
GbyAI
GbyAI
B
Blog RSS Feed
月光博客
月光博客
人人都是产品经理
人人都是产品经理
腾讯CDC
Recent Announcements
Recent Announcements
Microsoft Azure Blog
Microsoft Azure Blog
I
InfoQ
The Cloudflare Blog
D
Docker
Cyberwarzone
Cyberwarzone
U
Unit 42
NISL@THU
NISL@THU
C
Check Point Blog
B
Blog
大猫的无限游戏
大猫的无限游戏
Cisco Talos Blog
Cisco Talos Blog
Recorded Future
Recorded Future
H
Hackread – Cybersecurity News, Data Breaches, AI and More
J
Java Code Geeks
G
GRAHAM CLULEY
Engineering at Meta
Engineering at Meta
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 叶小钗
P
Proofpoint News Feed
F
Fortinet All Blogs
V
V2EX
T
Threat Research - Cisco Blogs
T
Threatpost
S
SegmentFault 最新的问题
Know Your Adversary
Know Your Adversary
雷峰网
雷峰网
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园 - 司徒正美
P
Privacy & Cybersecurity Law Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
TaoSecurity Blog
TaoSecurity Blog
Latest news
Latest news
Apple Machine Learning Research
Apple Machine Learning Research
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Y
Y Combinator Blog
P
Privacy International News Feed
L
Lohrmann on Cybersecurity
AWS News Blog
AWS News Blog
G
Google Developers Blog
美团技术团队

土法炼钢兴趣小组的算法知识备份

国密算法与国密 TLS 系列索引 【系统架构设计】架构质量属性:不只是"高可用高性能" 【系统架构设计百科】告警策略:如何避免"狼来了" 【系统架构设计】CQRS:读写分离的架构哲学 【系统架构设计】空间架构:极端扩展场景的解法 【系统架构设计】微服务架构深度审视:优势、代价与适用边界 【系统架构设计】扩展性原理:水平、垂直与对角扩展 【系统架构设计】无状态设计:扩展的第一步也是最难的一步 【系统架构设计】缓存架构:从本地到分布式的多级缓存体系 【系统架构设计】管道与过滤器:Unix 哲学的架构表达 【系统架构设计】复杂性管理:架构的核心战场 【系统架构设计】消息队列架构:异步解耦的设计与陷阱 【系统架构设计】CDN 架构:全球加速的设计原理 【系统架构设计】连接池设计:被忽视的性能杀手 【系统架构设计】弹性设计模式:熔断器、舱壁与超时 【系统架构设计】高可用设计模式:冗余、故障转移与仲裁 【系统架构设计】容量规划:从拍脑袋到数据驱动 【系统架构设计】数据库扩展:分库分表的工程实践与替代方案 【系统架构设计】SLO 工程:可靠性的量化管理 【系统架构设计】性能建模:用数学思维分析系统瓶颈 【系统架构设计】混沌工程:主动验证系统的韧性 【系统架构设计】零拷贝与内存映射:数据搬运的极致优化 【系统架构设计】线程模型:从 thread-per-request 到协程 【系统架构设计】容灾架构:多活与灾备设计 【系统架构设计】数据库性能模式:索引、查询与连接管理 【系统架构设计】数据建模:从关系范式到文档模型的真实权衡 【系统架构设计】吞吐量优化:批处理、流水线与并发模型 【系统架构设计】流处理架构:从批处理到实时的范式迁移 【系统架构设计】搜索引擎架构:倒排索引之上的系统设计 【系统架构设计】时序数据架构:监控与 IoT 的存储设计 【系统架构设计】数据迁移与版本化:在线不停机的数据演进 【系统架构设计】数据湖与数据仓库:分析架构的演进路线 【系统架构设计】API 网关设计:入口层的职责边界 【系统架构设计】应用层数据一致性模式:在正确性与性能之间走钢丝 【系统架构设计】多模数据库选型:Polyglot Persistence 的工程实践 【系统架构设计】服务发现与注册:动态拓扑的基础设施 【系统架构设计】配置管理架构:从配置文件到配置中心 【系统架构设计】全链路压测:大规模系统的性能验证 【系统架构设计】幂等性设计:分布式环境下的安全重试 【系统架构设计】契约测试与 Schema 演进:服务间的信任协议 【系统架构设计】长连接与推送架构:WebSocket、SSE 与 MQTT 【系统架构设计】延迟分析:从 P50 到 P999 的全链路追踪 【系统架构设计百科】DDD 战术模式:聚合、实体与值对象 【系统架构设计百科】防腐层与开放主机服务:系统集成的 DDD 方案 【系统架构设计百科】领域事件与事件风暴:从业务到架构的桥梁 【系统架构设计百科】CQRS + Event Sourcing 完整实战:从领域建模到部署 【系统架构设计百科】DDD 与微服务:用领域模型划分服务边界 【系统架构设计】DDD 战略设计:限界上下文与上下文映射 【系统架构设计百科】认证架构:从 Session 到 JWT 到 OIDC 【系统架构设计】API 设计哲学:REST vs GraphQL vs gRPC 的真实权衡 排序算法专题:从 TimSort 到并行排序 【密码学百科】国密算法体系:SM2/SM3/SM4/SM9 全景解读 【密码学百科】承诺方案:Pedersen 承诺、向量承诺与多项式承诺 【密码学百科】不经意传输与隐私信息检索:OT、OT 扩展与 PIR 【密码学百科】门限密码学:门限签名、门限解密与分布式密钥生成 完美哈希:从理论到 gperf 实践 【密码学百科】安全多方计算:从 Yao 的混淆电路到实用 MPC 【密码学百科】同态加密:从 Paillier 到全同态加密(FHE) 【密码学百科】零知识证明系统:zk-SNARKs、zk-STARKs 与 Bulletproofs 【密码学百科】概率论与密码分析:生日攻击、差分分析与线性分析 【密码学百科】计算复杂性与归约:密码安全性证明的基石 【密码学百科】秘密共享:Shamir 方案、VSS 与安全多方计算入口 【密码学百科】椭圆曲线代数:Weierstrass 方程、点群运算与曲线选择 【密码学百科】离散对数与配对密码学:从 DLP 到 BLS 签名 【密码学百科】格密码数学基础:SVP、LWE 与格基约化 【密码学百科】抽象代数:群、环、域的密码学视角 【密码学百科】有限域算术:GF(2^n) 运算与在 AES/ECC 中的应用 【密码学百科】数论进阶:二次剩余、椭圆曲线上的 Weil 配对 【密码学百科】密码学简史:从凯撒密码到量子时代 【密码学百科】威胁模型与安全目标:CIA 三要素之外 【密码学百科】Kerckhoffs 原则与现代密码设计哲学 【密码学百科】随机性:密码学的基石 【密码学百科】信息论入门:熵、完美保密与 Shannon 定理 【密码学百科】分组密码原理:Feistel 网络与 SPN 结构 【密码学百科】AES 逐步拆解:SubBytes 到 MixColumns 的数学 【密码学百科】分组密码工作模式全览:ECB/CBC/CTR/OFB/CFB 【密码学百科】流密码:RC4 的兴衰与 ChaCha20 的崛起 【密码学百科】密码学哈希函数:MD5→SHA-2→SHA-3 的进化之路 【密码学百科】MAC 与 HMAC:消息认证的正确姿势 【密码学百科】认证加密(AEAD):GCM、ChaCha20-Poly1305 与 OCB 【密码学百科】密钥派生函数:HKDF、PBKDF2、Argon2 与密码存储 【密码学百科】公钥密码的数论基础:模运算、群、原根 【密码学百科】RSA 从原理到攻击:教科书 RSA 为什么不安全 【密码学百科】Diffie-Hellman 密钥交换与离散对数问题 【密码学百科】椭圆曲线密码学(ECC):从几何直觉到点群运算 【密码学百科】数字签名:ECDSA、EdDSA 与 Schnorr 签名 【密码学百科】现代密钥交换:X25519、ECDHE 与前向保密 【密码学百科】混合加密与 KEM/DEM 范式:ECIES 与 HPKE 【密码学百科】填充方案:PKCS#1 v1.5、OAEP 与 PSS 【密码学百科】TLS 协议全解析:从握手到 0-RTT 【密码学百科】PKI 与数字证书:信任链的构建与崩塌 【密码学百科】密码认证协议:从 SRP 到 OPAQUE 【密码学百科】零知识证明入门:如何证明你知道而不泄露 【密码学百科】安全信道构造:Noise 协议框架与 Signal 协议 【密码学百科】密钥管理工程:HSM、KMS 与密钥生命周期 【密码学百科】侧信道攻击:从时序攻击到功耗分析 【密码学百科】密码学实现陷阱:三层漏洞分类、审计工具链与系统性预防 密码敏捷性:如何设计可升级的密码系统 【密码学百科】OpenSSL/BoringSSL 架构剖析:ENGINE、Provider 与 FIPS 模块 排序基准测试:用数据说话
【Linux 网络子系统深度拆解】Traffic Control 深度拆解:qdisc、class 与 filter
2026-04-23 · via 土法炼钢兴趣小组的算法知识备份

发包路径全解 一文中,我们看到 dev_queue_xmit() 在把包交给驱动之前,经过了 qdisc 的排队和调度。那篇文章给出了发包路径的全景——本文深入 Traffic Control 框架本身。

TC 的核心问题:当多个应用同时发包,谁先发、发多快、怎么限速?这不是简单的先进先出——内核需要支持带宽分配、优先级调度、延迟控制、公平排队等复杂策略。TC 框架通过三层抽象实现:

   应用层:tc qdisc / tc filter / tc class 命令
   ───────────────────────────────────────────
   内核层:
   ┌──────────────────────────────────────────┐
   │ filter(分类器)                           │
   │  └── tcf_proto: 根据包特征选择 class       │
   │       ├── u32: 通用字段匹配               │
   │       ├── flower: 流表匹配                │
   │       └── bpf: BPF 程序分类               │
   ├──────────────────────────────────────────┤
   │ class(分类)                              │
   │  └── 仅分层 qdisc 有:HTB class、CBQ class│
   │       每个 class 可挂自己的子 qdisc        │
   ├──────────────────────────────────────────┤
   │ qdisc(排队规则)                          │
   │  └── Qdisc_ops: enqueue → dequeue → 驱动  │
   │       ├── pfifo_fast: 三优先级 FIFO       │
   │       ├── fq_codel: 公平排队 + 延迟控制   │
   │       ├── htb: 分层令牌桶                  │
   │       └── fq: 公平排队 + pacing            │
   └──────────────────────────────────────────┘

一、struct Qdisc:排队规则对象

核心结构

struct Qdiscinclude/net/sch_generic.h:73)是 TC 框架的核心——每个发送队列(netdev_queue)上挂载一个 qdisc 实例:

struct Qdisc {
    int (*enqueue)(struct sk_buff *skb,
                   struct Qdisc *sch,
                   struct sk_buff **to_free);  /* 入队 */
    struct sk_buff *(*dequeue)(struct Qdisc *sch); /* 出队 */
    unsigned int    flags;               /* TCQ_F_* 标志 */

    const struct Qdisc_ops  *ops;        /* 操作表 */
    struct netdev_queue     *dev_queue;   /* 所属发送队列 */

    struct qdisc_skb_head    q;          /* 内部包队列 */
    struct gso_skb           gso_skb;    /* GSO 大包暂存 */

    unsigned long            state;       /* 运行状态 */
    struct Qdisc            *parent;     /* 父 qdisc */

    /* 统计 */
    struct gnet_stats_basic_sync bstats;  /* 基本统计 */
    struct gnet_stats_queue  qstats;      /* 队列统计 */

    /* 并发控制 */
    seqcount_t               running;     /* 无锁运行标记 */
    spinlock_t               busylock;    /* 并发保护 */
    spinlock_t               seqlock;     /* 序列锁 */

    long                     privdata[] ____cacheline_aligned;
};

关键标志

标志 含义
TCQ_F_BUILTIN 内建 qdisc(不可删除)
TCQ_F_CAN_BYPASS 队列空时可跳过排队直接发送
TCQ_F_MQROOT MQ 多队列根 qdisc
TCQ_F_NOLOCK 无锁 qdisc(fq、fq_codel 等)

Qdisc_ops:操作表

struct Qdisc_opssch_generic.h:287)定义 qdisc 实现的接口:

struct Qdisc_ops {
    struct Qdisc_ops        *next;       /* 链表 */
    const struct Qdisc_class_ops *cl_ops;/* 分类操作(分层 qdisc 才有) */
    char                    id[IFNAMSIZ];/* 名称:"pfifo_fast"、"htb" */
    int                     priv_size;   /* 私有数据大小 */
    unsigned int            static_flags;/* 默认标志 */

    int  (*enqueue)(struct sk_buff *, struct Qdisc *,
                    struct sk_buff **to_free);
    struct sk_buff *(*dequeue)(struct Qdisc *);
    struct sk_buff *(*peek)(struct Qdisc *);

    int  (*init)(struct Qdisc *, struct nlattr *arg,
                 struct netlink_ext_ack *);
    void (*reset)(struct Qdisc *);
    void (*destroy)(struct Qdisc *);
    int  (*change)(struct Qdisc *, struct nlattr *arg,
                   struct netlink_ext_ack *);
    int  (*dump)(struct Qdisc *, struct sk_buff *);
    int  (*dump_stats)(struct Qdisc *, struct gnet_dump *);
};

二、发送路径中的 TC

dev_queue_xmit 到 qdisc

回顾发包路径中 TC 的位置(详见 发包路径全解):

dev_queue_xmit(skb)
    → __dev_queue_xmit(skb, sb_dev)
        → 选择 TX 队列:netdev_pick_tx()
        → 获取队列的 qdisc:rcu_dereference(txq->qdisc)
        → __dev_xmit_skb(skb, q, dev, txq)

__dev_xmit_skb:快路径与慢路径

/* 简化逻辑 */
static inline int __dev_xmit_skb(struct sk_buff *skb,
                                  struct Qdisc *q, ...)
{
    /* 快路径:队列空且支持 bypass */
    if ((q->flags & TCQ_F_CAN_BYPASS) && qdisc_empty(q) &&
        qdisc_run_begin(q)) {
        /* 跳过排队,直接发送 */
        sch_direct_xmit(skb, q, dev, txq, root_lock);
        qdisc_run_end(q);
        return NET_XMIT_SUCCESS;
    }

    /* 慢路径:入队 */
    rc = q->enqueue(skb, q, &to_free);
    if (qdisc_run_begin(q)) {
        __qdisc_run(q);  /* 出队循环 */
        qdisc_run_end(q);
    }
    return rc;
}

CAN_BYPASS 优化:当 qdisc 队列为空(没有积压)且标志允许,直接跳过 enqueue/dequeue 流程,走 sch_direct_xmit() 发送。对于低负载场景,这避免了不必要的排队和调度开销。pfifo_fast、fq_codel 等默认都支持 bypass。

__qdisc_run:出队循环

void __qdisc_run(struct Qdisc *q)
{
    int quota = dev_tx_weight;  /* 默认 64 */

    while (qdisc_restart(q, &packets)) {
        quota -= packets;
        if (quota <= 0) {
            /* 配额用尽,调度 NET_TX_SOFTIRQ 稍后继续 */
            __netif_schedule(q);
            break;
        }
    }
}

qdisc_restart() 每次调用 q->dequeue() 取一个包,交给 sch_direct_xmit() 发送到驱动。dev_tx_weight(默认 64)限制每次软中断处理的包数——防止单个 qdisc 独占 CPU。

无锁 qdisc(TCQ_F_NOLOCK)

传统 qdisc 用 qdisc->root_lock 保护——所有 CPU 竞争同一把锁。现代 qdisc(fq、fq_codel)使用 TCQ_F_NOLOCK 标志,配合 seqlockQDISC_STATE_MISSED 标志实现无锁并发:

static inline bool qdisc_run_begin(struct Qdisc *qdisc)
{
    if (qdisc->flags & TCQ_F_NOLOCK) {
        if (spin_trylock(&qdisc->seqlock))
            return true;
        /* 竞争失败:设置 MISSED 标志,通知持有者 */
        set_bit(__QDISC_STATE_MISSED, &qdisc->state);
        /* 再试一次(避免 MISSED 丢失) */
        if (spin_trylock(&qdisc->seqlock))
            return true;
        return false;
    }
    /* 传统 qdisc:测试并设置 RUNNING 位 */
    return !__test_and_set_bit(__QDISC_STATE2_RUNNING, ...);
}

MISSED 标志的作用:CPU-A 持有锁正在出队,CPU-B 入队后发现拿不到锁——设置 MISSED。CPU-A 在 qdisc_run_end() 检查 MISSED,如果有则重新调度出队——保证 CPU-B 入队的包不会被遗忘。

pfifo_fast:默认 qdisc

pfifo_fast 维护三个优先级 band(band 0 最高),根据 TOS/DSCP 映射选择 band:

Band 0(高优先级)← TOS: Minimize-Delay
Band 1(普通)    ← 大多数流量
Band 2(低优先级)← TOS: Maximize-Throughput

dequeue 时从 band 0 开始——只有高优先级 band 为空才取下一级。

特点:实现极简、零开销,但无公平排队——一个大流量可以饿死同 band 的其他流。

fq_codel:公平排队 + 延迟控制

fq_codel(Fair Queuing Controlled Delay)是现代 Linux 的推荐 qdisc——解决 bufferbloat 问题:

入队:
skb → hash(五元组) → 分配到 1024 个桶之一
    → 桶内 FIFO

出队:
DRR(Deficit Round Robin)→ 轮流从各桶出队
    → CoDel AQM → 检测排队延迟
        → 延迟 > target(5ms) 持续 > interval(100ms)
            → 开始丢包(概率递增)

CoDel 算法的核心洞察:排队延迟才是 bufferbloat 的信号,而不是队列长度。CoDel 测量每个包在队列中停留的时间——如果排队延迟持续超过 target(默认 5ms),开始概率性丢包,强迫发送端降速。

fq 与 fq_codel 的区别:fq 支持 pacing——根据 TCP 的 EDT(Earliest Departure Time)时间戳控制每个包的精确发送时间。BBR 拥塞控制依赖 fq 的 pacing 能力。

HTB:分层令牌桶

HTB(Hierarchical Token Bucket)支持树形带宽分配:

tc qdisc add dev eth0 root handle 1: htb default 30
tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 30mbit ceil 60mbit
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 50mbit ceil 80mbit
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 20mbit ceil 100mbit

每个 class 有两个参数: - rate:保证带宽(即使其他 class 满载也能获得) - ceil:最大带宽(可以借用父 class 的空闲带宽)

HTB 在内核中用红黑树管理 class 层级,令牌桶算法控制每个 class 的发送速率。当 class 用完自己的令牌但 ceil 未满时,可以从父 class”借”令牌。

TBF:简单令牌桶

TBF(Token Bucket Filter)是最简单的限速 qdisc:

# 限速 10Mbps,突发 32KB
tc qdisc add dev eth0 root tbf rate 10mbit burst 32kbit latency 400ms

令牌以 rate 速率填充桶,每发一个包消耗对应令牌。桶满时丢弃新到的令牌(不超过 burst)。无令牌时包在队列中等待,超过 latency 则丢弃。

四、EDT(Earliest Departure Time)

EDT 是现代 TC 的调度模型——不再问”每秒发多少包”,而是为每个包标记”这个包应该在什么时间发出”。

工作原理

TCP 层:
    skb->tstamp = ktime_get() + pacing_delay
    (BBR/CUBIC pacing 计算精确发送时间)
        ↓
fq qdisc:
    按 skb->tstamp 排序
    只有 tstamp ≤ 当前时间的包才出队
        ↓
驱动:
    支持 EDT 的网卡可进一步延迟到精确时间

EDT 的优势: 1. 精确 pacing:BBR 可以精确控制每个包的发送间隔 2. 消除排队:包在 qdisc 中按时间排序,不需要大队列 3. 端到端协同skb->tstamp 从 TCP 层传递到驱动,全路径感知

# 启用 fq qdisc(支持 EDT pacing)
tc qdisc replace dev eth0 root fq

五、filter(分类器)

tcf_proto:分类器实例

struct tcf_protosch_generic.h:406)是单个分类器:

struct tcf_proto {
    struct tcf_proto __rcu *next;
    void __rcu      *root;          /* 分类器特定的数据 */
    int (*classify)(struct sk_buff *,
                    const struct tcf_proto *,
                    struct tcf_result *);
    __be16           protocol;      /* 匹配的协议(ETH_P_IP 等) */
    const struct tcf_proto_ops *ops;/* 分类器类型操作 */
    struct tcf_chain *chain;        /* 所属链 */
    u32              prio;          /* 优先级 */
};

常用分类器

分类器 用途 特点
u32 通用字段匹配 可匹配任意包头偏移
flower 流表匹配 支持硬件卸载
bpf BPF 程序 最灵活,直接返回 classid
fw fwmark 匹配 配合 iptables MARK
route 路由匹配 基于路由 realm
basic 基础匹配 ematch 表达式

TC BPF:direct-action 模式

传统 TC 流程:filter 分类 → 选择 class → class 的 qdisc 处理。TC BPF 的 direct-action 模式跳过中间步骤——BPF 程序直接返回 TC_ACT 动作:

/* BPF 程序返回值 */
TC_ACT_OK      (0)  /* 放行 */
TC_ACT_SHOT    (2)  /* 丢弃 */
TC_ACT_PIPE    (3)  /* 传递给下一个 filter */
TC_ACT_REDIRECT(7)  /* 重定向到其他设备 */

内核对 TC BPF 有 retpoline 优化——tc_wrapper.h 中为 cls_bpf_classify 提供直接调用路径,避免间接函数调用的 Spectre 缓解开销:

/* tc_wrapper.h 中的快路径 */
if (IS_BUILTIN(CONFIG_NET_CLS_BPF)) {
    if (tp->classify == cls_bpf_classify)
        return cls_bpf_classify(skb, tp, res);  /* 直接调用 */
}
# 挂载 TC BPF 程序(direct-action 模式)
tc qdisc add dev eth0 clsact
tc filter add dev eth0 ingress bpf da obj prog.o sec classifier

六、MQ 多队列 qdisc

netdev_queue 与 qdisc 的关系

现代网卡有多个 TX 队列——每个队列是一个 struct netdev_queueinclude/linux/netdevice.h:640):

struct netdev_queue {
    struct Qdisc __rcu *qdisc;         /* 活跃 qdisc */
    struct Qdisc __rcu *qdisc_sleeping;/* 非活跃 qdisc */
    struct net_device  *dev;
    spinlock_t          _xmit_lock;     /* TX 锁 */
    unsigned long       trans_start;
    struct dql           dql;           /* BQL(动态队列限制) */
};

MQ 根 qdisc

多队列网卡默认使用 MQ qdisc 作为根——它本身不排队,只是把包分发给各队列的子 qdisc:

root: mq (TCQ_F_MQROOT)
├── queue 0: fq_codel (子 qdisc)
├── queue 1: fq_codel
├── queue 2: fq_codel
└── queue 3: fq_codel

每个子 qdisc 独立运行——不同 TX 队列的入队/出队可以并行,无锁竞争。

BQL(Byte Queue Limits)

struct dqlstruct netdev_queue 的成员)动态控制驱动层队列的深度——防止驱动 ring buffer 积攒过多包导致延迟增加:

BQL 算法:
1. 记录每次 xmit 提交的字节数
2. 记录驱动完成(interrupt)的字节数
3. 动态调整 limit = f(inflight, completions)
4. inflight > limit 时暂停提交,等驱动消化

BQL 与 qdisc 配合——qdisc 控制”怎么排队”,BQL 控制”排多深”。

七、可观测性实战

qdisc 状态查看

# 查看所有 qdisc 统计
tc -s qdisc show dev eth0

# 查看 class 统计(HTB)
tc -s class show dev eth0

# 查看 filter
tc filter show dev eth0

qdisc 丢包追踪

# 追踪 qdisc 丢包——enqueue 返回非零
bpftrace -e '
kretprobe:htb_enqueue /retval != 0/ {
    printf("htb enqueue DROP ret=%d\n", retval);
    @drops = count();
}
interval:s:10 { print(@drops); clear(@drops); }'

fq_codel 延迟监控

# fq_codel 的 CoDel 丢包事件
bpftrace -e '
tracepoint:qdisc:qdisc_dequeue {
    if (args->qdisc_handle == 0x00010000) {
        @dequeue_bytes = hist(args->skbaddr);
    }
}'

# 查看 fq_codel 内部统计
tc -s qdisc show dev eth0 | grep -A5 fq_codel

TC BPF 程序性能

# 查看 TC BPF 程序统计
tc filter show dev eth0 ingress

# BPF 程序执行时间
bpftool prog show
bpftool prog profile id <ID> duration 5

__qdisc_run 耗时

# 测量 qdisc 出队循环耗时
bpftrace -e '
kprobe:__qdisc_run { @start[tid] = nsecs; }
kretprobe:__qdisc_run /@start[tid]/ {
    @qdisc_run_us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}'

八、关键参数速查

参数 默认值 含义 调优建议
net.core.default_qdisc pfifo_fast 默认 qdisc 推荐 fq 或 fq_codel
net.core.dev_weight 64 每次 softirq 的出队包数 高吞吐可增大
net.core.dev_weight_tx_bias 1 TX/RX 权重比 发送密集型增大
fq_codel target 5ms CoDel 目标延迟 低延迟场景减小
fq_codel interval 100ms CoDel 检测间隔 通常不需调整
fq_codel quantum MTU DRR 调度量 通常不需调整
fq_codel flows 1024 哈希桶数 高并发可增大
fq pacing 1 启用 pacing BBR 必须开启
fq initial_quantum 10×MTU 新流首包配额 通常不需调整
HTB r2q 10 rate-to-quantum 转换系数 低速 class 可调

参考文献

  1. Linux 内核源码,include/net/sch_generic.h,6.8(struct Qdisc 于第 73 行,Qdisc_ops 于第 287 行)
  2. Linux 内核源码,include/net/pkt_sched.h,6.8(qdisc_run 于第 120 行)
  3. Linux 内核源码,include/net/pkt_cls.h,6.8(tcf_exts 于第 214 行)
  4. Linux 内核源码,include/net/tc_wrapper.h,6.8(cls_bpf_classify 快路径于第 144 行)
  5. Kathleen Nichols, Van Jacobson, “Controlling Queue Delay”, ACM Queue, 2012(CoDel 算法)
  6. Linux 内核文档,Documentation/networking/tc-actions-env-rules.rst

上一篇Netfilter 内核实现:钩子、conntrack 与 NAT

下一篇网络命名空间:内核级网络隔离的实现

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-07-22 · linux / networking

【Linux 网络子系统深度拆解】eBPF 网络钩子全景:TC/XDP/socket/cgroup

从内核源码全面拆解 eBPF 在网络子系统中的所有挂载点:TC BPF direct-action 模式与 bpf_mprog 多程序链、XDP 驱动级钩子回顾、socket ops 回调与 TCP 生命周期事件、cgroup BPF 策略控制、sk_msg/sk_skb 的 sockmap 重定向引擎、struct_ops 实现自定义拥塞控制,以及 bpftrace 可观测实战。

2026-04-27 · linux / networking

【Linux 网络子系统深度拆解】多队列与流量分发:RSS/RPS/RFS/XPS

单队列网卡的时代早已过去,但多队列本身只是起点——如何把包分到正确的 CPU 上,才是性能的关键。本文从 Linux 6.6 内核源码拆解多队列网络的完整流量分发体系:RSS 硬件哈希与 Toeplitz 算法、RPS 软件多队列模拟与 get_rps_cpu() 路径、RFS 应用感知的 rps_sock_flow_table 机制、XPS 发送端 CPU/队列亲和、aRFS 硬件流表加速,以及 netdev_pick_tx() 发送队列选择逻辑。

2026-04-20 · linux / networking

【Linux 网络子系统深度拆解】TCP 内核实现(下):数据传输与拥塞控制

tcp_sendmsg 把用户数据拷到 sk_buff 就完事了?远没有。后面还有 Nagle 合并、TSQ 限流、cwnd/rwnd 双窗口门控、RACK-TLP 丢包检测、拥塞状态机五态跳转、sk_pacing_rate 软件限速。本文从 Linux 6.6 内核源码拆解 TCP 数据传输的完整路径——从 send() 到 ACK 处理——以及拥塞控制框架 tcp_congestion_ops 的可插拔架构。

2026-04-20 · linux / networking

【Linux 网络子系统深度拆解】发包路径全解:从 send() 到网线

一个用户态 send() 调用要走过 TCP 分段、IP 路由、Netfilter 钩子、Qdisc 排队、GSO 分段、驱动 DMA 映射六个阶段才能把数据送上网线。本文从 Linux 6.6 内核源码出发,逐函数拆解完整的 TX 发包路径,深入 TSQ 限流、Qdisc 调度、BQL 防膨胀、GSO/TSO 分段决策等核心机制。