Linux 组调度的 burst 带宽:突发负载的临时资源分配
简介
在容器云、K8s 集群、微服务分布式架构普及之后,基于 cgroup 的 CFS 带宽限流已经成为服务器资源隔离的标配手段。传统 CFS 带宽控制依靠cfs_quota_us/cfs_period_us硬性约束单个任务组在固定周期内最大 CPU 耗时,一旦进程瞬时业务峰值超出配额,任务会立刻被 throttle 节流挂起,直接出现业务卡顿、接口超时、容器 RT 飙升等线上故障。比如 Web 服务日常 CPU 负载仅占用配置配额的 30%,但促销秒杀、日志批量刷盘、定时批处理等瞬时突发场景下短时间 CPU 算力需求翻倍,传统固定带宽策略会直接限流,严重破坏业务可用性。
为解决闲时带宽闲置、忙时立刻被限流的矛盾,Linux 内核自 5.15 版本正式合入CFS Burst 突发带宽机制,新增cpu.cfs_burst_us配置项,核心设计思路是:任务组在调度周期内未用完的配额带宽自动留存累积,突发峰值阶段可以透支历史闲置带宽临时超额使用 CPU,整体长期平均 CPU 占用仍不超出配置配额,既保留资源隔离上限约束,又兼容业务短时峰值负载需求。
对后端开发、云原生运维、嵌入式 Linux 内核研发人员来说,吃透 Burst 带宽底层源码逻辑、参数调优、线上压测验证,是优化容器资源利用率、降低业务限流故障率、定制资源调度策略的必备能力;同时该模块源码可作为操作系统课程、内核相关毕业论文的优质研究素材。本文从原理、环境搭建、内核源码解析、用户态实操、压测验证、故障排查全链路落地,附带可直接复现的源码与 Shell 测试代码。
一、核心概念与术语解析
1.1 CFS 组调度与基础带宽控制
CFS 组调度依托 Linux cgroup v1/cgroup v2 的 CPU 控制器实现,以task_group为最小资源管控单元,传统带宽依靠两个核心参数约束:
- cpu.cfs_period_us:带宽统计周期,单位微秒,系统默认 100000us (100ms),代表每经过一个周期系统重置一次配额池;
- cpu.cfs_quota_us:单个周期内允许消耗的最大 CPU 时间,-1 代表无带宽限制。例如 period=100ms、quota=50000us 等价于限制该 cgroup 最多占用0.5 核 CPU。
传统限流规则:每个周期配额耗尽后,组内全部就绪任务加入节流链表throttled_cfs_rq,整个周期剩余时间禁止调度运行,等待下一个周期配额刷新解除限流。
1.2 Burst 突发带宽核心定义
Burst 带宽:任务组在空闲周期节省下来的未消耗 CPU 配额,存入内核专属突发带宽池,突发负载时优先消耗池内累积资源;cpu.cfs_burst_us用于限制突发池最大容量,代表该任务组最多能累积的闲置带宽上限,取值范围0 < burst_us ≤ quota_us,burst=0 时关闭突发功能(内核默认配置)。
举量化示例:
period=100ms,quota=50ms (0.5 核),burst=30ms。前 3 个周期分别只用 20ms、10ms、15ms,累计闲置 (30+40+35)=105ms,但 burst 上限 30ms,因此突发池仅留存 30ms 富余带宽;业务峰值时单次周期最多可使用
50+30=80msCPU 时间,用完突发资源后若继续超配额则恢复传统限流规则。
1.3 关键内核结构体字段
Burst 机制改动集中在kernel/sched/fair.c中struct cfs_bandwidth结构体,是整个功能实现的载体:
struct cfs_bandwidth { raw_spinlock_t lock; ktime_t period; /* 带宽周期(ns),对应cfs_period_us */ s64 quota; /* 单周期基础配额(ns),对应cfs_quota_us */ s64 runtime; /* 当前周期剩余基础配额 */ s64 burst_runtime; /* Burst累积闲置带宽,突发资源池 */ s64 burst_max; /* burst_runtime上限,映射cpu.cfs_burst_us */ struct list_head throttled_cfs_rq; /* 被节流的运行队列链表 */ struct timer_list period_timer; /* 周期刷新定时器 */ bool period_active; };字段说明:
burst_max:用户配置cpu.cfs_burst_us后内核同步赋值,锁定突发池最大容量;burst_runtime:动态增减,闲时剩余配额转入该字段,忙时优先扣减此字段资源。
1.4 配套调试与观测工具
- ftrace:跟踪
account_cfs_rq_runtime、refill_cfs_bandwidth_runtime等 Burst 关键内核函数调用; - cpu.stat:cgroup 内置统计文件,输出
nr_periods、nr_throttled、throttled_time,直观查看限流次数与耗时; - perf stat:统计进程 CPU 利用率、上下文切换,量化 Burst 开启前后限流差异;
- sysctl 参数:
/proc/sys/kernel/sched_cfs_bw_burst_enabled全局开关,默认 1 开启 Burst,0 全局禁用。
二、环境准备
2.1 软硬件环境清单
| 环境项 | 参数要求 |
|---|---|
| 操作系统 | Ubuntu22.04 LTS / Debian12(适配 5.15/6.1 内核) |
| 内核版本 | Linux5.15+、Linux6.1 LTS(5.15 之前内核无 Burst 原生支持) |
| CPU 硬件 | x86_64 双核及以上 CPU,推荐 4 核 8G 内存(便于多 cgroup 压测) |
| 编译依赖 | gcc-11、make、libncurses-dev、bison、flex、libelf-dev |
| 测试工具 | stress-ng、cpuset、cgroup-tools、perf、trace-cmd |
注:CentOS7/8 内核版本偏低,不具备原生 Burst,不推荐作为实验环境。
2.2 环境部署步骤
步骤 1:安装依赖与 cgroup 工具
# 更新源并安装编译、cgroup、压测全套工具 sudo apt update -y sudo apt install cgroup-tools stress-ng perf trace-cmd build-essential libncurses-dev bison flex libssl-dev libelf-dev -y步骤 2:确认内核 Burst 编译开关
Burst 依赖内核配置CONFIG_CFS_BANDWIDTH=y、CONFIG_CGROUP_CPUACCT=y,执行校验命令:
grep CONFIG_CFS_BANDWIDTH /boot/config-$(uname -r) grep CONFIG_CGROUP_CPUACCT /boot/config-$(uname -r)输出 = y 代表内核已开启带宽与 Burst 支持;无输出则需要自行编译开启对应配置的内核。
步骤 3:挂载 cgroup v1 CPU 子系统(实验统一用 v1,适配大多数云主机)
# 创建cgroup挂载目录 sudo mkdir -p /sys/fs/cgroup/cpu # 挂载cpu控制器 sudo mount -t cgroup -o cpu none /sys/fs/cgroup/cpu # 查看挂载结果 ls /sys/fs/cgroup/cpu/ | grep cfs挂载成功后目录下出现cpu.cfs_quota_us、cpu.cfs_period_us、cpu.cfs_burst_us三个核心配置文件。
步骤 4:全局 Burst 开关校验
# 查看全局Burst启用状态,默认1开启 cat /proc/sys/kernel/sched_cfs_bw_burst_enabled # 如需全局关闭执行:echo 0 | sudo tee /proc/sys/kernel/sched_cfs_bw_burst_enabled三、应用场景(302 字)
Burst 带宽在容器化业务场景落地广泛。互联网后端 Java 微服务容器集群中,日常接口 QPS 平稳,CPU 仅占用配置配额 40%,夜间闲置带宽持续存入突发池;早高峰流量突增 3 倍时,容器自动消耗累积 Burst 资源临时超配额运行,避免接口因 CPU 限流超时。边缘网关嵌入式 Linux 设备中,网关进程常态低负载,定时日志压缩、报文批量上报瞬时算力暴涨,依靠 Burst 借用历史闲置带宽,无需为瞬时峰值冗余配置高额 CPU 配额,大幅降低硬件采购成本。大数据离线计算场景,容器集群日间业务空闲累积带宽,夜间批量 Spark 任务启动时透支突发资源,缩短批处理运行时长。云主机按量计费场景,运维通过合理配置 burst_us,在保障业务峰值稳定性前提下压缩基础 CPU 配额,实现资源成本与业务稳定性平衡。
四、实际案例与步骤(内核源码 + 用户态实操 + 压测全代码)
本章节分为三部分:①Burst 核心内核源码逐行注释解析;②Shell 脚本创建 cgroup、配置带宽与 burst 参数;③C 语言 CPU 压测程序对比开启 / 关闭 Burst 限流差异;④ftrace 跟踪内核函数验证资源流转逻辑。
4.1 内核关键源码解析(kernel/sched/fair.c)
4.1.1 周期刷新函数 refill_cfs_bandwidth_runtime:闲时带宽存入 Burst 池
每个 period 定时器到期时触发,刷新基础配额,剩余未使用带宽转入突发资源池:
static void refill_cfs_bandwidth_runtime(struct cfs_bandwidth *cfs_b) { s64 unused; /* 1. 补充本周期固定基础配额 */ cfs_b->runtime += cfs_b->quota; /* 2. 计算上个周期剩余没用完的基础配额=新增可存入burst的资源 */ unused = cfs_b->quota - (cfs_b->quota - cfs_b->runtime); if(unused > 0 && cfs_b->burst_max > 0){ /* 剩余带宽存入burst_runtime,不能超过burst_max上限 */ cfs_b->burst_runtime = min(cfs_b->burst_runtime + unused, cfs_b->burst_max); } /* burst=0则不累积闲置带宽,传统限流逻辑 */ }代码作用:闲时任务用不完 quota,剩余资源自动攒入 burst_runtime,是突发资源的来源;burst_max 由用户写入 cpu.cfs_burst_us 决定上限。
4.1.2 account_cfs_rq_runtime:任务运行扣减资源,优先消耗 Burst
任务运行消耗 CPU 时间时,内核优先扣突发池资源,耗尽后再消耗基础 runtime,基础配额用尽触发限流:
static int account_cfs_rq_runtime(struct cfs_rq *cfs_rq, s64 delta_exec) { struct cfs_bandwidth *cfs_b = &task_group(cfs_rq->tg)->cfs_bandwidth; s64 remaining = delta_exec; raw_spin_lock(&cfs_b->lock); /* 第一步:优先消耗Burst突发资源池 */ if(cfs_b->burst_runtime > 0 && remaining > 0){ s64 use_burst = min(cfs_b->burst_runtime, remaining); cfs_b->burst_runtime -= use_burst; remaining -= use_burst; } /* 第二步:再消耗本周期基础配额runtime */ if(remaining > 0){ cfs_b->runtime -= remaining; } raw_spin_unlock(&cfs_b->lock); /* 基础配额+突发资源全部耗尽,返回非0触发throttle节流 */ return (cfs_b->runtime < 0 && cfs_b->burst_runtime <= 0) ? 1 : 0; }核心逻辑:突发场景下优先透支历史闲置带宽,只有突发池 + 基础配额全部用光才执行限流,完美实现短时超额运行。
4.1.3 cfs_bandwidth_write:用户配置 cpu.cfs_burst_us 写入内核
用户向文件写入 burst 数值时,内核同步更新burst_max字段:
static ssize_t cfs_bandwidth_write(struct kernfs_open_file *of, char *buf, size_t nbytes, loff_t off) { struct task_group *tg = cgroup_kn_lock_live(of->kn, cpu_cgrp_subsys); struct cfs_bandwidth *b = &tg->cfs_bandwidth; u64 burst_us; kstrtoull(buf, 10, &burst_us); /* burst不能超过quota数值,内核做合法性校验 */ if(burst_us > (b->quota / NSEC_PER_USEC)) burst_us = b->quota / NSEC_PER_USEC; /* 微秒转纳秒存入burst_max */ b->burst_max = burst_us * NSEC_PER_USEC; cgroup_kn_unlock(of->kn); return nbytes; }4.2 实操案例 1:Shell 脚本创建 cgroup、配置带宽与 Burst 参数
新建burst_setup.sh,所有命令可直接复制运行,功能:创建 test_burst 分组、配置 period=100ms、quota=50ms (0.5 核)、burst=30ms:
#!/bin/bash # burst_setup.sh 配置CFS带宽与突发参数 CGROUP_PATH=/sys/fs/cgroup/cpu/test_burst sudo mkdir -p ${CGROUP_PATH} # 1. 设置周期100000us(100ms) echo 100000 | sudo tee ${CGROUP_PATH}/cpu.cfs_period_us # 2. 设置基础配额50000us(50ms=0.5CPU) echo 50000 | sudo tee ${CGROUP_PATH}/cpu.cfs_quota_us # 3. 设置Burst最大累积30000us(30ms),开启突发功能 echo 30000 | sudo tee ${CGROUP_PATH}/cpu.cfs_burst_us # 查看配置结果 echo "===参数配置结果===" cat ${CGROUP_PATH}/cpu.cfs_period_us cat ${CGROUP_PATH}/cpu.cfs_quota_us cat ${CGROUP_PATH}/cpu.cfs_burst_us # 清空历史统计数据 echo 0 | sudo tee ${CGROUP_PATH}/cpu.stat执行授权与运行:
chmod +x burst_setup.sh sudo ./burst_setup.sh4.3 实操案例 2:C 语言 CPU 压测程序,模拟闲时 + 突发负载
新建cpu_stress.c,程序逻辑:前 10 秒低负载运行(闲时积攒 Burst 带宽),后 10 秒满负载跑 CPU(突发峰值),用于对比关闭 / 开启 Burst 的限流表现:
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/resource.h> /* 空循环消耗CPU */ void cpu_burn(int load_ratio) { unsigned long i; while(1){ for(i=0; i<100000*load_ratio; i++); /* load_ratio=3:低负载,占用约0.25核;load_ratio=12:满负载超0.5核 */ usleep(100); } } int main(void) { pid_t pid = getpid(); printf("stress pid=%d, 前10s低负载攒Burst,后10s峰值压测\n", pid); /* 前10s低负载,仅消耗约20ms/周期,富余30ms存入突发池 */ cpu_burn(3); sleep(10); /* 后10s满载,需求0.9核,超出基础0.5核,依赖Burst资源 */ cpu_burn(12); return 0; }编译 + 运行步骤:
gcc cpu_stress.c -o cpu_stress # 后台启动压测程序 ./cpu_stress & # 获取PID,加入cgroup echo $! | sudo tee /sys/fs/cgroup/cpu/test_burst/cgroup.procs4.4 实操案例 3:观测限流数据 + ftrace 跟踪 Burst 内核函数
① 查看 cpu.stat 统计,验证 Burst 规避限流
运行 20s 后执行:
cat /sys/fs/cgroup/cpu/test_burst/cpu.stat字段释义:
nr_throttled:限流触发次数,开启 burst 后理论为 0;关闭 burst (设 burst_us=0) 重新测试,该数值会大幅上涨;throttled_time:累计被节流的 CPU 时间。
② ftrace 跟踪 Burst 关键函数调用
# 挂载debugfs sudo mount -t debugfs none /sys/kernel/debug # 清空跟踪缓存 echo > /sys/kernel/debug/tracing/trace # 配置跟踪Burst三大核心函数 echo account_cfs_rq_runtime >> /sys/kernel/debug/tracing/set_ftrace_filter echo refill_cfs_bandwidth_runtime >> /sys/kernel/debug/tracing/set_ftrace_filter echo cfs_bandwidth_write >> /sys/kernel/debug/tracing/set_ftrace_filter # 开启函数跟踪 echo function > /sys/kernel/debug/tracing/current_tracer echo 1 > /sys/kernel/debug/tracing/tracing_on # 另起终端重新启动压测程序,等待15s后关闭跟踪 echo 0 > /sys/kernel/debug/tracing/tracing_on # 查看内核调用日志 cat /sys/kernel/debug/tracing/trace日志可直观看到:闲时周期结束触发refill_cfs_bandwidth_runtime存入 burst 资源,峰值阶段account_cfs_rq_runtime优先扣减 burst_runtime。
4.5 对照实验:关闭 Burst 验证传统限流
# 关闭突发,burst=0 echo 0 | sudo tee /sys/fs/cgroup/cpu/test_burst/cpu.cfs_burst_us # 杀掉原有进程,重新运行压测 pkill cpu_stress ./cpu_stress & echo $! | sudo tee /sys/fs/cgroup/cpu/test_burst/cgroup.procs # 20s后查看stat,nr_throttled>0代表触发限流 cat /sys/fs/cgroup/cpu/test_burst/cpu.stat五、常见问题与解答
Q1:配置 cpu.cfs_burst_us 大于 cfs_quota_us 写入失败?
解答:内核源码cfs_bandwidth_write做参数校验,burst_us 最大值不能超过同组 quota_us,超出部分内核自动截断为 quota 数值。设计初衷:突发累积资源不能超过单个周期全部配额,防止无上限透支资源破坏整体资源隔离。
Q2:burst 配置正常,但突发峰值依旧被限流?
解答:分 3 点排查:①全局开关sched_cfs_bw_burst_enabled=0,全局禁用突发;②前期无空闲周期,突发池burst_runtime=0没有积攒富余带宽;③cgroup 层级嵌套,父 cgroup 配额不足,子组无法借用父级 burst 资源,CFS 带宽层级隔离。排查命令cat /proc/sys/kernel/sched_cfs_bw_burst_enabled、cat cpu.stat。
Q3:同一个 cgroup 多 CPU 核上多进程并发,burst 资源跨 CPU 共享吗?
解答:Burst 资源属于task_group 全局资源池,组内所有 CPU 运行队列共用同一个cfs_bandwidth->burst_runtime,任意 CPU 空闲富余带宽全部汇入全局突发池,任意 CPU 峰值均可透支使用,内核通过 cfs_b->lock 自旋锁做并发保护。
Q4:修改 quota_us 后原有 burst_runtime 数据会清空吗?
解答:修改 quota 会同步更新 burst_max 上限,原有已累积的 burst_runtime 若超出新 burst_max,超出部分直接丢弃;用户主动改写 burst_us 数值不会清空存量突发资源。
Q5:cgroup v2 环境下 burst 配置文件名变了吗?
解答:v2 统一在cpu.max配置 quota/period,cpu.burst配置突发上限,不再拆分多个独立文件,v1 为 cpu.cfs_* 系列文件,实验环境推荐 v1 方便新手调试。
六、实践建议与最佳实践
6.1 线上参数调优规范
- period 选型:常规业务固定 100ms (100000us),低延迟网关类业务缩小至 50ms;period 过小会提升内核定时器刷新开销,过大带宽统计粒度粗糙。
- burst_us 配置:建议取值
0.3~0.5 * quota_us,即突发上限为基础配额的 30%~50%;峰值波动剧烈的批处理服务可上调至 70%,不建议等于 quota,避免过度透支影响整机资源均衡。 - 容器 K8s 落地:通过 limit 配置 quota,request 对应基础配额,burst 借助
kubectl patch动态注入 cpu.cfs_burst_us,实现容器闲时攒带宽、峰值防限流。
6.2 内核调试排障技巧
- 业务突发卡顿优先查看
cpu.stat->nr_throttled,数值持续上涨代表 burst 配置不足或未开启突发; - 突发资源异常不积攒时,用 ftrace 抓取
refill_cfs_bandwidth_runtime调用,确认周期定时器正常触发; - 排查资源泄露:定期监控 burst_runtime 累积上限,避免异常死循环进程长期占满突发池。
6.3 性能优化避坑
- 禁止单个 cgroup 内进程数量超百,大量进程频繁切换会加速突发资源消耗,缩短峰值支撑时长;
- 实时 SCHED_FIFO/SCHED_RR 进程不受 CFS 带宽与 Burst 管控,若组内混杂实时任务,实时进程占用 CPU 不会消耗 burst 资源;
- 频繁动态修改 quota/burst 参数会重置带宽池,生产环境避免运行中反复变更配置。
6.4 内核二次开发建议
如需自研增强 Burst 规则(比如多周期跨周累积),优先修改refill_cfs_bandwidth_runtime闲置带宽入库逻辑,不要改动account_cfs_rq_runtime资源扣减路径,该函数处于调度热点路径,修改不当极易引发系统调度卡顿。
七、总结与应用延伸
本文从底层结构体定义、核心源码实现、用户态参数配置、C 语言压测验证、ftrace 内核跟踪完整拆解 CFS Burst 突发带宽机制。Burst 本质是CFS 固定带宽限流的弹性优化,通过闲置带宽跨周期存储,在长期平均 CPU 占用不超配额的约束下,赋予任务组短时超额使用算力的能力,从机制上化解固定配额和瞬时业务峰值的冲突。
从工程落地看,该特性是云原生容器、边缘嵌入式、大数据离线集群资源优化的核心手段,合理配置 burst 可以在不提升基础 CPU 配额、不增加硬件成本的前提下大幅降低业务限流故障率;从学术研究角度,Burst 源码涉及资源记账、周期定时器、内核自旋锁、cgroup 层级资源隔离等经典操作系统知识点,可直接用于操作系统论文、内核课题的案例分析。
建议读者基于本文测试代码,修改 quota、burst 数值反复压测对比限流数据,也可尝试在内核源码中微调 burst 资源上限逻辑,重新编译内核观察业务负载变化,把理论原理落地到实操,后续可结合 K8s 容器资源管理做生产环境落地调优。
