Linux内核默认规则qdisc发展: fq_codel / mq

一、前言

Ubuntu的默认qdisc规则会随内核版本迭代逐步优化,核心变化是从早期简单的先进先出规则,过渡到支持多队列、低延迟的智能调度规则,适配不同时期的网络需求。以下按内核版本阶段梳理其默认qdisc规则,结合对应Ubuntu发行版实例说明:

  1. 内核3.12之前(对应Ubuntu 14.04及更早版本)
    这一阶段默认qdisc为pfifo_fast。它是一种带优先级的先进先出队列规则,包含3个优先级队列(band),数据包会根据优先级映射到不同队列,高优先级队列的数据包优先发送,适配早期简单的网络传输场景。该规则逻辑简单、开销低,但不具备延迟控制和带宽公平分配能力,在高并发场景下易出现缓冲膨胀问题。
  2. 内核3.12 - 4.11(对应Ubuntu 16.04等版本)
    此阶段内核开始支持fq_codel,但默认qdisc仍以pfifo_fast为主。不过部分支持多队列网卡的设备会默认启用mq(多队列)调度器。mq本身不直接处理数据包调度,而是为网卡的每个硬件发送队列绑定独立的子qdisc,默认子qdisc依然是pfifo_fast。这种组合能将数据包处理负载分散到多个CPU核心,提升多线程网络场景的处理效率,适配当时逐步普及的多核服务器和千兆网卡。
  3. 内核4.12 - 5.14(对应Ubuntu 18.04、Ubuntu 20.04等版本)
    这一阶段是qdisc规则的过渡阶段,主要有两种情况:
    • 普通单队列网卡的默认qdisc逐步切换为fq_codel。该内核版本完善了fq_codel的稳定性,其融合公平调度与延迟控制能力,能解决pfifo_fast的缓冲膨胀问题,同时默认启用ECN拥塞通知,提升传输效率,适配视频通话、在线办公等对延迟敏感的场景。
    • 多队列网卡仍默认用mq作为顶层调度器,但其绑定的子qdisc从pfifo_fast替换为fq_codel。此外,部分用户为搭配BBR拥塞控制算法,会手动将默认qdisc修改为fq,但系统出厂默认仍以fq_codel和mq组合为主。
  4. 内核5.15及之后(对应Ubuntu 22.04、Ubuntu 24.04等新版本)
    该阶段默认qdisc正式全面稳定为fq_codel。一方面,内核对fq_codel的参数适配进一步优化,默认配置(如limit 10240数据包、target 5ms延迟阈值)可满足绝大多数场景;另一方面,针对多队列网卡,系统会自动协调mq与fq_codel的配合,顶层用mq实现多核负载分担,底层每个子队列用fq_codel管控延迟和带宽,兼顾性能与稳定性。此时pfifo_fast仅作为备用规则,仅在特殊兼容场景或网卡不支持复杂调度时触发。

若需确认当前Ubuntu系统的默认qdisc,可通过tc qdisc show dev 网卡名(如tc qdisc show dev eth0)查看具体网卡的调度规则,也可通过sysctl net.core.default_qdisc查看系统全局默认配置。

二、fq_codel

FQ_Codel(Fair Queue Controlled Delay,公平队列控制延迟)是一种融合公平排队与CoDel主动队列管理机制的队列调度算法,核心用于解决网络中的带宽抢占和缓冲区膨胀问题,由Eric Dumazet对CoDel算法改进而来,目前已广泛应用在Linux内核、路由器系统等网络设备中。以下是其详细介绍:

  1. 核心设计目标
    1. 解决带宽公平性问题:避免单一数据流(如大文件下载)独占网络带宽,确保DNS查询、视频会议等小流量或实时流量能获得足够带宽,保障各类应用的基本网络体验。
    2. 抑制缓冲区膨胀:缓冲区膨胀会导致数据包在队列中滞留过久,引发网络延迟激增。该算法通过动态控制队列延迟,减少不必要的数据包堆积,从根源缓解这一问题。
  2. 核心工作机制
    FQ_Codel的功能实现依赖两大核心模块的协同工作,实际并未采用传统FQ算法,而是通过DRR(赤字轮询)算法实现公平调度,搭配CoDel算法控制延迟。
    1. DRR公平调度模块:首先通过数据包的五元组(源IP、目的IP、源端口、目的端口、协议)哈希,将所有进入的流量划分为多个独立流队列,默认最多支持1024个流。之后采用DRR算法分配带宽,为每个队列设定“赤字额度”,调度时依次为队列分配发送机会,发送数据包后扣除对应额度,不足时则累积额度等待下一轮,以此保证各流公平占用带宽。同时新流会获得比旧流更高的优先级,减少新连接的等待延迟。
    2. CoDel延迟控制模块:每个独立流队列都会由CoDel算法单独管理。CoDel通过监控数据包在队列中的驻留时间,当延迟超过预设的target(默认5ms)且持续时长达到interval(默认100ms)时,触发主动丢包。丢包过程会从温和逐步转为激进,直到队列延迟回落至目标值;若延迟恢复正常,则重置丢包机制。这种方式能促使发送端感知拥塞并降低发送速率,避免队列持续拥堵。且CoDel内部采用FIFO队列,可避免单一流内部的数据包乱序。
  3. 关键配置参数
    该算法的参数可通过网络配置工具调整,适配不同场景,常见核心参数如下:
    参数默认值作用
    limit10240个数据包队列的最大容量,超出此限制的数据包会被直接丢弃
    flows1024可划分的最大流数量,配置时需提前分配哈希表内存
    target5ms数据包在队列中可接受的目标延迟
    interval100ms延迟检测窗口,用于确认延迟是否持续超标,需匹配网络瓶颈的往返时间
    quantum1514字节DRR调度的基本带宽单位,通常与网络接口的MTU值匹配
    ecn关闭是否启用显式拥塞通知,启用后会标记拥塞而非直接丢包,需网络设备协同支持
  4. 典型应用场景
    1. 家用与企业路由器:像OpenWrt、Tomato、DD - WRT等开源路由器系统,早在2014年OpenWrt 14.07版本中就将其作为默认队列管理方案,可解决多设备同时联网时的带宽争抢问题,保障网课、游戏等实时应用的流畅性。
    2. 服务器与数据中心:Linux内核3.11版本后引入该算法,4.12版本起将其作为默认队列调度策略,能保障服务器上多个服务的流量公平分发,避免单个高负载服务拖慢其他服务。
    3. 防火墙与网关设备:在OPNsense等防火墙系统中,FQ_Codel通过管道、队列和规则的组合配置,实现上下行流量的整形,适配不同带宽场景下的延迟控制需求。
  5. 核心优势
    1. 低配置成本:相比RED等传统队列算法,无需复杂的参数调优,默认参数即可适配多数网络场景,降低了运维难度。
    2. 兼顾公平与低延迟:既通过多流隔离避免带宽抢占,又通过精准的延迟监控减少数据包滞留,平衡了各类流量的传输需求。
    3. 资源开销低:算法逻辑简洁,对硬件的CPU和内存消耗较小,可适配从家用网关到数据中心设备的不同硬件规模。

三、mq

mq qdisc指Linux流量控制(TC)中的多队列排队规则,它是专门为多队列网卡设计的“桥接式”空壳排队规则,核心作用是对接网卡的多个硬件发送队列,本身不做复杂调度,仅负责流量分发和子排队规则管理,以下是其详细规则说明:

  1. 核心定位与设计初衷
    mq qdisc本质是一个“傀儡调度器”,自身没有独立的排队、丢包或带宽分配逻辑。在配备多队列网卡的Linux设备中,内核会自动初始化该规则,目的是将软件层面的流量与网卡的多个硬件发送队列(Tx queues)对应起来,通过并行处理多个队列的流量提升网卡数据传输效率,解决单队列网卡在高负载下的性能瓶颈。2009年它被加入Linux内核,是多队列网卡发挥性能的关键流量控制组件。
  2. 默认工作机制
    • 子队列初始化:创建mq qdisc时,会为网卡的每个硬件发送队列分配一个默认子排队规则,默认是pfifo_fast(基于优先级的先进先出队列)。比如网卡有4个硬件发送队列,mq qdisc就会生成4个pfifo_fast子队列,每个子队列对应一个硬件队列。
    • 流量分发方式:通过哈希函数对数据包进行计算,将不同流的流量分散到各个子队列中,避免单一子队列过度拥堵,实现流量的负载均衡。
  3. 关键特性与限制
    特性具体说明
    无独立调度能力自身不处理数据包的排序、丢包或优先级控制,所有核心调度逻辑均由挂载的子qdisc(如pfifo_fast、fq_codel等)实现
    强制队列匹配必须创建与网卡硬件发送队列数量一致的子qdisc,不允许内部创建独立队列,也无需额外配置数据包过滤器
    支持子规则替换默认子qdisc是pfifo_fast,但可根据需求替换为fq_codel、htb等更复杂的规则,适配不同场景(如低延迟、带宽限速等)
    仅适配多队列设备若网络设备是单队列网卡,mq qdisc无法初始化,系统会默认使用pfifo_fast等单队列规则
  4. 常用操作命令
    可通过Linux的tc工具对mq qdisc进行查看、配置等操作,常见命令如下:
    • 查看网卡的mq qdisc配置:查看指定网卡(如eth0)上的mq qdisc及流量统计,命令为tc -s qdisc show dev eth0。若网卡启用多队列,结果会显示qdisc mq及对应的发送字节数、数据包数量等信息。
    • 查看子队列(类)详情mq qdisc下的每个子qdisc以class形式存在,查看命令为tc -s class show dev eth0,可查看每个子队列的流量传输、丢弃情况。
    • 修改子队列默认规则:若需将默认的pfifo_fast替换为fq_codel,可先创建mq qdisc并添加类,再为每个类挂载fq_codel子规则,类似ns-3中的配置逻辑。
  5. 典型应用场景
    • 高性能服务器:多队列网卡的服务器(如数据中心服务器)使用mq qdisc,搭配fq_codel子规则,既能通过多队列并行传输提升吞吐量,又能解决带宽抢占和延迟问题,保障多服务同时运行时的网络稳定性。
    • 无线网络设备:在支持QoS的WiFi设备中(通常含多个传输队列),mq qdisc可对接不同优先级的流量(如语音、视频、普通数据),将高优先级流量映射到专属子队列,保障实时业务的传输质量。
    • 企业网关:多队列网关设备通过mq qdisc分散不同部门或业务的流量,后续可通过子规则对各队列设置带宽上限,实现精细化的流量管理。
zhp@newpc:~$ ip a # 查看所有网卡状态 route -n # 查看路由表(net-tools提供) ip rule list # 检查路由策略 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether b0:25:aa:63:e9:e7 brd ff:ff:ff:ff:ff:ff inet 192.168.13.101/24 brd 192.168.13.255 scope global noprefixroute enp4s0 valid_lft forever preferred_lft forever inet6 fe80::9787:1469:fe03:495f/64 scope link noprefixroute valid_lft forever preferred_lft forever 3: wlo1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 90:65:84:79:7d:7c brd ff:ff:ff:ff:ff:ff altname wlp0s20f3 inet 192.168.188.235/24 brd 192.168.188.255 scope global dynamic noprefixroute wlo1 valid_lft 3047sec preferred_lft 3047sec inet6 2408:844d:3700:3852:7e3e:9db5:4bd1:7656/64 scope global temporary dynamic valid_lft 7163sec preferred_lft 7163sec inet6 2408:844d:3700:3852:5bb3:e74e:d57f:307f/64 scope global dynamic mngtmpaddr noprefixroute valid_lft 7163sec preferred_lft 7163sec inet6 fe80::f193:92c1:800a:bc5b/64 scope link noprefixroute valid_lft forever preferred_lft forever 内核 IP 路由表 目标 网关 子网掩码 标志 跃点 引用 使用 接口 0.0.0.0 192.168.188.4 0.0.0.0 UG 600 0 0 wlo1 0.0.0.0 192.168.13.1 0.0.0.0 UG 20100 0 0 enp4s0 169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 enp4s0 192.168.13.0 0.0.0.0 255.255.255.0 U 100 0 0 enp4s0 192.168.188.0 0.0.0.0 255.255.255.0 U 600 0 0 wlo1 0: from all lookup local 32766: from all lookup main 32767: from all lookup default
06-26
这是一个**极其深刻且关键的问题**! > **“所以入队跟出队不是在同一个运行线上的吗?”** 答案是: --- ## ❌ 不在同一条执行路径上(不在同一个上下文) 入队和出队发生在 **完全不同的执行上下文、时间点、甚至 CPU 上**。 我们来从内核源码层面彻底解析这个机制。 --- ## ✅ 一、整体模型:生产者-消费者模式 qdisc 的排队系统本质上是一个典型的 **生产者-消费者模型**: | 角色 | 上下文 | 调用栈来源 | 功能 | |------|--------|------------|------| | **生产者(Producer)** | 进程或软中断上下文(发送数据包) | 协议栈 → `dev_queue_xmit()` | 调用 `enqueue` | | **消费者(Consumer)** | 软中断上下文(NET_TX_SOFTIRQ) | `net_tx_action()` | 调用 `dequeue` 并发包 | 它们通过一个共享队列(`struct sk_buff_head q`)通信,但执行流分离。 --- ## ✅ 二、入队(enqueue)发生在哪里? ### 调用路径: ```text [用户进程] ↓ send() ↓ tcp_write_xmit() ↓ __tcp_transmit_skb() ↓ ip_output() ↓ dev_queue_xmit() ← 关键入口 ↓ __dev_xmit_skb() ↓ qdisc_enqueue_root() ↓ q->ops->enqueue(skb, q) ``` ### 执行上下文: - 可能在 **进程上下文**(如应用调用 send) - 或 **软中断上下文**(如 TCP ACK 包由 softirq 发送) - 持有 `qdisc root lock`(spinlock) ### 示例代码: ```c // 在 dev_queue_xmit 中 int dev_queue_xmit(struct sk_buff *skb) { struct Qdisc *q = rcu_dereference_bh(txq->qdisc); if (q->enqueue) { rc = q->enqueue(skb, q); // ← 入队 if (rc == NET_XMIT_SUCCESS) { if (!q->running) __netif_schedule(q); // 触发出队调度 } } } ``` 📌 此时只是把包放进队列,并不立即发送。 --- ## ✅ 三、出队(dequeue)发生在哪里? ### 调用路径: ```text [软中断] ↓ raise_softirq(NET_TX_SOFTIRQ) ↓ net_tx_action() ↓ __netif_reschedule() ↓ __qdisc_run() ↓ while (quota-- && q->running) skb = q->dequeue(q) ← 出队 dev_hard_start_xmit(skb, ...) ``` ### 执行上下文: - **软中断上下文(softirq)** - 名为 `NET_TX_SOFTIRQ` - 由网卡驱动完成发送后触发,或由 `__netif_schedule()` 显式触发 ### 示例代码: ```c void __qdisc_run(struct Qdisc *q) { int quota = dev_tx_weight; // 默认 64 while (q->running && quota > 0) { struct sk_buff *skb = q->dequeue(q); // ← 出队 if (!skb) break; bool rc = dev_hard_start_xmit(skb, txq, ...); if (rc) { // 发送失败(TX busy),重新入队 q->ops->requeue(skb, q); break; } quota--; } if (q->running) __netif_reschedule(q); // 延迟到下次 softirq 处理 } ``` --- ## ✅ 四、为什么不能同步处理?为什么要分离开关? ### 核心原因:防止阻塞协议栈 如果每次 `enqueue` 都立刻尝试 `dequeue` 并等待网卡空闲: - 网卡忙时会导致进程卡住 - 高频发包会耗尽 CPU - 出现“发送抖动”或死锁风险 ✅ 所以内核采用 **异步解耦设计**: - enqueue:快速入队,返回成功 - dequeue:由软中断异步消费 这就像快递入库 vs 快递派送: - 你寄快递(enqueue)很快完成 - 快递员 later 才去取件发货(dequeue) --- ## ✅ 五、典型场景演示(以 pfifo_fast 为例) 假设我们有两个包要发送: ### 包1:入队(进程上下文) ```c dev_queue_xmit(skb1) └→ q->enqueue(skb1) → 加入 q->q 链表 └→ __netif_schedule(q) → 标记需要处理 ``` 此时函数返回,用户进程继续运行。 ### 包2:入队(同一进程) ```c dev_queue_xmit(skb2) └→ q->enqueue(skb2) → 再加一个包 ``` 现在队列里有两个包。 ### 软中断触发:出队(softirq 上下文) 稍后,内核调度 `NET_TX_SOFTIRQ`: ```c __qdisc_run(q) ├→ q->dequeue() → 取出 skb1 → 成功发送 ├→ q->dequeue() → 取出 skb2 → 成功发送 └→ 队列空了,停止运行 ``` 👉 完全跨时间和上下文! --- ## ✅ 六、多核环境下的复杂性 在 SMP 系统中: - 多个 CPU 可能同时向同一个设备的 qdisc 入队 - 但只有一个 CPU 能运行 `__qdisc_run()`(因为 `q->running` 标志 + lock) - 所以 qdisc 实际上是 **单消费者、多生产者模型** 这也是为什么现代网络使用 **multi-queue qdisc(如 mq)**,每个 TX 队列有自己的 qdisc,避免锁争用。 --- ## ✅ 七、总结对比表 | 特性 | 入队(enqueue) | 出队(dequeue) | |------|------------------|------------------| | 执行上下文 | 进程 / softirq | softirq(NET_TX_SOFTIRQ) | | 是否持有 qdisc lock | 是(spinlock) | 是(通常已持锁) | | 是否可能睡眠 | 否(原子上下文) | 否 | | 触发时机 | 协议栈发包时 | `__netif_schedule()` 或硬件中断后 | | 调用者 | `dev_queue_xmit()` | `__qdisc_run()` | | 目的 | 缓存包 | 发送包 | | 是否同步 | 否,完全异步 | --- ## ✅ 结论 > **入队和出队绝对不在同一条“运行线”上!** 它们是: - 时间上分离(先 enqueue,后 dequeue) - 上下文上分离(进程/softirq vs NET_TX_SOFTIRQ) - 逻辑上解耦(生产者-消费者模型) 这种设计保证了: - 协议栈不被网卡速度拖慢 - 支持流量整形、优先级调度 - 实现复杂的 QoS 策略 --- ###请结合linux4.4.115阐述
09-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ray.so

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值